/** | |
* \file | |
* | |
* \brief USB Device Personal Healthcare Device Class (PHDC) interface. | |
* | |
* Copyright (C) 2009 Atmel Corporation. All rights reserved. | |
* | |
* \page License | |
* | |
* 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 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. The name of Atmel may not be used to endorse or promote products derived | |
* from this software without specific prior written permission. | |
* | |
* 4. This software may only be redistributed and used in connection with an | |
* Atmel AVR product. | |
* | |
* THIS SOFTWARE IS PROVIDED BY ATMEL "AS IS" AND ANY EXPRESS OR IMPLIED | |
* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF | |
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT ARE | |
* EXPRESSLY AND SPECIFICALLY DISCLAIMED. IN NO EVENT SHALL ATMEL 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 "conf_usb.h" | |
#include "usb_protocol.h" | |
#include "usb_protocol_phdc.h" | |
#include "udd.h" | |
#include "udc.h" | |
#include "udi_phdc.h" | |
#include <string.h> | |
#if ((UDI_PHDC_QOS_OUT&USB_PHDC_QOS_LOW_GOOD)==USB_PHDC_QOS_LOW_GOOD) | |
# error LOW-GOOD (latency-reliability) is not authorized on OUT bulk endpoint | |
#endif | |
/** | |
* \addtogroup udi_phdc_group | |
* @{ | |
*/ | |
/** | |
* \name Interface for UDC | |
*/ | |
//@{ | |
bool udi_phdc_enable(void); | |
void udi_phdc_disable(void); | |
bool udi_phdc_setup(void); | |
uint8_t udi_phdc_getsetting(void); | |
//! Global struture which contains standard UDI API for UDC | |
UDC_DESC_STORAGE udi_api_t udi_api_phdc = { | |
.enable = udi_phdc_enable, | |
.disable = udi_phdc_disable, | |
.setup = udi_phdc_setup, | |
.getsetting = udi_phdc_getsetting, | |
}; | |
//@} | |
/** | |
* \name Internal variables to manage PHDC | |
*/ | |
//@{ | |
//! Variable to store the transfer pending flag | |
static le16_t udi_phdc_holding_data; | |
//! Flag to signal the state of preample feature | |
static uint8_t udi_phdc_preample_feature; | |
COMPILER_PACK_SET(1); | |
//! Structure to manage the transfer on PHDC IN endpoints | |
static struct { | |
bool b_run; | |
bool b_preample_run; | |
#if (UDI_PHDC_PREAMBLE_FEATURE == TRUE) | |
usb_phdc_metadata_msg_t preample_header; | |
uint8_t preample_opaque_data[UDI_PHDC_EP_SIZE_BULK_IN - | |
sizeof(usb_phdc_metadata_msg_t)]; | |
#endif | |
udi_phdc_metadata_t *metadata; | |
uint16_t metadata_pos; | |
void (*callback) (uint16_t); | |
void *cntx; | |
} udi_phdc_in_trans; | |
//! Structure to manage the transfer on PHDC OUT endpoint | |
static struct { | |
bool b_run; | |
bool b_preample_run; | |
#if (UDI_PHDC_PREAMBLE_FEATURE == TRUE) | |
usb_phdc_metadata_msg_t preample_header; | |
uint8_t preample_opaque_data[UDI_PHDC_EP_SIZE_BULK_OUT - | |
sizeof(usb_phdc_metadata_msg_t)]; | |
#endif | |
udi_phdc_metadata_t *metadata; | |
uint16_t metadata_pos; | |
void (*callback) (uint16_t); | |
void *cntx; | |
} udi_phdc_out_trans; | |
COMPILER_PACK_RESET(); | |
//@} | |
/** | |
* \name Internal routines | |
*/ | |
//@{ | |
/*! \brief This function sends a preample message | |
* | |
* \retval true when success | |
*/ | |
static bool udi_phdc_send_preamplemsg(void); | |
/*! \brief Callback called when preample message is sent | |
* | |
* \retval status Transfer state (UDD_EP_TRANSFER_ABORT/_OK) | |
* \retval nb_send Number of data sent | |
*/ | |
void udi_phdc_preamplemsg_ack(udd_ep_status_t status, iram_size_t nb_send); | |
/*! \brief This function sends a metadata | |
* | |
* \retval true when success | |
*/ | |
static bool udi_phdc_send_metadata(void); | |
/*! \brief Callback called when metadata is sent | |
* | |
* \retval status Transfer state (UDD_EP_TRANSFER_ABORT/_OK) | |
* \retval nb_send Number of data sent | |
*/ | |
void udi_phdc_metadata_ack(udd_ep_status_t status, iram_size_t nb_send); | |
/*! \brief This function requests a preample message | |
* | |
* \retval true when success | |
*/ | |
static bool udi_phdc_wait_preamplemsg(void); | |
/*! \brief Callback called when metadata is received | |
* | |
* \retval status Transfer state (UDD_EP_TRANSFER_ABORT/_OK) | |
* \retval nb_received Number of data received | |
*/ | |
void udi_phdc_received_preample(udd_ep_status_t status, | |
iram_size_t nb_received); | |
/*! \brief This function request metadata | |
* | |
* \retval true when success | |
*/ | |
static bool udi_phdc_wait_metadata(void); | |
/*! \brief Callback called when metadata is received | |
* | |
* \retval status Transfer state (UDD_EP_TRANSFER_ABORT/_OK) | |
* \retval nb_received Number of data received | |
*/ | |
void udi_phdc_received_metadata(udd_ep_status_t status, | |
iram_size_t nb_received); | |
//@} | |
//-------------------------------------------- | |
//------ Interface for UDC | |
bool udi_phdc_enable(void) | |
{ | |
uint8_t sig_tmp[] = METADATA_MESSAGE_SIG; | |
// Enable external component of PHDC interface | |
udi_phdc_preample_feature = false; | |
udi_phdc_holding_data = 0; | |
udi_phdc_in_trans.b_run = false; | |
udi_phdc_out_trans.b_run = false; | |
// Init struct signature | |
memcpy(udi_phdc_in_trans.preample_header.aSignature, sig_tmp, | |
sizeof(sig_tmp)); | |
udi_phdc_in_trans.preample_header.bQoSEncodingVersion = | |
USB_PHDC_QOS_ENCODING_VERSION_1; | |
return UDI_PHDC_ENABLE_EXT(); | |
} | |
void udi_phdc_disable(void) | |
{ | |
UDI_PHDC_DISABLE_EXT(); | |
} | |
bool udi_phdc_setup(void) | |
{ | |
udd_ep_id_t ep_num; | |
//** Interface requests | |
if (Udd_setup_is_in()) { | |
// Requests Interface GET | |
if (udd_g_ctrlreq.req.wLength == 0) | |
return false; // Error for USB host | |
if (Udd_setup_type() == USB_REQ_TYPE_CLASS) { | |
// Requests Class Interface Get | |
switch (udd_g_ctrlreq.req.bRequest) { | |
case USB_REQ_GET_STATUS: | |
if (udd_g_ctrlreq.req.wValue != 0) | |
return false; | |
if (udd_g_ctrlreq.req.wLength != | |
sizeof(udi_phdc_holding_data)) | |
return false; | |
udd_g_ctrlreq.payload = | |
(uint8_t *) & | |
udi_phdc_holding_data; | |
udd_g_ctrlreq.payload_size = | |
sizeof(udi_phdc_holding_data); | |
return true; | |
} | |
} | |
} | |
if (Udd_setup_is_out()) { | |
// Requests Interface SET | |
if (Udd_setup_type() == USB_REQ_TYPE_CLASS) { | |
// Requests Class Interface Set | |
switch (udd_g_ctrlreq.req.bRequest) { | |
#if (UDI_PHDC_PREAMBLE_FEATURE == TRUE) | |
case USB_REQ_CLEAR_FEATURE: | |
if ((udd_g_ctrlreq.req.wValue & 0xFF) != | |
USB_PHDC_FEATURE_METADATA) | |
return false; | |
if ((udd_g_ctrlreq.req.wValue >> 8) != 0) | |
return false; | |
if (0 != udi_phdc_holding_data) { | |
#if ((UDI_PHDC_QOS_IN&USB_PHDC_QOS_LOW_GOOD)==USB_PHDC_QOS_LOW_GOOD) | |
if (USB_PHDC_QOS_LOW_GOOD == | |
udi_phdc_in_trans.metadata-> | |
qos) | |
ep_num = UDI_PHDC_EP_INTERRUPT_IN; | |
else | |
#endif | |
ep_num = UDI_PHDC_EP_BULK_IN; | |
udd_ep_abort(ep_num); | |
} | |
udi_phdc_preample_feature = false; | |
if (udi_phdc_out_trans.b_run) { | |
// Kill waiting transfer to start new one | |
udd_ep_abort(UDI_PHDC_EP_BULK_OUT); | |
} | |
return true; | |
case USB_REQ_SET_FEATURE: | |
if (udd_g_ctrlreq.req.wLength != 0) | |
return false; | |
if ((udd_g_ctrlreq.req.wValue & 0xFF) != | |
USB_PHDC_FEATURE_METADATA) | |
return false; | |
if ((udd_g_ctrlreq.req.wValue >> 8) != | |
USB_PHDC_QOS_ENCODING_VERSION_1) | |
return false; | |
if (0 != udi_phdc_holding_data) { | |
#if ((UDI_PHDC_QOS_IN&USB_PHDC_QOS_LOW_GOOD)==USB_PHDC_QOS_LOW_GOOD) | |
if (USB_PHDC_QOS_LOW_GOOD == | |
udi_phdc_in_trans.metadata-> | |
qos) | |
ep_num = UDI_PHDC_EP_INTERRUPT_IN; | |
else | |
#endif | |
ep_num = UDI_PHDC_EP_BULK_IN; | |
udd_ep_abort(ep_num); | |
} | |
udi_phdc_preample_feature = true; | |
if (udi_phdc_out_trans.b_run) { | |
// Kill waiting transfer to start new one | |
udd_ep_abort(UDI_PHDC_EP_BULK_OUT); | |
} | |
return true; | |
#endif | |
} | |
} | |
} | |
return false; // Not supported request | |
} | |
uint8_t udi_phdc_getsetting(void) | |
{ | |
return 0; // Always 0, no alternate setting on this interface | |
} | |
//-------------------------------------------- | |
//------ Interface for application | |
bool udi_phdc_senddata(udi_phdc_metadata_t * metadata, | |
void (*callback) (uint16_t)) | |
{ | |
bool b_status; | |
irqflags_t flags; | |
uint8_t qos = metadata->qos; | |
// Check qos: Only one bit must be set in qos | |
if ((0 == qos) || (0 != (qos & (qos - 1)))) | |
return false; // bad qos value | |
// Check if qos is supported | |
if (0 == (qos & UDI_PHDC_QOS_IN)) | |
return false; // qos not supported | |
if (udi_phdc_in_trans.b_run) | |
return false; // transfer already running | |
// Init transfer | |
udi_phdc_in_trans.metadata = metadata; | |
udi_phdc_in_trans.metadata_pos = 0; | |
udi_phdc_in_trans.callback = callback; | |
udi_phdc_in_trans.b_preample_run = false; | |
flags = cpu_irq_save(); | |
#if (UDI_PHDC_PREAMBLE_FEATURE == TRUE) | |
// It is a bulk then check preample feature | |
if ((USB_PHDC_QOS_LOW_GOOD != qos) | |
&& (udi_phdc_preample_feature)) { | |
b_status = udi_phdc_send_preamplemsg(); | |
} else | |
#endif | |
b_status = udi_phdc_send_metadata(); | |
cpu_irq_restore(flags); | |
return b_status; | |
} | |
bool udi_phdc_waitdata(udi_phdc_metadata_t * metadata, | |
void (*callback) (uint16_t)) | |
{ | |
bool b_status; | |
irqflags_t flags; | |
if (udi_phdc_out_trans.b_run) | |
return false; // transfer already running | |
// Init transfer | |
udi_phdc_out_trans.metadata = metadata; | |
udi_phdc_out_trans.metadata_pos = 0; | |
udi_phdc_out_trans.callback = callback; | |
flags = cpu_irq_save(); | |
#if (UDI_PHDC_PREAMBLE_FEATURE == TRUE) | |
// It is a bulk then check preample feature | |
if (udi_phdc_preample_feature) { | |
b_status = udi_phdc_wait_preamplemsg(); | |
} else | |
#endif | |
{ | |
// Enable of wait data | |
#if (UDI_PHDC_PREAMBLE_FEATURE == TRUE) | |
udi_phdc_out_trans.preample_header.bmLatencyReliability = 0; | |
#else | |
udi_phdc_out_trans.preample_header.bmLatencyReliability = | |
UDI_PHDC_QOS_OUT; | |
#endif | |
udi_phdc_out_trans.preample_header.bNumTransfers = | |
udi_phdc_out_trans.metadata->metadata_size / | |
UDI_PHDC_EP_SIZE_BULK_OUT; | |
b_status = udi_phdc_wait_metadata(); | |
} | |
cpu_irq_restore(flags); | |
return b_status; | |
} | |
//-------------------------------------------- | |
//------ Internal routines | |
static bool udi_phdc_send_preamplemsg(void) | |
{ | |
if ((UDI_PHDC_EP_SIZE_BULK_IN - 1 - sizeof(usb_phdc_metadata_msg_t)) | |
< udi_phdc_in_trans.metadata->opaque_size) | |
return false; // Opaque data too large | |
// Fill preample message | |
udi_phdc_in_trans.preample_header.bNumTransfers = | |
1 + ((udi_phdc_in_trans.metadata->metadata_size - | |
1) / UDI_PHDC_EP_SIZE_BULK_IN); | |
udi_phdc_in_trans.preample_header.bmLatencyReliability = | |
udi_phdc_in_trans.metadata->qos; | |
udi_phdc_in_trans.preample_header.bOpaqueDataSize = | |
udi_phdc_in_trans.metadata->opaque_size; | |
memcpy(udi_phdc_in_trans.preample_opaque_data, | |
udi_phdc_in_trans.metadata->opaquedata, | |
udi_phdc_in_trans.metadata->opaque_size); | |
// Send preample message | |
if (!udd_ep_run(UDI_PHDC_EP_BULK_IN, | |
false, | |
(uint8_t *) & | |
udi_phdc_in_trans.preample_header, | |
udi_phdc_in_trans. | |
preample_header.bOpaqueDataSize + | |
sizeof(usb_phdc_metadata_msg_t), | |
udi_phdc_preamplemsg_ack)) { | |
return false; | |
} | |
udi_phdc_in_trans.b_run = true; | |
udi_phdc_in_trans.b_preample_run = true; | |
udi_phdc_holding_data |= | |
1 << (UDI_PHDC_EP_SIZE_BULK_IN & USB_EP_ADDR_MASK); | |
return true; | |
} | |
void udi_phdc_preamplemsg_ack(udd_ep_status_t status, iram_size_t nb_send) | |
{ | |
// Preample sending | |
udi_phdc_in_trans.b_run = false; | |
udi_phdc_holding_data &= | |
~(1 << (UDI_PHDC_EP_SIZE_BULK_IN & USB_EP_ADDR_MASK)); | |
udi_phdc_in_trans.b_preample_run = false; | |
if (UDD_EP_TRANSFER_ABORT == status) { | |
// Transfer abort | |
udi_phdc_in_trans.callback(0); | |
return; | |
} | |
if (!udi_phdc_send_metadata()) { | |
// Transfer metadata impossible | |
udi_phdc_in_trans.callback(0); | |
} | |
} | |
static bool udi_phdc_send_metadata(void) | |
{ | |
udd_ep_id_t ep_num; | |
#if ((UDI_PHDC_QOS_IN&USB_PHDC_QOS_LOW_GOOD)==USB_PHDC_QOS_LOW_GOOD) | |
if (USB_PHDC_QOS_LOW_GOOD == udi_phdc_in_trans.metadata->qos) | |
ep_num = UDI_PHDC_EP_INTERRUPT_IN; | |
else | |
#endif | |
ep_num = UDI_PHDC_EP_BULK_IN; | |
// Send data | |
if (!udd_ep_run(ep_num, false, udi_phdc_in_trans.metadata->metadata, | |
udi_phdc_in_trans. | |
metadata->metadata_size, | |
udi_phdc_metadata_ack)) { | |
// Error then end of transfer | |
return false; | |
} | |
// Uptade struct | |
udi_phdc_holding_data |= 1 << (ep_num & USB_EP_ADDR_MASK); | |
udi_phdc_in_trans.b_run = true; | |
return true; | |
} | |
void udi_phdc_metadata_ack(udd_ep_status_t status, iram_size_t nb_send) | |
{ | |
udd_ep_id_t ep_num; | |
#if ((UDI_PHDC_QOS_IN&USB_PHDC_QOS_LOW_GOOD)==USB_PHDC_QOS_LOW_GOOD) | |
if (USB_PHDC_QOS_LOW_GOOD == udi_phdc_in_trans.metadata->qos) | |
ep_num = UDI_PHDC_EP_INTERRUPT_IN; | |
else | |
#endif | |
ep_num = UDI_PHDC_EP_BULK_IN; | |
udi_phdc_in_trans.b_run = false; | |
udi_phdc_holding_data &= ~(1 << (ep_num & USB_EP_ADDR_MASK)); | |
udi_phdc_in_trans.callback(nb_send); | |
} | |
#if (UDI_PHDC_PREAMBLE_FEATURE == TRUE) | |
static bool udi_phdc_wait_preamplemsg(void) | |
{ | |
// Enable the reception of preample message | |
if (!udd_ep_run(UDI_PHDC_EP_BULK_OUT, | |
false, | |
(uint8_t *) & | |
udi_phdc_out_trans.preample_header, | |
UDI_PHDC_EP_SIZE_BULK_OUT, | |
udi_phdc_received_preample)) | |
return false; | |
udi_phdc_out_trans.b_run = true; | |
udi_phdc_out_trans.b_preample_run = true; | |
return true; | |
} | |
void udi_phdc_received_preample(udd_ep_status_t status, iram_size_t nb_received) | |
{ | |
uint8_t sig_tmp[] = METADATA_MESSAGE_SIG; | |
udi_phdc_out_trans.b_preample_run = false; | |
// Check preample integrity | |
if (UDD_EP_TRANSFER_ABORT == status) | |
goto udi_phdc_received_preample_abort; | |
if (nb_received < sizeof(usb_phdc_metadata_msg_t)) | |
goto udi_phdc_received_preample_bad; | |
if (0 != memcmp(udi_phdc_out_trans.preample_header.aSignature, sig_tmp, | |
sizeof(sig_tmp))) | |
goto udi_phdc_received_preample_bad; | |
if (0 == udi_phdc_out_trans.preample_header.bNumTransfers) | |
goto udi_phdc_received_preample_bad; | |
if (USB_PHDC_QOS_ENCODING_VERSION_1 != | |
udi_phdc_out_trans.preample_header.bQoSEncodingVersion) | |
goto udi_phdc_received_preample_bad; | |
if (0 != (udi_phdc_out_trans.preample_header.bmLatencyReliability | |
& (udi_phdc_out_trans. | |
preample_header.bmLatencyReliability | |
- 1))) | |
goto udi_phdc_received_preample_bad; | |
if (0 == (UDI_PHDC_QOS_OUT & udi_phdc_out_trans. | |
preample_header.bmLatencyReliability)) | |
goto udi_phdc_received_preample_bad; | |
if (nb_received != | |
(udi_phdc_out_trans.preample_header.bOpaqueDataSize + | |
sizeof(usb_phdc_metadata_msg_t))) | |
goto udi_phdc_received_preample_bad; | |
udi_phdc_out_trans.metadata->opaque_size = | |
udi_phdc_out_trans.preample_header.bOpaqueDataSize; | |
udi_phdc_out_trans.metadata->opaquedata = | |
udi_phdc_out_trans.preample_opaque_data; | |
if (!udi_phdc_wait_metadata()) | |
goto udi_phdc_received_preample_bad; | |
return; | |
udi_phdc_received_preample_bad: | |
// Bad preample message then stall this endpoint | |
udd_ep_set_halt(UDI_PHDC_EP_BULK_OUT); | |
udi_phdc_received_preample_abort: | |
udi_phdc_out_trans.b_run = false; | |
udi_phdc_out_trans.callback(0); | |
} | |
#endif | |
static bool udi_phdc_wait_metadata(void) | |
{ | |
// Check output buffer size | |
if (udi_phdc_out_trans.metadata->metadata_size | |
< | |
(udi_phdc_out_trans.preample_header.bNumTransfers * | |
UDI_PHDC_EP_SIZE_BULK_OUT)) | |
return false; | |
// Wait metadata | |
udi_phdc_out_trans.b_run = | |
udd_ep_run(UDI_PHDC_EP_BULK_OUT, | |
false, | |
udi_phdc_out_trans.metadata->metadata, | |
udi_phdc_out_trans.preample_header.bNumTransfers * | |
UDI_PHDC_EP_SIZE_BULK_OUT, udi_phdc_received_metadata); | |
return udi_phdc_out_trans.b_run; | |
} | |
void udi_phdc_received_metadata(udd_ep_status_t status, iram_size_t nb_received) | |
{ | |
if (UDD_EP_TRANSFER_OK == status) | |
udi_phdc_out_trans.b_preample_run = false; | |
udi_phdc_out_trans.b_run = false; | |
udi_phdc_out_trans.callback(nb_received); | |
} | |
//@} |