blob: 96e8db3073e64abbd6ed2f6535f079f32be53305 [file] [log] [blame]
/**
* \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);
}
//@}