| 1 | /* |
| 2 | * |
| 3 | * Copyright (c) 2007 Atheros Communications Inc. |
| 4 | * All rights reserved. |
| 5 | * |
| 6 | * |
| 7 | * This program is free software; you can redistribute it and/or modify |
| 8 | * it under the terms of the GNU General Public License version 2 as |
| 9 | * published by the Free Software Foundation; |
| 10 | * |
| 11 | * Software distributed under the License is distributed on an "AS |
| 12 | * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or |
| 13 | * implied. See the License for the specific language governing |
| 14 | * rights and limitations under the License. |
| 15 | * |
| 16 | * |
| 17 | * |
| 18 | */ |
| 19 | |
| 20 | #include "htc_internal.h" |
| 21 | |
| 22 | |
| 23 | static HTC_INIT_INFO HTCInitInfo = {NULL,NULL,NULL}; |
| 24 | static A_BOOL HTCInitialized = FALSE; |
| 25 | |
| 26 | static A_STATUS HTCTargetInsertedHandler(void *hif_handle); |
| 27 | static A_STATUS HTCTargetRemovedHandler(void *handle, A_STATUS status); |
| 28 | static void HTCReportFailure(void *Context); |
| 29 | |
| 30 | /* Initializes the HTC layer */ |
| 31 | A_STATUS HTCInit(HTC_INIT_INFO *pInitInfo) |
| 32 | { |
| 33 | HTC_CALLBACKS htcCallbacks; |
| 34 | |
| 35 | AR_DEBUG_PRINTF(ATH_DEBUG_TRC, ("HTCInit: Enter\n")); |
| 36 | if (HTCInitialized) { |
| 37 | AR_DEBUG_PRINTF(ATH_DEBUG_TRC, ("HTCInit: Exit\n")); |
| 38 | return A_OK; |
| 39 | } |
| 40 | |
| 41 | A_MEMCPY(&HTCInitInfo,pInitInfo,sizeof(HTC_INIT_INFO)); |
| 42 | |
| 43 | A_MEMZERO(&htcCallbacks, sizeof(HTC_CALLBACKS)); |
| 44 | |
| 45 | /* setup HIF layer callbacks */ |
| 46 | htcCallbacks.deviceInsertedHandler = HTCTargetInsertedHandler; |
| 47 | htcCallbacks.deviceRemovedHandler = HTCTargetRemovedHandler; |
| 48 | /* the device layer handles these */ |
| 49 | htcCallbacks.rwCompletionHandler = DevRWCompletionHandler; |
| 50 | htcCallbacks.dsrHandler = DevDsrHandler; |
| 51 | HIFInit(&htcCallbacks); |
| 52 | HTCInitialized = TRUE; |
| 53 | |
| 54 | AR_DEBUG_PRINTF(ATH_DEBUG_TRC, ("HTCInit: Exit\n")); |
| 55 | return A_OK; |
| 56 | } |
| 57 | |
| 58 | void HTCFreeControlBuffer(HTC_TARGET *target, HTC_PACKET *pPacket, HTC_PACKET_QUEUE *pList) |
| 59 | { |
| 60 | LOCK_HTC(target); |
| 61 | HTC_PACKET_ENQUEUE(pList,pPacket); |
| 62 | UNLOCK_HTC(target); |
| 63 | } |
| 64 | |
| 65 | HTC_PACKET *HTCAllocControlBuffer(HTC_TARGET *target, HTC_PACKET_QUEUE *pList) |
| 66 | { |
| 67 | HTC_PACKET *pPacket; |
| 68 | |
| 69 | LOCK_HTC(target); |
| 70 | pPacket = HTC_PACKET_DEQUEUE(pList); |
| 71 | UNLOCK_HTC(target); |
| 72 | |
| 73 | return pPacket; |
| 74 | } |
| 75 | |
| 76 | /* cleanup the HTC instance */ |
| 77 | static void HTCCleanup(HTC_TARGET *target) |
| 78 | { |
| 79 | if (A_IS_MUTEX_VALID(&target->HTCLock)) { |
| 80 | A_MUTEX_DELETE(&target->HTCLock); |
| 81 | } |
| 82 | |
| 83 | if (A_IS_MUTEX_VALID(&target->HTCRxLock)) { |
| 84 | A_MUTEX_DELETE(&target->HTCRxLock); |
| 85 | } |
| 86 | |
| 87 | if (A_IS_MUTEX_VALID(&target->HTCTxLock)) { |
| 88 | A_MUTEX_DELETE(&target->HTCTxLock); |
| 89 | } |
| 90 | /* free our instance */ |
| 91 | A_FREE(target); |
| 92 | } |
| 93 | |
| 94 | /* registered target arrival callback from the HIF layer */ |
| 95 | static A_STATUS HTCTargetInsertedHandler(void *hif_handle) |
| 96 | { |
| 97 | HTC_TARGET *target = NULL; |
| 98 | A_STATUS status; |
| 99 | int i; |
| 100 | |
| 101 | AR_DEBUG_PRINTF(ATH_DEBUG_TRC, ("htcTargetInserted - Enter\n")); |
| 102 | |
| 103 | do { |
| 104 | |
| 105 | /* allocate target memory */ |
| 106 | if ((target = (HTC_TARGET *)A_MALLOC(sizeof(HTC_TARGET))) == NULL) { |
| 107 | AR_DEBUG_PRINTF(ATH_DEBUG_ERR, ("Unable to allocate memory\n")); |
| 108 | status = A_ERROR; |
| 109 | break; |
| 110 | } |
| 111 | |
| 112 | A_MEMZERO(target, sizeof(HTC_TARGET)); |
| 113 | A_MUTEX_INIT(&target->HTCLock); |
| 114 | A_MUTEX_INIT(&target->HTCRxLock); |
| 115 | A_MUTEX_INIT(&target->HTCTxLock); |
| 116 | INIT_HTC_PACKET_QUEUE(&target->ControlBufferTXFreeList); |
| 117 | INIT_HTC_PACKET_QUEUE(&target->ControlBufferRXFreeList); |
| 118 | |
| 119 | /* give device layer the hif device handle */ |
| 120 | target->Device.HIFDevice = hif_handle; |
| 121 | /* give the device layer our context (for event processing) |
| 122 | * the device layer will register it's own context with HIF |
| 123 | * so we need to set this so we can fetch it in the target remove handler */ |
| 124 | target->Device.HTCContext = target; |
| 125 | /* set device layer target failure callback */ |
| 126 | target->Device.TargetFailureCallback = HTCReportFailure; |
| 127 | /* set device layer recv message pending callback */ |
| 128 | target->Device.MessagePendingCallback = HTCRecvMessagePendingHandler; |
| 129 | target->EpWaitingForBuffers = ENDPOINT_MAX; |
| 130 | |
| 131 | /* setup device layer */ |
| 132 | status = DevSetup(&target->Device); |
| 133 | |
| 134 | if (A_FAILED(status)) { |
| 135 | break; |
| 136 | } |
| 137 | |
| 138 | /* carve up buffers/packets for control messages */ |
| 139 | for (i = 0; i < NUM_CONTROL_RX_BUFFERS; i++) { |
| 140 | HTC_PACKET *pControlPacket; |
| 141 | pControlPacket = &target->HTCControlBuffers[i].HtcPacket; |
| 142 | SET_HTC_PACKET_INFO_RX_REFILL(pControlPacket, |
| 143 | target, |
| 144 | target->HTCControlBuffers[i].Buffer, |
| 145 | HTC_CONTROL_BUFFER_SIZE, |
| 146 | ENDPOINT_0); |
| 147 | HTC_FREE_CONTROL_RX(target,pControlPacket); |
| 148 | } |
| 149 | |
| 150 | for (;i < NUM_CONTROL_BUFFERS;i++) { |
| 151 | HTC_PACKET *pControlPacket; |
| 152 | pControlPacket = &target->HTCControlBuffers[i].HtcPacket; |
| 153 | INIT_HTC_PACKET_INFO(pControlPacket, |
| 154 | target->HTCControlBuffers[i].Buffer, |
| 155 | HTC_CONTROL_BUFFER_SIZE); |
| 156 | HTC_FREE_CONTROL_TX(target,pControlPacket); |
| 157 | } |
| 158 | |
| 159 | } while (FALSE); |
| 160 | |
| 161 | if (A_SUCCESS(status)) { |
| 162 | AR_DEBUG_PRINTF(ATH_DEBUG_TRC, (" calling AddInstance callback \n")); |
| 163 | /* announce ourselves */ |
| 164 | HTCInitInfo.AddInstance((HTC_HANDLE)target); |
| 165 | } else { |
| 166 | if (target != NULL) { |
| 167 | HTCCleanup(target); |
| 168 | } |
| 169 | } |
| 170 | |
| 171 | AR_DEBUG_PRINTF(ATH_DEBUG_TRC, ("htcTargetInserted - Exit\n")); |
| 172 | |
| 173 | return status; |
| 174 | } |
| 175 | |
| 176 | /* registered removal callback from the HIF layer */ |
| 177 | static A_STATUS HTCTargetRemovedHandler(void *handle, A_STATUS status) |
| 178 | { |
| 179 | HTC_TARGET *target; |
| 180 | |
| 181 | AR_DEBUG_PRINTF(ATH_DEBUG_TRC, ("+HTCTargetRemovedHandler handle:0x%X \n",(A_UINT32)handle)); |
| 182 | |
| 183 | if (NULL == handle) { |
| 184 | /* this could be NULL in the event that target initialization failed */ |
| 185 | return A_OK; |
| 186 | } |
| 187 | |
| 188 | target = ((AR6K_DEVICE *)handle)->HTCContext; |
| 189 | |
| 190 | AR_DEBUG_PRINTF(ATH_DEBUG_TRC, (" removing target:0x%X instance:0x%X ... \n", |
| 191 | (A_UINT32)target, (A_UINT32)target->pInstanceContext)); |
| 192 | |
| 193 | if (target->pInstanceContext != NULL) { |
| 194 | /* let upper layer know, it needs to call HTCStop() */ |
| 195 | HTCInitInfo.DeleteInstance(target->pInstanceContext); |
| 196 | } |
| 197 | |
| 198 | HIFShutDownDevice(target->Device.HIFDevice); |
| 199 | |
| 200 | HTCCleanup(target); |
| 201 | AR_DEBUG_PRINTF(ATH_DEBUG_TRC, ("-HTCTargetRemovedHandler \n")); |
| 202 | return A_OK; |
| 203 | } |
| 204 | |
| 205 | /* get the low level HIF device for the caller , the caller may wish to do low level |
| 206 | * HIF requests */ |
| 207 | void *HTCGetHifDevice(HTC_HANDLE HTCHandle) |
| 208 | { |
| 209 | HTC_TARGET *target = GET_HTC_TARGET_FROM_HANDLE(HTCHandle); |
| 210 | return target->Device.HIFDevice; |
| 211 | } |
| 212 | |
| 213 | /* set the instance block for this HTC handle, so that on removal, the blob can be |
| 214 | * returned to the caller */ |
| 215 | void HTCSetInstance(HTC_HANDLE HTCHandle, void *Instance) |
| 216 | { |
| 217 | HTC_TARGET *target = GET_HTC_TARGET_FROM_HANDLE(HTCHandle); |
| 218 | |
| 219 | target->pInstanceContext = Instance; |
| 220 | } |
| 221 | |
| 222 | /* wait for the target to arrive (sends HTC Ready message) |
| 223 | * this operation is fully synchronous and the message is polled for */ |
| 224 | A_STATUS HTCWaitTarget(HTC_HANDLE HTCHandle) |
| 225 | { |
| 226 | HTC_TARGET *target = GET_HTC_TARGET_FROM_HANDLE(HTCHandle); |
| 227 | A_STATUS status; |
| 228 | HTC_PACKET *pPacket = NULL; |
| 229 | HTC_READY_MSG *pRdyMsg; |
| 230 | HTC_SERVICE_CONNECT_REQ connect; |
| 231 | HTC_SERVICE_CONNECT_RESP resp; |
| 232 | |
| 233 | AR_DEBUG_PRINTF(ATH_DEBUG_TRC, ("HTCWaitTarget - Enter (target:0x%X) \n", (A_UINT32)target)); |
| 234 | |
| 235 | do { |
| 236 | |
| 237 | #ifdef MBOXHW_UNIT_TEST |
| 238 | |
| 239 | status = DoMboxHWTest(&target->Device); |
| 240 | |
| 241 | if (status != A_OK) { |
| 242 | break; |
| 243 | } |
| 244 | |
| 245 | #endif |
| 246 | |
| 247 | /* we should be getting 1 control message that the target is ready */ |
| 248 | status = HTCWaitforControlMessage(target, &pPacket); |
| 249 | |
| 250 | if (A_FAILED(status)) { |
| 251 | AR_DEBUG_PRINTF(ATH_DEBUG_ERR, (" Target Not Available!!\n")); |
| 252 | break; |
| 253 | } |
| 254 | |
| 255 | /* we controlled the buffer creation so it has to be properly aligned */ |
| 256 | pRdyMsg = (HTC_READY_MSG *)pPacket->pBuffer; |
| 257 | |
| 258 | if ((pRdyMsg->MessageID != HTC_MSG_READY_ID) || |
| 259 | (pPacket->ActualLength < sizeof(HTC_READY_MSG))) { |
| 260 | /* this message is not valid */ |
| 261 | AR_DEBUG_ASSERT(FALSE); |
| 262 | status = A_EPROTO; |
| 263 | break; |
| 264 | } |
| 265 | |
| 266 | if (pRdyMsg->CreditCount == 0 || pRdyMsg->CreditSize == 0) { |
| 267 | /* this message is not valid */ |
| 268 | AR_DEBUG_ASSERT(FALSE); |
| 269 | status = A_EPROTO; |
| 270 | break; |
| 271 | } |
| 272 | |
| 273 | target->TargetCredits = pRdyMsg->CreditCount; |
| 274 | target->TargetCreditSize = pRdyMsg->CreditSize; |
| 275 | |
| 276 | AR_DEBUG_PRINTF(ATH_DEBUG_TRC, (" Target Ready: credits: %d credit size: %d\n", |
| 277 | target->TargetCredits, target->TargetCreditSize)); |
| 278 | |
| 279 | /* setup our pseudo HTC control endpoint connection */ |
| 280 | A_MEMZERO(&connect,sizeof(connect)); |
| 281 | A_MEMZERO(&resp,sizeof(resp)); |
| 282 | connect.EpCallbacks.pContext = target; |
| 283 | connect.EpCallbacks.EpTxComplete = HTCControlTxComplete; |
| 284 | connect.EpCallbacks.EpRecv = HTCControlRecv; |
| 285 | connect.EpCallbacks.EpRecvRefill = NULL; /* not needed */ |
| 286 | connect.EpCallbacks.EpSendFull = NULL; /* not needed */ |
| 287 | connect.EpCallbacks.EpSendAvail = NULL; /* not needed */ |
| 288 | connect.MaxSendQueueDepth = NUM_CONTROL_BUFFERS; |
| 289 | connect.ServiceID = HTC_CTRL_RSVD_SVC; |
| 290 | |
| 291 | /* connect fake service */ |
| 292 | status = HTCConnectService((HTC_HANDLE)target, |
| 293 | &connect, |
| 294 | &resp); |
| 295 | |
| 296 | if (!A_FAILED(status)) { |
| 297 | break; |
| 298 | } |
| 299 | |
| 300 | } while (FALSE); |
| 301 | |
| 302 | if (pPacket != NULL) { |
| 303 | HTC_FREE_CONTROL_RX(target,pPacket); |
| 304 | } |
| 305 | |
| 306 | AR_DEBUG_PRINTF(ATH_DEBUG_TRC, ("HTCWaitTarget - Exit\n")); |
| 307 | |
| 308 | return status; |
| 309 | } |
| 310 | |
| 311 | |
| 312 | |
| 313 | /* Start HTC, enable interrupts and let the target know host has finished setup */ |
| 314 | A_STATUS HTCStart(HTC_HANDLE HTCHandle) |
| 315 | { |
| 316 | HTC_TARGET *target = GET_HTC_TARGET_FROM_HANDLE(HTCHandle); |
| 317 | HTC_PACKET *pPacket; |
| 318 | A_STATUS status; |
| 319 | |
| 320 | AR_DEBUG_PRINTF(ATH_DEBUG_TRC, ("HTCStart Enter\n")); |
| 321 | |
| 322 | /* now that we are starting, push control receive buffers into the |
| 323 | * HTC control endpoint */ |
| 324 | |
| 325 | while (1) { |
| 326 | pPacket = HTC_ALLOC_CONTROL_RX(target); |
| 327 | if (NULL == pPacket) { |
| 328 | break; |
| 329 | } |
| 330 | HTCAddReceivePkt((HTC_HANDLE)target,pPacket); |
| 331 | } |
| 332 | |
| 333 | do { |
| 334 | |
| 335 | AR_DEBUG_ASSERT(target->InitCredits != NULL); |
| 336 | AR_DEBUG_ASSERT(target->EpCreditDistributionListHead != NULL); |
| 337 | AR_DEBUG_ASSERT(target->EpCreditDistributionListHead->pNext != NULL); |
| 338 | |
| 339 | /* call init credits callback to do the distribution , |
| 340 | * NOTE: the first entry in the distribution list is ENDPOINT_0, so |
| 341 | * we pass the start of the list after this one. */ |
| 342 | target->InitCredits(target->pCredDistContext, |
| 343 | target->EpCreditDistributionListHead->pNext, |
| 344 | target->TargetCredits); |
| 345 | |
| 346 | if (AR_DEBUG_LVL_CHECK(ATH_DEBUG_TRC)) { |
| 347 | DumpCreditDistStates(target); |
| 348 | } |
| 349 | |
| 350 | /* the caller is done connecting to services, so we can indicate to the |
| 351 | * target that the setup phase is complete */ |
| 352 | status = HTCSendSetupComplete(target); |
| 353 | |
| 354 | if (A_FAILED(status)) { |
| 355 | break; |
| 356 | } |
| 357 | |
| 358 | /* unmask interrupts */ |
| 359 | status = DevUnmaskInterrupts(&target->Device); |
| 360 | |
| 361 | if (A_FAILED(status)) { |
| 362 | HTCStop(target); |
| 363 | } |
| 364 | |
| 365 | } while (FALSE); |
| 366 | |
| 367 | AR_DEBUG_PRINTF(ATH_DEBUG_TRC, ("HTCStart Exit\n")); |
| 368 | return status; |
| 369 | } |
| 370 | |
| 371 | |
| 372 | /* stop HTC communications, i.e. stop interrupt reception, and flush all queued buffers */ |
| 373 | void HTCStop(HTC_HANDLE HTCHandle) |
| 374 | { |
| 375 | HTC_TARGET *target = GET_HTC_TARGET_FROM_HANDLE(HTCHandle); |
| 376 | AR_DEBUG_PRINTF(ATH_DEBUG_TRC, ("+HTCStop \n")); |
| 377 | |
| 378 | /* mark that we are shutting down .. */ |
| 379 | target->HTCStateFlags |= HTC_STATE_STOPPING; |
| 380 | |
| 381 | /* Masking interrupts is a synchronous operation, when this function returns |
| 382 | * all pending HIF I/O has completed, we can safely flush the queues */ |
| 383 | DevMaskInterrupts(&target->Device); |
| 384 | |
| 385 | /* flush all send packets */ |
| 386 | HTCFlushSendPkts(target); |
| 387 | /* flush all recv buffers */ |
| 388 | HTCFlushRecvBuffers(target); |
| 389 | |
| 390 | AR_DEBUG_PRINTF(ATH_DEBUG_TRC, ("-HTCStop \n")); |
| 391 | } |
| 392 | |
| 393 | /* undo what was done in HTCInit() */ |
| 394 | void HTCShutDown(void) |
| 395 | { |
| 396 | AR_DEBUG_PRINTF(ATH_DEBUG_TRC, ("+HTCShutDown: \n")); |
| 397 | HTCInitialized = FALSE; |
| 398 | /* undo HTCInit */ |
| 399 | HIFShutDownDevice(NULL); |
| 400 | AR_DEBUG_PRINTF(ATH_DEBUG_TRC, ("-HTCShutDown: \n")); |
| 401 | } |
| 402 | |
| 403 | void HTCDumpCreditStates(HTC_HANDLE HTCHandle) |
| 404 | { |
| 405 | HTC_TARGET *target = GET_HTC_TARGET_FROM_HANDLE(HTCHandle); |
| 406 | |
| 407 | LOCK_HTC_TX(target); |
| 408 | |
| 409 | DumpCreditDistStates(target); |
| 410 | |
| 411 | UNLOCK_HTC_TX(target); |
| 412 | } |
| 413 | |
| 414 | /* report a target failure from the device, this is a callback from the device layer |
| 415 | * which uses a mechanism to report errors from the target (i.e. special interrupts) */ |
| 416 | static void HTCReportFailure(void *Context) |
| 417 | { |
| 418 | HTC_TARGET *target = (HTC_TARGET *)Context; |
| 419 | |
| 420 | target->TargetFailure = TRUE; |
| 421 | |
| 422 | if ((target->pInstanceContext != NULL) && (HTCInitInfo.TargetFailure != NULL)) { |
| 423 | /* let upper layer know, it needs to call HTCStop() */ |
| 424 | HTCInitInfo.TargetFailure(target->pInstanceContext, A_ERROR); |
| 425 | } |
| 426 | } |
| 427 | |
| 428 | void DebugDumpBytes(A_UCHAR *buffer, A_UINT16 length, char *pDescription) |
| 429 | { |
| 430 | A_CHAR stream[60]; |
| 431 | A_UINT32 i; |
| 432 | A_UINT16 offset, count; |
| 433 | |
| 434 | AR_DEBUG_PRINTF(ATH_DEBUG_ANY, ("<---------Dumping %d Bytes : %s ------>\n", length, pDescription)); |
| 435 | |
| 436 | count = 0; |
| 437 | offset = 0; |
| 438 | for(i = 0; i < length; i++) { |
| 439 | sprintf(stream + offset, "%2.2X ", buffer[i]); |
| 440 | count ++; |
| 441 | offset += 3; |
| 442 | |
| 443 | if(count == 16) { |
| 444 | count = 0; |
| 445 | offset = 0; |
| 446 | AR_DEBUG_PRINTF(ATH_DEBUG_ANY, ("[H]: %s\n", stream)); |
| 447 | A_MEMZERO(stream, 60); |
| 448 | } |
| 449 | } |
| 450 | |
| 451 | if(offset != 0) { |
| 452 | AR_DEBUG_PRINTF(ATH_DEBUG_ANY, ("[H]: %s\n", stream)); |
| 453 | } |
| 454 | |
| 455 | AR_DEBUG_PRINTF(ATH_DEBUG_ANY, ("<------------------------------------------------->\n")); |
| 456 | } |
| 457 | |
| 458 | A_BOOL HTCGetEndpointStatistics(HTC_HANDLE HTCHandle, |
| 459 | HTC_ENDPOINT_ID Endpoint, |
| 460 | HTC_ENDPOINT_STAT_ACTION Action, |
| 461 | HTC_ENDPOINT_STATS *pStats) |
| 462 | { |
| 463 | |
| 464 | #ifdef HTC_EP_STAT_PROFILING |
| 465 | HTC_TARGET *target = GET_HTC_TARGET_FROM_HANDLE(HTCHandle); |
| 466 | A_BOOL clearStats = FALSE; |
| 467 | A_BOOL sample = FALSE; |
| 468 | |
| 469 | switch (Action) { |
| 470 | case HTC_EP_STAT_SAMPLE : |
| 471 | sample = TRUE; |
| 472 | break; |
| 473 | case HTC_EP_STAT_SAMPLE_AND_CLEAR : |
| 474 | sample = TRUE; |
| 475 | clearStats = TRUE; |
| 476 | break; |
| 477 | case HTC_EP_STAT_CLEAR : |
| 478 | clearStats = TRUE; |
| 479 | break; |
| 480 | default: |
| 481 | break; |
| 482 | } |
| 483 | |
| 484 | A_ASSERT(Endpoint < ENDPOINT_MAX); |
| 485 | |
| 486 | /* lock out TX and RX while we sample and/or clear */ |
| 487 | LOCK_HTC_TX(target); |
| 488 | LOCK_HTC_RX(target); |
| 489 | |
| 490 | if (sample) { |
| 491 | A_ASSERT(pStats != NULL); |
| 492 | /* return the stats to the caller */ |
| 493 | A_MEMCPY(pStats, &target->EndPoint[Endpoint].EndPointStats, sizeof(HTC_ENDPOINT_STATS)); |
| 494 | } |
| 495 | |
| 496 | if (clearStats) { |
| 497 | /* reset stats */ |
| 498 | A_MEMZERO(&target->EndPoint[Endpoint].EndPointStats, sizeof(HTC_ENDPOINT_STATS)); |
| 499 | } |
| 500 | |
| 501 | UNLOCK_HTC_RX(target); |
| 502 | UNLOCK_HTC_TX(target); |
| 503 | |
| 504 | return TRUE; |
| 505 | #else |
| 506 | return FALSE; |
| 507 | #endif |
| 508 | } |
| 509 | |