blob: ca32848da19ab93ef4868863ee62c56c4a240a64 [file] [log] [blame]
/*
* Copyright (c) 2013-2018 The Linux Foundation. All rights reserved.
*
* Previously licensed under the ISC license by Qualcomm Atheros, Inc.
*
*
* Permission to use, copy, modify, and/or distribute this software for
* any purpose with or without fee is hereby granted, provided that the
* above copyright notice and this permission notice appear in all
* copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL
* WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE
* AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL
* DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR
* PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
* TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
* PERFORMANCE OF THIS SOFTWARE.
*/
/*
* This file was originally distributed by Qualcomm Atheros, Inc.
* under proprietary terms before Copyright ownership was assigned
* to the Linux Foundation.
*/
#include "htc_debug.h"
#include "htc_internal.h"
#include <qdf_nbuf.h> /* qdf_nbuf_t */
/* HTC Control message receive timeout msec */
#define HTC_CONTROL_RX_TIMEOUT 6000
#if defined(WLAN_DEBUG) || defined(DEBUG)
void debug_dump_bytes(uint8_t *buffer, uint16_t length, char *pDescription)
{
int8_t stream[60];
int8_t byteOffsetStr[10];
uint32_t i;
uint16_t offset, count, byteOffset;
A_PRINTF("<---------Dumping %d Bytes : %s ------>\n", length,
pDescription);
count = 0;
offset = 0;
byteOffset = 0;
for (i = 0; i < length; i++) {
A_SNPRINTF(stream + offset, (sizeof(stream) - offset),
"%02X ", buffer[i]);
count++;
offset += 3;
if (count == 16) {
count = 0;
offset = 0;
A_SNPRINTF(byteOffsetStr, sizeof(byteOffset), "%4.4X",
byteOffset);
A_PRINTF("[%s]: %s\n", byteOffsetStr, stream);
qdf_mem_zero(stream, 60);
byteOffset += 16;
}
}
if (offset != 0) {
A_SNPRINTF(byteOffsetStr, sizeof(byteOffset), "%4.4X",
byteOffset);
A_PRINTF("[%s]: %s\n", byteOffsetStr, stream);
}
A_PRINTF("<------------------------------------------------->\n");
}
#else
void debug_dump_bytes(uint8_t *buffer, uint16_t length, char *pDescription)
{
}
#endif
static A_STATUS htc_process_trailer(HTC_TARGET *target,
uint8_t *pBuffer,
int Length, HTC_ENDPOINT_ID FromEndpoint);
static void do_recv_completion_pkt(HTC_ENDPOINT *pEndpoint,
HTC_PACKET *pPacket)
{
if (pEndpoint->EpCallBacks.EpRecv == NULL) {
AR_DEBUG_PRINTF(ATH_DEBUG_ERR,
("HTC ep %d has NULL recv callback on packet %pK\n",
pEndpoint->Id,
pPacket));
if (pPacket)
qdf_nbuf_free(pPacket->pPktContext);
} else {
AR_DEBUG_PRINTF(ATH_DEBUG_RECV,
("HTC calling ep %d recv callback on packet %pK\n",
pEndpoint->Id, pPacket));
pEndpoint->EpCallBacks.EpRecv(pEndpoint->EpCallBacks.pContext,
pPacket);
}
}
static void do_recv_completion(HTC_ENDPOINT *pEndpoint,
HTC_PACKET_QUEUE *pQueueToIndicate)
{
HTC_PACKET *pPacket;
if (HTC_QUEUE_EMPTY(pQueueToIndicate)) {
/* nothing to indicate */
return;
}
while (!HTC_QUEUE_EMPTY(pQueueToIndicate)) {
pPacket = htc_packet_dequeue(pQueueToIndicate);
do_recv_completion_pkt(pEndpoint, pPacket);
}
}
static void recv_packet_completion(HTC_TARGET *target, HTC_ENDPOINT *pEndpoint,
HTC_PACKET *pPacket)
{
do_recv_completion_pkt(pEndpoint, pPacket);
/* recover the packet container */
free_htc_packet_container(target, pPacket);
}
void htc_control_rx_complete(void *Context, HTC_PACKET *pPacket)
{
/* TODO, can't really receive HTC control messages yet.... */
AR_DEBUG_PRINTF(ATH_DEBUG_ERR,
("Invalid call to htc_control_rx_complete\n"));
}
void htc_unblock_recv(HTC_HANDLE HTCHandle)
{
/* TODO find the Need in new model */
}
void htc_enable_recv(HTC_HANDLE HTCHandle)
{
/* TODO find the Need in new model */
}
void htc_disable_recv(HTC_HANDLE HTCHandle)
{
/* TODO find the Need in new model */
}
int htc_get_num_recv_buffers(HTC_HANDLE HTCHandle, HTC_ENDPOINT_ID Endpoint)
{
HTC_TARGET *target = GET_HTC_TARGET_FROM_HANDLE(HTCHandle);
HTC_ENDPOINT *pEndpoint = &target->endpoint[Endpoint];
return HTC_PACKET_QUEUE_DEPTH(&pEndpoint->RxBufferHoldQueue);
}
HTC_PACKET *allocate_htc_packet_container(HTC_TARGET *target)
{
HTC_PACKET *pPacket;
LOCK_HTC_RX(target);
if (NULL == target->pHTCPacketStructPool) {
UNLOCK_HTC_RX(target);
return NULL;
}
pPacket = target->pHTCPacketStructPool;
target->pHTCPacketStructPool = (HTC_PACKET *) pPacket->ListLink.pNext;
UNLOCK_HTC_RX(target);
pPacket->ListLink.pNext = NULL;
return pPacket;
}
void free_htc_packet_container(HTC_TARGET *target, HTC_PACKET *pPacket)
{
pPacket->ListLink.pPrev = NULL;
LOCK_HTC_RX(target);
if (NULL == target->pHTCPacketStructPool) {
target->pHTCPacketStructPool = pPacket;
pPacket->ListLink.pNext = NULL;
} else {
pPacket->ListLink.pNext =
(DL_LIST *) target->pHTCPacketStructPool;
target->pHTCPacketStructPool = pPacket;
}
UNLOCK_HTC_RX(target);
}
#ifdef RX_SG_SUPPORT
qdf_nbuf_t rx_sg_to_single_netbuf(HTC_TARGET *target)
{
qdf_nbuf_t skb;
uint8_t *anbdata;
uint8_t *anbdata_new;
uint32_t anblen;
qdf_nbuf_t new_skb = NULL;
uint32_t sg_queue_len;
qdf_nbuf_queue_t *rx_sg_queue = &target->RxSgQueue;
sg_queue_len = qdf_nbuf_queue_len(rx_sg_queue);
if (sg_queue_len <= 1) {
AR_DEBUG_PRINTF(ATH_DEBUG_ERR,
("rx_sg_to_single_netbuf: invalid sg queue len %u\n"));
goto _failed;
}
new_skb = qdf_nbuf_alloc(target->ExpRxSgTotalLen, 0, 4, false);
if (new_skb == NULL) {
AR_DEBUG_PRINTF(ATH_DEBUG_ERR,
("rx_sg_to_single_netbuf: can't allocate %u size netbuf\n",
target->ExpRxSgTotalLen));
goto _failed;
}
qdf_nbuf_peek_header(new_skb, &anbdata_new, &anblen);
skb = qdf_nbuf_queue_remove(rx_sg_queue);
do {
qdf_nbuf_peek_header(skb, &anbdata, &anblen);
qdf_mem_copy(anbdata_new, anbdata, qdf_nbuf_len(skb));
qdf_nbuf_put_tail(new_skb, qdf_nbuf_len(skb));
anbdata_new += qdf_nbuf_len(skb);
qdf_nbuf_free(skb);
skb = qdf_nbuf_queue_remove(rx_sg_queue);
} while (skb != NULL);
RESET_RX_SG_CONFIG(target);
return new_skb;
_failed:
while ((skb = qdf_nbuf_queue_remove(rx_sg_queue)) != NULL)
qdf_nbuf_free(skb);
RESET_RX_SG_CONFIG(target);
return NULL;
}
#endif
#ifdef CONFIG_WIN
#define HTC_MSG_NACK_SUSPEND 7
#endif
QDF_STATUS htc_rx_completion_handler(void *Context, qdf_nbuf_t netbuf,
uint8_t pipeID)
{
QDF_STATUS status = QDF_STATUS_SUCCESS;
HTC_FRAME_HDR *HtcHdr;
HTC_TARGET *target = (HTC_TARGET *) Context;
uint8_t *netdata;
uint32_t netlen;
HTC_ENDPOINT *pEndpoint;
HTC_PACKET *pPacket;
uint16_t payloadLen;
uint32_t trailerlen = 0;
uint8_t htc_ep_id;
#ifdef RX_SG_SUPPORT
LOCK_HTC_RX(target);
if (target->IsRxSgInprogress) {
target->CurRxSgTotalLen += qdf_nbuf_len(netbuf);
qdf_nbuf_queue_add(&target->RxSgQueue, netbuf);
if (target->CurRxSgTotalLen == target->ExpRxSgTotalLen) {
netbuf = rx_sg_to_single_netbuf(target);
if (netbuf == NULL) {
UNLOCK_HTC_RX(target);
goto _out;
}
} else {
netbuf = NULL;
UNLOCK_HTC_RX(target);
goto _out;
}
}
UNLOCK_HTC_RX(target);
#endif
netdata = qdf_nbuf_data(netbuf);
netlen = qdf_nbuf_len(netbuf);
HtcHdr = (HTC_FRAME_HDR *) netdata;
do {
htc_ep_id = HTC_GET_FIELD(HtcHdr, HTC_FRAME_HDR, ENDPOINTID);
if (htc_ep_id >= ENDPOINT_MAX) {
AR_DEBUG_PRINTF(ATH_DEBUG_ERR,
("HTC Rx: invalid EndpointID=%d\n",
htc_ep_id));
debug_dump_bytes((uint8_t *) HtcHdr,
sizeof(HTC_FRAME_HDR),
"BAD HTC Header");
status = QDF_STATUS_E_FAILURE;
QDF_BUG(0);
break;
}
pEndpoint = &target->endpoint[htc_ep_id];
/*
* If this endpoint that received a message from the target has
* a to-target HIF pipe whose send completions are polled rather
* than interrupt driven, this is a good point to ask HIF to
* check whether it has any completed sends to handle.
*/
if (pEndpoint->ul_is_polled)
htc_send_complete_check(pEndpoint, 1);
payloadLen = HTC_GET_FIELD(HtcHdr, HTC_FRAME_HDR, PAYLOADLEN);
if (netlen < (payloadLen + HTC_HDR_LENGTH)) {
#ifdef RX_SG_SUPPORT
LOCK_HTC_RX(target);
target->IsRxSgInprogress = true;
qdf_nbuf_queue_init(&target->RxSgQueue);
qdf_nbuf_queue_add(&target->RxSgQueue, netbuf);
target->ExpRxSgTotalLen = (payloadLen + HTC_HDR_LENGTH);
target->CurRxSgTotalLen += netlen;
UNLOCK_HTC_RX(target);
netbuf = NULL;
break;
#else
AR_DEBUG_PRINTF(ATH_DEBUG_ERR,
("HTC Rx: insufficient length, got:%d expected =%zu\n",
netlen, payloadLen + HTC_HDR_LENGTH));
debug_dump_bytes((uint8_t *) HtcHdr,
sizeof(HTC_FRAME_HDR),
"BAD RX packet length");
status = QDF_STATUS_E_FAILURE;
QDF_BUG(0);
break;
#endif
}
#ifdef HTC_EP_STAT_PROFILING
LOCK_HTC_RX(target);
INC_HTC_EP_STAT(pEndpoint, RxReceived, 1);
UNLOCK_HTC_RX(target);
#endif
/* if (IS_TX_CREDIT_FLOW_ENABLED(pEndpoint)) { */
{
uint8_t temp;
A_STATUS temp_status;
/* get flags to check for trailer */
temp = HTC_GET_FIELD(HtcHdr, HTC_FRAME_HDR, FLAGS);
if (temp & HTC_FLAGS_RECV_TRAILER) {
/* extract the trailer length */
temp =
HTC_GET_FIELD(HtcHdr, HTC_FRAME_HDR,
CONTROLBYTES0);
if ((temp < sizeof(HTC_RECORD_HDR))
|| (temp > payloadLen)) {
AR_DEBUG_PRINTF(ATH_DEBUG_ERR,
("htc_rx_completion_handler, invalid header (payloadlength should be :%d, CB[0] is:%d)\n",
payloadLen, temp));
status = QDF_STATUS_E_INVAL;
break;
}
trailerlen = temp;
/* process trailer data that follows HDR +
* application payload
*/
temp_status = htc_process_trailer(target,
((uint8_t *) HtcHdr +
HTC_HDR_LENGTH +
payloadLen - temp),
temp, htc_ep_id);
if (A_FAILED(temp_status)) {
status = QDF_STATUS_E_FAILURE;
break;
}
}
}
if (((int)payloadLen - (int)trailerlen) <= 0) {
/* 0 length packet with trailer data, just drop these */
break;
}
if (htc_ep_id == ENDPOINT_0) {
uint16_t message_id;
HTC_UNKNOWN_MSG *htc_msg;
bool wow_nack;
/* remove HTC header */
qdf_nbuf_pull_head(netbuf, HTC_HDR_LENGTH);
netdata = qdf_nbuf_data(netbuf);
netlen = qdf_nbuf_len(netbuf);
htc_msg = (HTC_UNKNOWN_MSG *) netdata;
message_id = HTC_GET_FIELD(htc_msg, HTC_UNKNOWN_MSG,
MESSAGEID);
switch (message_id) {
default:
/* handle HTC control message */
if (target->CtrlResponseProcessing) {
/* this is a fatal error, target should
* not be sending unsolicited messages
* on the endpoint 0
*/
AR_DEBUG_PRINTF(ATH_DEBUG_ERR,
("HTC Rx Ctrl still processing\n"));
status = QDF_STATUS_E_FAILURE;
QDF_BUG(false);
break;
}
LOCK_HTC_RX(target);
target->CtrlResponseLength =
min((int)netlen,
HTC_MAX_CONTROL_MESSAGE_LENGTH);
qdf_mem_copy(target->CtrlResponseBuffer,
netdata,
target->CtrlResponseLength);
/* Requester will clear this flag */
target->CtrlResponseProcessing = true;
UNLOCK_HTC_RX(target);
qdf_event_set(&target->ctrl_response_valid);
break;
#ifdef HTC_MSG_WAKEUP_FROM_SUSPEND_ID
case HTC_MSG_WAKEUP_FROM_SUSPEND_ID:
AR_DEBUG_PRINTF(ATH_DEBUG_ANY,
("Received initial wake up"));
LOCK_HTC_CREDIT(target);
htc_credit_record(HTC_INITIAL_WAKE_UP,
pEndpoint->TxCredits,
HTC_PACKET_QUEUE_DEPTH(
&pEndpoint->TxQueue));
UNLOCK_HTC_CREDIT(target);
if (target->HTCInitInfo.
target_initial_wakeup_cb)
target->HTCInitInfo.
target_initial_wakeup_cb();
else
AR_DEBUG_PRINTF(ATH_DEBUG_ANY,
("No initial wake up cb"));
break;
#endif
case HTC_MSG_SEND_SUSPEND_COMPLETE:
wow_nack = false;
LOCK_HTC_CREDIT(target);
htc_credit_record(HTC_SUSPEND_ACK,
pEndpoint->TxCredits,
HTC_PACKET_QUEUE_DEPTH(
&pEndpoint->TxQueue));
UNLOCK_HTC_CREDIT(target);
target->HTCInitInfo.TargetSendSuspendComplete(
target->HTCInitInfo.pContext,
wow_nack);
break;
case HTC_MSG_NACK_SUSPEND:
wow_nack = true;
LOCK_HTC_CREDIT(target);
htc_credit_record(HTC_SUSPEND_ACK,
pEndpoint->TxCredits,
HTC_PACKET_QUEUE_DEPTH(
&pEndpoint->TxQueue));
UNLOCK_HTC_CREDIT(target);
target->HTCInitInfo.TargetSendSuspendComplete(
target->HTCInitInfo.pContext,
wow_nack);
break;
}
qdf_nbuf_free(netbuf);
netbuf = NULL;
break;
}
/* the current message based HIF architecture allocates net bufs
* for recv packets since this layer bridges that HIF to upper
* layers , which expects HTC packets, we form the packets here
* TODO_FIXME
*/
pPacket = allocate_htc_packet_container(target);
if (NULL == pPacket) {
status = QDF_STATUS_E_RESOURCES;
break;
}
pPacket->Status = QDF_STATUS_SUCCESS;
pPacket->Endpoint = htc_ep_id;
pPacket->pPktContext = netbuf;
pPacket->pBuffer = qdf_nbuf_data(netbuf) + HTC_HDR_LENGTH;
pPacket->ActualLength = netlen - HTC_HEADER_LEN - trailerlen;
qdf_nbuf_pull_head(netbuf, HTC_HEADER_LEN);
qdf_nbuf_set_pktlen(netbuf, pPacket->ActualLength);
recv_packet_completion(target, pEndpoint, pPacket);
netbuf = NULL;
} while (false);
#ifdef RX_SG_SUPPORT
_out:
#endif
if (netbuf != NULL)
qdf_nbuf_free(netbuf);
return status;
}
A_STATUS htc_add_receive_pkt_multiple(HTC_HANDLE HTCHandle,
HTC_PACKET_QUEUE *pPktQueue)
{
HTC_TARGET *target = GET_HTC_TARGET_FROM_HANDLE(HTCHandle);
HTC_ENDPOINT *pEndpoint;
HTC_PACKET *pFirstPacket;
A_STATUS status = A_OK;
HTC_PACKET *pPacket;
pFirstPacket = htc_get_pkt_at_head(pPktQueue);
if (NULL == pFirstPacket) {
A_ASSERT(false);
return A_EINVAL;
}
AR_DEBUG_ASSERT(pFirstPacket->Endpoint < ENDPOINT_MAX);
AR_DEBUG_PRINTF(ATH_DEBUG_RECV,
("+- htc_add_receive_pkt_multiple : endPointId: %d, cnt:%d, length: %d\n",
pFirstPacket->Endpoint,
HTC_PACKET_QUEUE_DEPTH(pPktQueue),
pFirstPacket->BufferLength));
pEndpoint = &target->endpoint[pFirstPacket->Endpoint];
LOCK_HTC_RX(target);
do {
if (HTC_STOPPING(target)) {
status = A_ERROR;
break;
}
/* store receive packets */
HTC_PACKET_QUEUE_TRANSFER_TO_TAIL(&pEndpoint->RxBufferHoldQueue,
pPktQueue);
} while (false);
UNLOCK_HTC_RX(target);
if (A_FAILED(status)) {
/* walk through queue and mark each one canceled */
HTC_PACKET_QUEUE_ITERATE_ALLOW_REMOVE(pPktQueue, pPacket) {
pPacket->Status = QDF_STATUS_E_CANCELED;
}
HTC_PACKET_QUEUE_ITERATE_END;
do_recv_completion(pEndpoint, pPktQueue);
}
return status;
}
A_STATUS htc_add_receive_pkt(HTC_HANDLE HTCHandle, HTC_PACKET *pPacket)
{
HTC_PACKET_QUEUE queue;
INIT_HTC_PACKET_QUEUE_AND_ADD(&queue, pPacket);
return htc_add_receive_pkt_multiple(HTCHandle, &queue);
}
void htc_flush_rx_hold_queue(HTC_TARGET *target, HTC_ENDPOINT *pEndpoint)
{
HTC_PACKET *pPacket;
LOCK_HTC_RX(target);
while (1) {
pPacket = htc_packet_dequeue(&pEndpoint->RxBufferHoldQueue);
if (pPacket == NULL)
break;
UNLOCK_HTC_RX(target);
pPacket->Status = QDF_STATUS_E_CANCELED;
pPacket->ActualLength = 0;
AR_DEBUG_PRINTF(ATH_DEBUG_RECV,
("Flushing RX packet:%pK, length:%d, ep:%d\n",
pPacket, pPacket->BufferLength,
pPacket->Endpoint));
/* give the packet back */
do_recv_completion_pkt(pEndpoint, pPacket);
LOCK_HTC_RX(target);
}
UNLOCK_HTC_RX(target);
}
void htc_recv_init(HTC_TARGET *target)
{
/* Initialize ctrl_response_valid to block */
qdf_event_create(&target->ctrl_response_valid);
}
/* polling routine to wait for a control packet to be received */
QDF_STATUS htc_wait_recv_ctrl_message(HTC_TARGET *target)
{
/* int count = HTC_TARGET_MAX_RESPONSE_POLL; */
AR_DEBUG_PRINTF(ATH_DEBUG_TRC, ("+HTCWaitCtrlMessageRecv\n"));
/* Wait for BMI request/response transaction to complete */
if (qdf_wait_single_event(&target->ctrl_response_valid,
HTC_CONTROL_RX_TIMEOUT)) {
return QDF_STATUS_E_FAILURE;
}
LOCK_HTC_RX(target);
/* caller will clear this flag */
target->CtrlResponseProcessing = true;
UNLOCK_HTC_RX(target);
AR_DEBUG_PRINTF(ATH_DEBUG_TRC, ("-HTCWaitCtrlMessageRecv success\n"));
return QDF_STATUS_SUCCESS;
}
static A_STATUS htc_process_trailer(HTC_TARGET *target,
uint8_t *pBuffer,
int Length, HTC_ENDPOINT_ID FromEndpoint)
{
HTC_RECORD_HDR *pRecord;
uint8_t htc_rec_id;
uint8_t htc_rec_len;
uint8_t *pRecordBuf;
uint8_t *pOrigBuffer;
int origLength;
A_STATUS status;
AR_DEBUG_PRINTF(ATH_DEBUG_RECV,
("+htc_process_trailer (length:%d)\n", Length));
if (AR_DEBUG_LVL_CHECK(ATH_DEBUG_RECV))
AR_DEBUG_PRINTBUF(pBuffer, Length, "Recv Trailer");
pOrigBuffer = pBuffer;
origLength = Length;
status = A_OK;
while (Length > 0) {
if (Length < sizeof(HTC_RECORD_HDR)) {
status = A_EPROTO;
break;
}
/* these are byte aligned structs */
pRecord = (HTC_RECORD_HDR *) pBuffer;
Length -= sizeof(HTC_RECORD_HDR);
pBuffer += sizeof(HTC_RECORD_HDR);
htc_rec_len = HTC_GET_FIELD(pRecord, HTC_RECORD_HDR, LENGTH);
htc_rec_id = HTC_GET_FIELD(pRecord, HTC_RECORD_HDR, RECORDID);
if (htc_rec_len > Length) {
/* no room left in buffer for record */
AR_DEBUG_PRINTF(ATH_DEBUG_ERR,
("invalid record length: %d (id:%d) buffer has: %d bytes left\n",
htc_rec_len, htc_rec_id, Length));
status = A_EPROTO;
break;
}
/* start of record follows the header */
pRecordBuf = pBuffer;
switch (htc_rec_id) {
case HTC_RECORD_CREDITS:
AR_DEBUG_ASSERT(htc_rec_len >=
sizeof(HTC_CREDIT_REPORT));
htc_process_credit_rpt(target,
(HTC_CREDIT_REPORT *) pRecordBuf,
htc_rec_len /
(sizeof(HTC_CREDIT_REPORT)),
FromEndpoint);
break;
#ifdef HIF_SDIO
case HTC_RECORD_LOOKAHEAD:
/* Process in HIF layer */
break;
case HTC_RECORD_LOOKAHEAD_BUNDLE:
/* Process in HIF layer */
break;
#endif /* HIF_SDIO */
default:
AR_DEBUG_PRINTF(ATH_DEBUG_ERR,
("HTC unhandled record: id:%d length:%d\n",
htc_rec_id, htc_rec_len));
break;
}
if (A_FAILED(status)) {
break;
}
/* advance buffer past this record for next time around */
pBuffer += htc_rec_len;
Length -= htc_rec_len;
}
if (A_FAILED(status))
debug_dump_bytes(pOrigBuffer, origLength, "BAD Recv Trailer");
AR_DEBUG_PRINTF(ATH_DEBUG_RECV, ("-htc_process_trailer\n"));
return status;
}