Add ar6000 wireless driver.
[kernel.git] / drivers / ar6000 / htc / htc_send.c
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 #define DO_EP_TX_COMPLETION(ep,p)                                    \
23 {                                                                    \
24     (p)->Completion = NULL;                                          \
25     (ep)->EpCallBacks.EpTxComplete((ep)->EpCallBacks.pContext,(p));  \
26 }
27
28
29 /* call the distribute credits callback with the distribution */
30 #define DO_DISTRIBUTION(t,reason,description,pList) \
31 {                                             \
32     AR_DEBUG_PRINTF(ATH_DEBUG_SEND,           \
33         ("  calling distribute function (%s) (dfn:0x%X, ctxt:0x%X, dist:0x%X) \n", \
34                 (description),                                           \
35                 (A_UINT32)(t)->DistributeCredits,                        \
36                 (A_UINT32)(t)->pCredDistContext,                         \
37                 (A_UINT32)pList));                                       \
38     (t)->DistributeCredits((t)->pCredDistContext,                        \
39                            (pList),                                      \
40                            (reason));                                    \
41 }
42
43 /* our internal send packet completion handler when packets are submited to the AR6K device
44  * layer */
45 static void HTCSendPktCompletionHandler(void *Context, HTC_PACKET *pPacket)
46 {
47     HTC_TARGET      *target = (HTC_TARGET *)Context;
48     HTC_ENDPOINT    *pEndpoint = &target->EndPoint[pPacket->Endpoint];
49
50
51     if (A_FAILED(pPacket->Status)) {
52         AR_DEBUG_PRINTF(ATH_DEBUG_ERR,
53             ("HTCSendPktCompletionHandler: request failed (status:%d, ep:%d) \n",
54                 pPacket->Status, pPacket->Endpoint));
55     }
56         /* first, fixup the head room we allocated */
57     pPacket->pBuffer += HTC_HDR_LENGTH;
58         /* do completion */
59     DO_EP_TX_COMPLETION(pEndpoint,pPacket);
60 }
61
62 A_STATUS HTCIssueSend(HTC_TARGET *target, HTC_PACKET *pPacket, A_UINT8 SendFlags)
63 {
64     A_STATUS status;
65     A_UINT8 *pHdrBuf;
66     A_BOOL   sync = FALSE;
67
68         /* caller always provides headrooom */
69     pPacket->pBuffer -= HTC_HDR_LENGTH;
70     pHdrBuf = pPacket->pBuffer;
71         /* setup frame header */
72     A_SET_UINT16_FIELD(pHdrBuf,HTC_FRAME_HDR,PayloadLen,(A_UINT16)pPacket->ActualLength);
73     A_SET_UINT8_FIELD(pHdrBuf,HTC_FRAME_HDR,Flags,SendFlags);
74     A_SET_UINT8_FIELD(pHdrBuf,HTC_FRAME_HDR,EndpointID, (A_UINT8)pPacket->Endpoint);
75
76     if (pPacket->Completion == NULL) {
77             /* mark that this request was synchronously issued */
78         sync = TRUE;
79     }
80
81     AR_DEBUG_PRINTF(ATH_DEBUG_SEND,
82                     ("+-HTCIssueSend: transmit length : %d (%s) \n",
83                     pPacket->ActualLength + HTC_HDR_LENGTH,
84                     sync ? "SYNC" : "ASYNC" ));
85
86         /* send message to device */
87     status = DevSendPacket(&target->Device,
88                            pPacket,
89                            pPacket->ActualLength + HTC_HDR_LENGTH);
90
91     if (sync) {
92             /* use local sync variable.  If this was issued asynchronously, pPacket is no longer
93              * safe to access. */
94         pPacket->pBuffer += HTC_HDR_LENGTH;
95     }
96
97     /* if this request was asynchronous, the packet completion routine will be invoked by
98      * the device layer when the HIF layer completes the request */
99
100     return status;
101 }
102
103 /* try to send the current packet or a packet at the head of the TX queue,
104  * if there are no credits, the packet remains in the queue. */
105 static void HTCTrySend(HTC_TARGET      *target,
106                        HTC_PACKET      *pPacketToSend,
107                        HTC_ENDPOINT_ID ep)
108 {
109     HTC_PACKET   *pPacket;
110     HTC_ENDPOINT *pEndpoint;
111     int          creditsRequired;
112     A_UINT8      sendFlags;
113
114     AR_DEBUG_PRINTF(ATH_DEBUG_SEND,("+HTCTrySend (pPkt:0x%X)\n",(A_UINT32)pPacketToSend));
115
116     pEndpoint = &target->EndPoint[ep];
117
118     LOCK_HTC_TX(target);
119
120     if (pPacketToSend != NULL) {
121         /* caller supplied us a packet to queue to the tail of the HTC TX queue before
122          * we check the tx queue */
123         HTC_PACKET_ENQUEUE(&pEndpoint->TxQueue,pPacketToSend);
124         pEndpoint->CurrentTxQueueDepth++;
125     }
126
127         /* now drain the TX queue for transmission as long as we have enough
128          * credits */
129
130     while (1) {
131
132         if (HTC_QUEUE_EMPTY(&pEndpoint->TxQueue)) {
133                 /* nothing in the queue */
134             break;
135         }
136
137         sendFlags = 0;
138
139             /* get packet at head, but don't remove it */
140         pPacket = HTC_GET_PKT_AT_HEAD(&pEndpoint->TxQueue);
141         AR_DEBUG_PRINTF(ATH_DEBUG_SEND,(" Got head packet:0x%X , Queue Depth: %d\n",
142                 (A_UINT32)pPacket, pEndpoint->CurrentTxQueueDepth));
143
144             /* figure out how many credits this message requires */
145         creditsRequired  = pPacket->ActualLength + HTC_HDR_LENGTH;
146         creditsRequired += target->TargetCreditSize - 1;
147         creditsRequired /= target->TargetCreditSize;
148
149         AR_DEBUG_PRINTF(ATH_DEBUG_SEND,(" Creds Required:%d   Got:%d\n",
150                             creditsRequired, pEndpoint->CreditDist.TxCredits));
151
152         if (pEndpoint->CreditDist.TxCredits < creditsRequired) {
153
154             /* not enough credits */
155
156             if (pPacket->Endpoint == ENDPOINT_0) {
157                     /* leave it in the queue */
158                 break;
159             }
160                 /* invoke the registered distribution function only if this is not
161                  * endpoint 0, we let the driver layer provide more credits if it can.
162                  * We pass the credit distribution list starting at the endpoint in question
163                  * */
164
165                 /* set how many credits we need  */
166             pEndpoint->CreditDist.TxCreditsSeek =
167                                     creditsRequired - pEndpoint->CreditDist.TxCredits;
168             DO_DISTRIBUTION(target,
169                             HTC_CREDIT_DIST_SEEK_CREDITS,
170                             "Seek Credits",
171                             &pEndpoint->CreditDist);
172
173             pEndpoint->CreditDist.TxCreditsSeek = 0;
174
175             if (pEndpoint->CreditDist.TxCredits < creditsRequired) {
176                     /* still not enough credits to send, leave packet in the queue */
177                 AR_DEBUG_PRINTF(ATH_DEBUG_SEND,
178                     (" Not enough credits for ep %d leaving packet in queue..\n",
179                     pPacket->Endpoint));
180                 break;
181             }
182
183         }
184
185         pEndpoint->CreditDist.TxCredits -= creditsRequired;
186         INC_HTC_EP_STAT(pEndpoint, TxCreditsConsummed, creditsRequired);
187
188             /* check if we need credits */
189         if (pEndpoint->CreditDist.TxCredits < pEndpoint->CreditDist.TxCreditsPerMaxMsg) {
190             sendFlags |= HTC_FLAGS_NEED_CREDIT_UPDATE;
191             INC_HTC_EP_STAT(pEndpoint, TxCreditLowIndications, 1);
192             AR_DEBUG_PRINTF(ATH_DEBUG_SEND,(" Host Needs Credits  \n"));
193         }
194
195             /* now we can fully dequeue */
196         pPacket = HTC_PACKET_DEQUEUE(&pEndpoint->TxQueue);
197         pEndpoint->CurrentTxQueueDepth--;
198
199         INC_HTC_EP_STAT(pEndpoint, TxIssued, 1);
200
201         UNLOCK_HTC_TX(target);
202
203         HTCIssueSend(target, pPacket, sendFlags);
204
205         LOCK_HTC_TX(target);
206
207         /* go back and check for more messages */
208     }
209
210     if (pEndpoint->CurrentTxQueueDepth >= pEndpoint->MaxTxQueueDepth) {
211         AR_DEBUG_PRINTF(ATH_DEBUG_SEND, (" Endpoint %d, TX queue is full, Depth:%d, Max:%d \n",
212                         ep, pEndpoint->CurrentTxQueueDepth, pEndpoint->MaxTxQueueDepth));
213         UNLOCK_HTC_TX(target);
214             /* queue is now full, let caller know */
215         if (pEndpoint->EpCallBacks.EpSendFull != NULL) {
216             AR_DEBUG_PRINTF(ATH_DEBUG_SEND, (" Calling driver's send full callback.... \n"));
217             pEndpoint->EpCallBacks.EpSendFull(pEndpoint->EpCallBacks.pContext, ep);
218         }
219     } else {
220         UNLOCK_HTC_TX(target);
221             /* queue is now available for new packet, let caller know */
222         if (pEndpoint->EpCallBacks.EpSendAvail)
223             pEndpoint->EpCallBacks.EpSendAvail(pEndpoint->EpCallBacks.pContext, ep);
224     }
225
226     AR_DEBUG_PRINTF(ATH_DEBUG_SEND,("-HTCTrySend:  \n"));
227 }
228
229 /* HTC API - HTCSendPkt */
230 A_STATUS HTCSendPkt(HTC_HANDLE HTCHandle, HTC_PACKET *pPacket)
231 {
232     HTC_TARGET      *target = GET_HTC_TARGET_FROM_HANDLE(HTCHandle);
233     HTC_ENDPOINT    *pEndpoint;
234     HTC_ENDPOINT_ID ep;
235     A_STATUS        status = A_OK;
236
237     AR_DEBUG_PRINTF(ATH_DEBUG_SEND,
238                     ("+HTCSendPkt: Enter endPointId: %d, buffer: 0x%X, length: %d \n",
239                     pPacket->Endpoint, (A_UINT32)pPacket->pBuffer, pPacket->ActualLength));
240
241     ep = pPacket->Endpoint;
242     AR_DEBUG_ASSERT(ep < ENDPOINT_MAX);
243     pEndpoint = &target->EndPoint[ep];
244
245     do {
246
247         if (HTC_STOPPING(target)) {
248             status = A_ECANCELED;
249             pPacket->Status = status;
250             DO_EP_TX_COMPLETION(pEndpoint,pPacket);
251             break;
252         }
253             /* everything sent through this interface is asynchronous */
254             /* fill in HTC completion routines */
255         pPacket->Completion = HTCSendPktCompletionHandler;
256         pPacket->pContext = target;
257
258         HTCTrySend(target, pPacket, ep);
259
260     } while (FALSE);
261
262     AR_DEBUG_PRINTF(ATH_DEBUG_SEND, ("-HTCSendPkt \n"));
263
264     return status;
265 }
266
267
268 /* check TX queues to drain because of credit distribution update */
269 static INLINE void HTCCheckEndpointTxQueues(HTC_TARGET *target)
270 {
271     HTC_ENDPOINT                *pEndpoint;
272     HTC_ENDPOINT_CREDIT_DIST    *pDistItem;
273
274     AR_DEBUG_PRINTF(ATH_DEBUG_SEND, ("+HTCCheckEndpointTxQueues \n"));
275     pDistItem = target->EpCreditDistributionListHead;
276
277         /* run through the credit distribution list to see
278          * if there are packets queued
279          * NOTE: no locks need to be taken since the distribution list
280          * is not dynamic (cannot be re-ordered) and we are not modifying any state */
281     while (pDistItem != NULL) {
282         pEndpoint = (HTC_ENDPOINT *)pDistItem->pHTCReserved;
283
284         if (pEndpoint->CurrentTxQueueDepth > 0) {
285             AR_DEBUG_PRINTF(ATH_DEBUG_SEND, (" Ep %d has %d credits and %d Packets in TX Queue \n",
286                     pDistItem->Endpoint, pEndpoint->CreditDist.TxCredits, pEndpoint->CurrentTxQueueDepth));
287                 /* try to start the stalled queue, this list is ordered by priority.
288                  * Highest priority queue get's processed first, if there are credits available the
289                  * highest priority queue will get a chance to reclaim credits from lower priority
290                  * ones */
291             HTCTrySend(target, NULL, pDistItem->Endpoint);
292         }
293
294         pDistItem = pDistItem->pNext;
295     }
296
297     AR_DEBUG_PRINTF(ATH_DEBUG_SEND, ("-HTCCheckEndpointTxQueues \n"));
298 }
299
300 /* process credit reports and call distribution function */
301 void HTCProcessCreditRpt(HTC_TARGET *target, HTC_CREDIT_REPORT *pRpt, int NumEntries, HTC_ENDPOINT_ID FromEndpoint)
302 {
303     int             i;
304     HTC_ENDPOINT    *pEndpoint;
305     int             totalCredits = 0;
306     A_BOOL          doDist = FALSE;
307
308     AR_DEBUG_PRINTF(ATH_DEBUG_SEND, ("+HTCProcessCreditRpt, Credit Report Entries:%d \n", NumEntries));
309
310         /* lock out TX while we update credits */
311     LOCK_HTC_TX(target);
312
313     for (i = 0; i < NumEntries; i++, pRpt++) {
314         if (pRpt->EndpointID >= ENDPOINT_MAX) {
315             AR_DEBUG_ASSERT(FALSE);
316             break;
317         }
318
319         pEndpoint = &target->EndPoint[pRpt->EndpointID];
320
321         AR_DEBUG_PRINTF(ATH_DEBUG_SEND, ("  Endpoint %d got %d credits \n",
322                 pRpt->EndpointID, pRpt->Credits));
323
324
325 #ifdef HTC_EP_STAT_PROFILING
326
327         INC_HTC_EP_STAT(pEndpoint, TxCreditRpts, 1);
328         INC_HTC_EP_STAT(pEndpoint, TxCreditsReturned, pRpt->Credits);
329
330         if (FromEndpoint == pRpt->EndpointID) {
331                 /* this credit report arrived on the same endpoint indicating it arrived in an RX
332                  * packet */
333             INC_HTC_EP_STAT(pEndpoint, TxCreditsFromRx, pRpt->Credits);
334             INC_HTC_EP_STAT(pEndpoint, TxCreditRptsFromRx, 1);
335         } else if (FromEndpoint == ENDPOINT_0) {
336                 /* this credit arrived on endpoint 0 as a NULL message */
337             INC_HTC_EP_STAT(pEndpoint, TxCreditsFromEp0, pRpt->Credits);
338             INC_HTC_EP_STAT(pEndpoint, TxCreditRptsFromEp0, 1);
339         } else {
340                 /* arrived on another endpoint */
341             INC_HTC_EP_STAT(pEndpoint, TxCreditsFromOther, pRpt->Credits);
342             INC_HTC_EP_STAT(pEndpoint, TxCreditRptsFromOther, 1);
343         }
344
345 #endif
346
347         if (ENDPOINT_0 == pRpt->EndpointID) {
348                 /* always give endpoint 0 credits back */
349             pEndpoint->CreditDist.TxCredits += pRpt->Credits;
350         } else {
351                 /* for all other endpoints, update credits to distribute, the distribution function
352                  * will handle giving out credits back to the endpoints */
353             pEndpoint->CreditDist.TxCreditsToDist += pRpt->Credits;
354                 /* flag that we have to do the distribution */
355             doDist = TRUE;
356         }
357
358         totalCredits += pRpt->Credits;
359     }
360
361     AR_DEBUG_PRINTF(ATH_DEBUG_SEND, ("  Report indicated %d credits to distribute \n", totalCredits));
362
363     if (doDist) {
364             /* this was a credit return based on a completed send operations
365              * note, this is done with the lock held */
366         DO_DISTRIBUTION(target,
367                         HTC_CREDIT_DIST_SEND_COMPLETE,
368                         "Send Complete",
369                         target->EpCreditDistributionListHead->pNext);
370     }
371
372     UNLOCK_HTC_TX(target);
373
374     if (totalCredits) {
375         HTCCheckEndpointTxQueues(target);
376     }
377
378     AR_DEBUG_PRINTF(ATH_DEBUG_SEND, ("-HTCProcessCreditRpt \n"));
379 }
380
381 /* flush endpoint TX queue */
382 static void HTCFlushEndpointTX(HTC_TARGET *target, HTC_ENDPOINT *pEndpoint, HTC_TX_TAG Tag)
383 {
384     HTC_PACKET          *pPacket;
385     HTC_PACKET_QUEUE    discardQueue;
386
387         /* initialize the discard queue */
388     INIT_HTC_PACKET_QUEUE(&discardQueue);
389
390     LOCK_HTC_TX(target);
391
392         /* interate from the front of the TX queue and flush out packets */
393     ITERATE_OVER_LIST_ALLOW_REMOVE(&pEndpoint->TxQueue, pPacket, HTC_PACKET, ListLink) {
394
395             /* check for removal */
396         if ((HTC_TX_PACKET_TAG_ALL == Tag) || (Tag == pPacket->PktInfo.AsTx.Tag)) {
397                 /* remove from queue */
398             HTC_PACKET_REMOVE(pPacket);
399                 /* add it to the discard pile */
400             HTC_PACKET_ENQUEUE(&discardQueue, pPacket);
401             pEndpoint->CurrentTxQueueDepth--;
402         }
403
404     } ITERATE_END;
405
406     UNLOCK_HTC_TX(target);
407
408         /* empty the discard queue */
409     while (1) {
410         pPacket = HTC_PACKET_DEQUEUE(&discardQueue);
411         if (NULL == pPacket) {
412             break;
413         }
414         pPacket->Status = A_ECANCELED;
415         AR_DEBUG_PRINTF(ATH_DEBUG_TRC, ("  Flushing TX packet:0x%X, length:%d, ep:%d tag:0x%X \n",
416                 (A_UINT32)pPacket, pPacket->ActualLength, pPacket->Endpoint, pPacket->PktInfo.AsTx.Tag));
417         DO_EP_TX_COMPLETION(pEndpoint,pPacket);
418     }
419
420 }
421
422 void DumpCreditDist(HTC_ENDPOINT_CREDIT_DIST *pEPDist)
423 {
424 #ifdef DEBUG
425     HTC_ENDPOINT *pEndpoint = (HTC_ENDPOINT *)pEPDist->pHTCReserved;
426 #endif
427
428     AR_DEBUG_PRINTF(ATH_DEBUG_ANY, ("--- EP : %d  ServiceID: 0x%X    --------------\n",
429                         pEPDist->Endpoint, pEPDist->ServiceID));
430     AR_DEBUG_PRINTF(ATH_DEBUG_ANY, (" this:0x%X next:0x%X prev:0x%X\n",
431                 (A_UINT32)pEPDist, (A_UINT32)pEPDist->pNext, (A_UINT32)pEPDist->pPrev));
432     AR_DEBUG_PRINTF(ATH_DEBUG_ANY, (" DistFlags          : 0x%X \n", pEPDist->DistFlags));
433     AR_DEBUG_PRINTF(ATH_DEBUG_ANY, (" TxCreditsNorm      : %d \n", pEPDist->TxCreditsNorm));
434     AR_DEBUG_PRINTF(ATH_DEBUG_ANY, (" TxCreditsMin       : %d \n", pEPDist->TxCreditsMin));
435     AR_DEBUG_PRINTF(ATH_DEBUG_ANY, (" TxCredits          : %d \n", pEPDist->TxCredits));
436     AR_DEBUG_PRINTF(ATH_DEBUG_ANY, (" TxCreditsAssigned  : %d \n", pEPDist->TxCreditsAssigned));
437     AR_DEBUG_PRINTF(ATH_DEBUG_ANY, (" TxCreditsSeek      : %d \n", pEPDist->TxCreditsSeek));
438     AR_DEBUG_PRINTF(ATH_DEBUG_ANY, (" TxCreditSize       : %d \n", pEPDist->TxCreditSize));
439     AR_DEBUG_PRINTF(ATH_DEBUG_ANY, (" TxCreditsPerMaxMsg : %d \n", pEPDist->TxCreditsPerMaxMsg));
440     AR_DEBUG_PRINTF(ATH_DEBUG_ANY, (" TxCreditsToDist    : %d \n", pEPDist->TxCreditsToDist));
441     AR_DEBUG_PRINTF(ATH_DEBUG_ANY, (" TxQueueDepth       : %d \n", pEndpoint->CurrentTxQueueDepth));
442     AR_DEBUG_PRINTF(ATH_DEBUG_ANY, ("----------------------------------------------------\n"));
443 }
444
445 void DumpCreditDistStates(HTC_TARGET *target)
446 {
447     HTC_ENDPOINT_CREDIT_DIST *pEPList = target->EpCreditDistributionListHead;
448
449     while (pEPList != NULL) {
450         DumpCreditDist(pEPList);
451         pEPList = pEPList->pNext;
452     }
453
454     if (target->DistributeCredits != NULL) {
455         DO_DISTRIBUTION(target,
456                         HTC_DUMP_CREDIT_STATE,
457                         "Dump State",
458                         NULL);
459     }
460 }
461
462 /* flush all send packets from all endpoint queues */
463 void HTCFlushSendPkts(HTC_TARGET *target)
464 {
465     HTC_ENDPOINT    *pEndpoint;
466     int             i;
467
468     DumpCreditDistStates(target);
469
470     for (i = ENDPOINT_0; i < ENDPOINT_MAX; i++) {
471         pEndpoint = &target->EndPoint[i];
472         if (pEndpoint->ServiceID == 0) {
473                 /* not in use.. */
474             continue;
475         }
476         HTCFlushEndpointTX(target,pEndpoint,HTC_TX_PACKET_TAG_ALL);
477     }
478
479 }
480
481 /* HTC API to flush an endpoint's TX queue*/
482 void HTCFlushEndpoint(HTC_HANDLE HTCHandle, HTC_ENDPOINT_ID Endpoint, HTC_TX_TAG Tag)
483 {
484     HTC_TARGET      *target = GET_HTC_TARGET_FROM_HANDLE(HTCHandle);
485     HTC_ENDPOINT    *pEndpoint = &target->EndPoint[Endpoint];
486
487     if (pEndpoint->ServiceID == 0) {
488         AR_DEBUG_ASSERT(FALSE);
489         /* not in use.. */
490         return;
491     }
492
493     HTCFlushEndpointTX(target, pEndpoint, Tag);
494 }
495
496 /* HTC API to indicate activity to the credit distribution function */
497 void HTCIndicateActivityChange(HTC_HANDLE      HTCHandle,
498                                HTC_ENDPOINT_ID Endpoint,
499                                A_BOOL          Active)
500 {
501     HTC_TARGET      *target = GET_HTC_TARGET_FROM_HANDLE(HTCHandle);
502     HTC_ENDPOINT    *pEndpoint = &target->EndPoint[Endpoint];
503     A_BOOL          doDist = FALSE;
504
505     if (pEndpoint->ServiceID == 0) {
506         AR_DEBUG_ASSERT(FALSE);
507         /* not in use.. */
508         return;
509     }
510
511     LOCK_HTC_TX(target);
512
513     if (Active) {
514         if (!(pEndpoint->CreditDist.DistFlags & HTC_EP_ACTIVE)) {
515                 /* mark active now */
516             pEndpoint->CreditDist.DistFlags |= HTC_EP_ACTIVE;
517             doDist = TRUE;
518         }
519     } else {
520         if (pEndpoint->CreditDist.DistFlags & HTC_EP_ACTIVE) {
521                 /* mark inactive now */
522             pEndpoint->CreditDist.DistFlags &= ~HTC_EP_ACTIVE;
523             doDist = TRUE;
524         }
525     }
526
527     if (doDist) {
528         /* do distribution again based on activity change
529          * note, this is done with the lock held */
530         DO_DISTRIBUTION(target,
531                         HTC_CREDIT_DIST_ACTIVITY_CHANGE,
532                         "Activity Change",
533                         target->EpCreditDistributionListHead->pNext);
534     }
535
536     UNLOCK_HTC_TX(target);
537
538 }