1 /*
2 * Copyright (c) 2011-2013, Texas Instruments Incorporated
3 * All rights reserved.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions
7 * are met:
8 *
9 * * Redistributions of source code must retain the above copyright
10 * notice, this list of conditions and the following disclaimer.
11 *
12 * * Redistributions in binary form must reproduce the above copyright
13 * notice, this list of conditions and the following disclaimer in the
14 * documentation and/or other materials provided with the distribution.
15 *
16 * * Neither the name of Texas Instruments Incorporated nor the names of
17 * its contributors may be used to endorse or promote products derived
18 * from this software without specific prior written permission.
19 *
20 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
21 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
22 * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
23 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
24 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
25 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
26 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
27 * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
28 * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
29 * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
30 * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
31 */
32 /** ============================================================================
33 * @file RPMessage.c
34 *
35 * @brief A simple copy-based MessageQ, to work with Linux virtio_rp_msg.
36 *
37 * Notes:
38 * - The logic in the functions for sending (_put()) and receiving _swiFxn()
39 * depend on the role (host or slave) the processor is playing in the
40 * asymmetric virtio I/O.
41 * - The host always adds *available* buffers to send/receive, while the slave
42 * always adds *used* buffers to send/receive.
43 * - The logic is summarized below:
44 *
45 * Host:
46 * - Prime vq_host with avail bufs, and kick vq_host so slave can send.
47 * - To send a buffer to the slave processor:
48 * allocate a tx buffer, or get_used_buf(vq_slave);
49 * >> copy data into buf <<
50 * add_avail_buf(vq_slave);
51 * kick(vq_slave);
52 * - To receive buffer from slave processor:
53 * get_used_buf(vq_host);
54 * >> empty data from buf <<
55 * add_avail_buf(vq_host);
56 * kick(vq_host);
57 *
58 * Slave:
59 * - To receive buffer from the host:
60 * get_avail_buf(vq_slave);
61 * >> empty data from buf <<
62 * add_used_buf(vq_slave);
63 * kick(vq_slave);
64 * - To send buffer to the host:
65 * get_avail_buf(vq_host);
66 * >> copy data into buf <<
67 * add_used_buf(vq_host);
68 * kick(vq_host);
69 *
70 * ============================================================================
71 */
73 /* this define must precede inclusion of any xdc header file */
74 #define Registry_CURDESC ti_ipc_rpmsg_RPMessage__Desc
75 #define MODULE_NAME "ti.ipc.rpmsg.RPMessage"
77 #include <xdc/std.h>
78 #include <string.h>
80 #include <xdc/runtime/System.h>
81 #include <xdc/runtime/Assert.h>
82 #include <xdc/runtime/Memory.h>
83 #include <xdc/runtime/Registry.h>
84 #include <xdc/runtime/Log.h>
85 #include <xdc/runtime/Diags.h>
87 #include <ti/sysbios/BIOS.h>
88 #include <ti/sysbios/knl/Swi.h>
89 #include <ti/sysbios/knl/Semaphore.h>
90 #include <ti/sysbios/heaps/HeapBuf.h>
91 #include <ti/sysbios/gates/GateAll.h>
93 #include <ti/sdo/utils/List.h>
94 #include <ti/ipc/MultiProc.h>
96 #include <ti/ipc/rpmsg/RPMessage.h>
98 #include "_VirtQueue.h"
100 /* TBD: VirtQueue.h needs to somehow get factored out of family directory .*/
101 #if defined(OMAPL138)
102 #include <ti/ipc/family/omapl138/VirtQueue.h>
103 #elif defined(TCI6614)
104 #include <ti/ipc/family/tci6614/VirtQueue.h>
105 #elif defined(TCI6638)
106 #include <ti/ipc/family/tci6638/VirtQueue.h>
107 #elif defined(OMAP5)
108 #include <ti/ipc/family/omap54xx/VirtQueue.h>
109 #elif defined(VAYU)
110 #include <ti/ipc/family/vayu/VirtQueue.h>
111 #else
112 #error unknown processor!
113 #endif
115 /* =============================================================================
116 * Structures & Enums
117 * =============================================================================
118 */
120 /* Various arbitrary limits: */
121 #define MAXMESSAGEQOBJECTS 256
122 #define MAXMESSAGEBUFFERS 512
123 #define MSGBUFFERSIZE 512 // Max payload + sizeof(ListElem)
124 #define MAXHEAPSIZE (MAXMESSAGEBUFFERS * MSGBUFFERSIZE)
125 #define HEAPALIGNMENT 8
127 /* The RPMessage Object */
128 typedef struct RPMessage_Object {
129 UInt32 queueId; /* Unique id (procId | queueIndex) */
130 Semaphore_Handle semHandle; /* I/O Completion */
131 RPMessage_callback cb; /* RPMessage Callback */
132 UArg arg; /* Callback argument */
133 List_Handle queue; /* Queue of pending messages */
134 Bool unblocked; /* Use with signal to unblock _receive() */
135 } RPMessage_Object;
137 /* Module_State */
138 typedef struct RPMessage_Module {
139 /* Instance gate: */
140 GateAll_Handle gateH;
141 /* Array of messageQObjects in the system: */
142 struct RPMessage_Object *msgqObjects[MAXMESSAGEQOBJECTS];
143 /* Heap from which to allocate free messages for copying: */
144 HeapBuf_Handle heap;
145 } RPMessage_Module;
147 /* Message Header: Must match mp_msg_hdr in virtio_rp_msg.h on Linux side. */
148 typedef struct RPMessage_MsgHeader {
149 Bits32 srcAddr; /* source endpoint addr */
150 Bits32 dstAddr; /* destination endpoint addr */
151 Bits32 reserved; /* reserved */
152 Bits16 dataLen; /* data length */
153 Bits16 flags; /* bitmask of different flags */
154 UInt8 payload[]; /* Data payload */
155 } RPMessage_MsgHeader;
157 typedef RPMessage_MsgHeader *RPMessage_Msg;
159 /* Element to hold payload copied onto receiver's queue. */
160 typedef struct Queue_elem {
161 List_Elem elem; /* Allow list linking. */
162 UInt len; /* Length of data */
163 UInt32 src; /* Src address/endpt of the msg */
164 Char data[]; /* payload begins here */
165 } Queue_elem;
167 /* Combine transport related objects into a struct for future migration: */
168 typedef struct RPMessage_Transport {
169 Swi_Handle swiHandle;
170 VirtQueue_Handle virtQueue_toHost;
171 VirtQueue_Handle virtQueue_fromHost;
172 Semaphore_Handle semHandle_toHost;
173 } RPMessage_Transport;
176 /* module diags mask */
177 Registry_Desc Registry_CURDESC;
179 static RPMessage_Module module;
180 static RPMessage_Transport transport;
182 /* We create a fixed size heap over this memory for copying received msgs */
183 #pragma DATA_ALIGN (recv_buffers, HEAPALIGNMENT)
184 static UInt8 recv_buffers[MAXHEAPSIZE];
186 /* Module ref count: */
187 static Int curInit = 0;
189 /*
190 * ======== RPMessage_swiFxn ========
191 */
192 #define FXNN "RPMessage_swiFxn"
193 static Void RPMessage_swiFxn(UArg arg0, UArg arg1)
194 {
195 Int16 token;
196 RPMessage_Msg msg;
197 UInt16 dstProc = MultiProc_self();
198 Bool usedBufAdded = FALSE;
199 int len;
201 Log_print0(Diags_ENTRY, "--> "FXNN);
203 /* Process all available buffers: */
204 while ((token = VirtQueue_getAvailBuf(transport.virtQueue_fromHost,
205 (Void **)&msg, &len)) >= 0) {
207 Log_print3(Diags_INFO, FXNN": \n\tReceived msg: from: 0x%x, "
208 "to: 0x%x, dataLen: %d",
209 (IArg)msg->srcAddr, (IArg)msg->dstAddr, (IArg)msg->dataLen);
211 /* Pass to destination queue (on this proc), or callback: */
212 RPMessage_send(dstProc, msg->dstAddr, msg->srcAddr,
213 (Ptr)msg->payload, msg->dataLen);
215 VirtQueue_addUsedBuf(transport.virtQueue_fromHost, token,
216 RPMSG_BUF_SIZE);
217 usedBufAdded = TRUE;
218 }
220 if (usedBufAdded) {
221 /* Tell host we've processed the buffers: */
222 VirtQueue_kick(transport.virtQueue_fromHost);
223 }
224 Log_print0(Diags_EXIT, "<-- "FXNN);
225 }
226 #undef FXNN
229 #define FXNN "callback_availBufReady"
230 static Void callback_availBufReady(VirtQueue_Handle vq)
231 {
233 if (vq == transport.virtQueue_fromHost) {
234 /* Post a SWI to process all incoming messages */
235 Log_print0(Diags_INFO, FXNN": virtQueue_fromHost kicked");
236 Swi_post(transport.swiHandle);
237 }
238 else if (vq == transport.virtQueue_toHost) {
239 /* Note: We normally post nothing for transport.virtQueue_toHost,
240 * unless we were starved for buffers, and we turned on notifications.
241 */
242 Semaphore_post(transport.semHandle_toHost);
243 Log_print0(Diags_INFO, FXNN": virtQueue_toHost kicked");
244 }
245 }
246 #undef FXNN
248 /* =============================================================================
249 * RPMessage Functions:
250 * =============================================================================
251 */
253 /*
254 * ======== MessasgeQCopy_init ========
255 *
256 *
257 */
258 #define FXNN "RPMessage_init"
259 Void RPMessage_init(UInt16 remoteProcId)
260 {
261 GateAll_Params gatePrms;
262 HeapBuf_Params prms;
263 int i;
264 Registry_Result result;
265 Bool isHost;
266 VirtQueue_Params vqParams;
268 if (curInit++) {
269 Log_print1(Diags_ENTRY, "--> "FXNN": (remoteProcId=%d)",
270 (IArg)remoteProcId);
271 goto exit; /* module already initialized */
272 }
274 /* register with xdc.runtime to get a diags mask */
275 result = Registry_addModule(&Registry_CURDESC, MODULE_NAME);
276 Assert_isTrue(result == Registry_SUCCESS, (Assert_Id)NULL);
278 /* Log should be after the Registry_CURDESC is initialized */
279 Log_print1(Diags_ENTRY, "--> "FXNN": (remoteProcId=%d)",
280 (IArg)remoteProcId);
282 /* Gate to protect module object and lists: */
283 GateAll_Params_init(&gatePrms);
284 module.gateH = GateAll_create(&gatePrms, NULL);
286 /* Initialize Module State: */
287 for (i = 0; i < MAXMESSAGEQOBJECTS; i++) {
288 module.msgqObjects[i] = NULL;
289 }
291 HeapBuf_Params_init(&prms);
292 prms.blockSize = MSGBUFFERSIZE;
293 prms.numBlocks = MAXMESSAGEBUFFERS;
294 prms.buf = recv_buffers;
295 prms.bufSize = MAXHEAPSIZE;
296 prms.align = HEAPALIGNMENT;
297 module.heap = HeapBuf_create(&prms, NULL);
298 if (module.heap == 0) {
299 System_abort("RPMessage_init: HeapBuf_create returned 0\n");
300 }
301 transport.semHandle_toHost = Semaphore_create(0, NULL, NULL);
303 isHost = (MultiProc_self() == MultiProc_getId("HOST"));
305 /* Initialize Transport related objects: */
307 VirtQueue_Params_init(&vqParams);
308 if (isHost) {
309 /* We don't handle this case currently! Host would need to prime vq. */
310 Assert_isTrue(FALSE, NULL);
311 }
312 else {
313 vqParams.callback = callback_availBufReady;
314 }
316 /*
317 * Create a pair VirtQueues (one for sending, one for receiving).
318 * Note: First one gets an even, second gets odd vq ID.
319 */
320 vqParams.vqId = ID_SELF_TO_A9;
321 transport.virtQueue_toHost = (Ptr)VirtQueue_create(remoteProcId,
322 &vqParams, NULL);
323 vqParams.vqId = ID_A9_TO_SELF;
324 transport.virtQueue_fromHost = (Ptr)VirtQueue_create(remoteProcId,
325 &vqParams, NULL);
327 /* Plug Vring Interrupts, and wait for host ready to recv kick: */
328 VirtQueue_startup(remoteProcId, isHost);
330 /* construct the Swi to process incoming messages: */
331 transport.swiHandle = Swi_create(RPMessage_swiFxn, NULL, NULL);
333 exit:
334 Log_print0(Diags_EXIT, "<-- "FXNN);
335 }
336 #undef FXNN
338 /*
339 * ======== MessasgeQCopy_finalize ========
340 */
341 #define FXNN "RPMessage_finalize"
342 Void RPMessage_finalize()
343 {
344 Log_print0(Diags_ENTRY, "--> "FXNN);
346 if (!curInit || --curInit) {
347 goto exit; /* module still in use, or uninitialized */
348 }
350 /* Tear down Module */
351 HeapBuf_delete(&(module.heap));
353 Swi_delete(&(transport.swiHandle));
355 GateAll_delete(&module.gateH);
357 exit:
358 Log_print0(Diags_EXIT, "<-- "FXNN);
359 }
360 #undef FXNN
362 /*
363 * ======== RPMessage_create ========
364 */
365 #define FXNN "RPMessage_create"
366 RPMessage_Handle RPMessage_create(UInt32 reserved,
367 RPMessage_callback cb,
368 UArg arg,
369 UInt32 * endpoint)
370 {
371 RPMessage_Object *obj = NULL;
372 Bool found = FALSE;
373 Int i;
374 UInt16 queueIndex = 0;
375 IArg key;
377 Log_print4(Diags_ENTRY, "--> "FXNN": "
378 "(reserved=%d, cb=0x%x, arg=0x%x, endpoint=0x%x)",
379 (IArg)reserved, (IArg)cb, (IArg)arg, (IArg)endpoint);
381 Assert_isTrue((curInit > 0) , NULL);
383 key = GateAll_enter(module.gateH);
385 if (reserved == RPMessage_ASSIGN_ANY) {
386 /* Search the array for a free slot above reserved: */
387 for (i = RPMessage_MAX_RESERVED_ENDPOINT + 1;
388 (i < MAXMESSAGEQOBJECTS) && (found == FALSE) ; i++) {
389 if (module.msgqObjects[i] == NULL) {
390 queueIndex = i;
391 found = TRUE;
392 break;
393 }
394 }
395 }
396 else if ((queueIndex = reserved) <= RPMessage_MAX_RESERVED_ENDPOINT) {
397 if (module.msgqObjects[queueIndex] == NULL) {
398 found = TRUE;
399 }
400 }
402 if (found) {
403 obj = Memory_alloc(NULL, sizeof(RPMessage_Object), 0, NULL);
404 if (obj != NULL) {
405 if (cb) {
406 /* Store callback and it's arg instead of semaphore: */
407 obj->cb = cb;
408 obj->arg= arg;
409 }
410 else {
411 obj->cb = NULL;
413 /* Allocate a semaphore to signal when messages received: */
414 obj->semHandle = Semaphore_create(0, NULL, NULL);
416 /* Create our queue of to be received messages: */
417 obj->queue = List_create(NULL, NULL);
418 }
420 /* Store our endpoint, and object: */
421 obj->queueId = queueIndex;
422 module.msgqObjects[queueIndex] = obj;
424 /* See RPMessage_unblock() */
425 obj->unblocked = FALSE;
427 *endpoint = queueIndex;
428 Log_print1(Diags_LIFECYCLE, FXNN": endPt created: %d",
429 (IArg)queueIndex);
430 }
431 }
433 GateAll_leave(module.gateH, key);
435 Log_print1(Diags_EXIT, "<-- "FXNN": 0x%x", (IArg)obj);
436 return (obj);
437 }
438 #undef FXNN
440 /*
441 * ======== RPMessage_delete ========
442 */
443 #define FXNN "RPMessage_delete"
444 Int RPMessage_delete(RPMessage_Handle *handlePtr)
445 {
446 Int status = RPMessage_S_SUCCESS;
447 RPMessage_Object *obj;
448 Queue_elem *payload;
449 IArg key;
451 Log_print1(Diags_ENTRY, "--> "FXNN": (handlePtr=0x%x)", (IArg)handlePtr);
453 Assert_isTrue((curInit > 0) , NULL);
455 if (handlePtr && (obj = (RPMessage_Object *)(*handlePtr))) {
457 if (obj->cb) {
458 obj->cb = NULL;
459 obj->arg= NULL;
460 }
461 else {
462 Semaphore_delete(&(obj->semHandle));
464 /* Free/discard all queued message buffers: */
465 while ((payload = (Queue_elem *)List_get(obj->queue)) != NULL) {
466 HeapBuf_free(module.heap, (Ptr)payload, MSGBUFFERSIZE);
467 }
469 List_delete(&(obj->queue));
470 }
472 /* Null out our slot: */
473 key = GateAll_enter(module.gateH);
474 module.msgqObjects[obj->queueId] = NULL;
475 GateAll_leave(module.gateH, key);
477 Log_print1(Diags_LIFECYCLE, FXNN": endPt deleted: %d",
478 (IArg)obj->queueId);
480 /* Now free the obj */
481 Memory_free(NULL, obj, sizeof(RPMessage_Object));
483 *handlePtr = NULL;
484 }
486 Log_print1(Diags_EXIT, "<-- "FXNN": %d", (IArg)status);
487 return(status);
488 }
489 #undef FXNN
491 /*
492 * ======== RPMessage_recv ========
493 */
494 #define FXNN "RPMessage_recv"
495 Int RPMessage_recv(RPMessage_Handle handle, Ptr data, UInt16 *len,
496 UInt32 *rplyEndpt, UInt timeout)
497 {
498 Int status = RPMessage_S_SUCCESS;
499 RPMessage_Object *obj = (RPMessage_Object *)handle;
500 Bool semStatus;
501 Queue_elem *payload;
503 Log_print5(Diags_ENTRY, "--> "FXNN": (handle=0x%x, data=0x%x, len=0x%x,"
504 "rplyEndpt=0x%x, timeout=%d)", (IArg)handle, (IArg)data,
505 (IArg)len, (IArg)rplyEndpt, (IArg)timeout);
507 Assert_isTrue((curInit > 0) , NULL);
508 /* A callback was set: client should not be calling this fxn! */
509 Assert_isTrue((!obj->cb), NULL);
511 /* Check vring for pending messages before we block: */
512 Swi_post(transport.swiHandle);
514 /* Block until notified. */
515 semStatus = Semaphore_pend(obj->semHandle, timeout);
517 if (semStatus == FALSE) {
518 status = RPMessage_E_TIMEOUT;
519 Log_print0(Diags_STATUS, FXNN": Sem pend timeout!");
520 }
521 else if (obj->unblocked) {
522 status = RPMessage_E_UNBLOCKED;
523 }
524 else {
525 payload = (Queue_elem *)List_get(obj->queue);
526 Assert_isTrue((!payload), NULL);
527 }
529 if (status == RPMessage_S_SUCCESS) {
530 /* Now, copy payload to client and free our internal msg */
531 memcpy(data, payload->data, payload->len);
532 *len = payload->len;
533 *rplyEndpt = payload->src;
535 HeapBuf_free(module.heap, (Ptr)payload,
536 (payload->len + sizeof(Queue_elem)));
537 }
539 Log_print1(Diags_EXIT, "<-- "FXNN": %d", (IArg)status);
540 return (status);
541 }
542 #undef FXNN
544 /*
545 * ======== RPMessage_send ========
546 */
547 #define FXNN "RPMessage_send"
548 Int RPMessage_send(UInt16 dstProc,
549 UInt32 dstEndpt,
550 UInt32 srcEndpt,
551 Ptr data,
552 UInt16 len)
553 {
554 Int status = RPMessage_S_SUCCESS;
555 RPMessage_Object *obj;
556 Int16 token = 0;
557 RPMessage_Msg msg;
558 Queue_elem *payload;
559 UInt size;
560 IArg key;
561 int length;
563 Log_print5(Diags_ENTRY, "--> "FXNN": (dstProc=%d, dstEndpt=%d, "
564 "srcEndpt=%d, data=0x%x, len=%d", (IArg)dstProc, (IArg)dstEndpt,
565 (IArg)srcEndpt, (IArg)data, (IArg)len);
567 Assert_isTrue((curInit > 0) , NULL);
569 if (dstProc != MultiProc_self()) {
570 /* Send to remote processor: */
571 do {
572 token = VirtQueue_getAvailBuf(transport.virtQueue_toHost,
573 (Void **)&msg, &length);
574 } while (token < 0 && Semaphore_pend(transport.semHandle_toHost,
575 BIOS_WAIT_FOREVER));
576 if (token >= 0) {
577 /* Copy the payload and set message header: */
578 memcpy(msg->payload, data, len);
579 msg->dataLen = len;
580 msg->dstAddr = dstEndpt;
581 msg->srcAddr = srcEndpt;
582 msg->flags = 0;
583 msg->reserved = 0;
585 VirtQueue_addUsedBuf(transport.virtQueue_toHost, token,
586 RPMSG_BUF_SIZE);
587 VirtQueue_kick(transport.virtQueue_toHost);
588 }
589 else {
590 status = RPMessage_E_FAIL;
591 Log_print0(Diags_STATUS, FXNN": getAvailBuf failed!");
592 }
593 }
594 else {
595 /* Put on a Message queue on this processor: */
597 /* Protect from RPMessage_delete */
598 key = GateAll_enter(module.gateH);
599 obj = module.msgqObjects[dstEndpt];
600 GateAll_leave(module.gateH, key);
602 if (obj == NULL) {
603 Log_print1(Diags_STATUS, FXNN": no object for endpoint: %d",
604 (IArg)dstEndpt);
605 status = RPMessage_E_NOENDPT;
606 return status;
607 }
609 /* If callback registered, call it: */
610 if (obj->cb) {
611 Log_print2(Diags_INFO, FXNN": calling callback with data len: "
612 "%d, from: %d\n", len, srcEndpt);
613 obj->cb(obj, obj->arg, data, len, srcEndpt);
614 }
615 else {
616 /* else, put on a Message queue on this processor: */
617 /* Allocate a buffer to copy the payload: */
618 size = len + sizeof(Queue_elem);
620 /* HeapBuf_alloc() is non-blocking, so needs protection: */
621 key = GateAll_enter(module.gateH);
622 payload = (Queue_elem *)HeapBuf_alloc(module.heap, size, 0, NULL);
623 GateAll_leave(module.gateH, key);
625 if (payload != NULL) {
626 memcpy(payload->data, data, len);
627 payload->len = len;
628 payload->src = srcEndpt;
630 /* Put on the endpoint's queue and signal: */
631 List_put(obj->queue, (List_Elem *)payload);
632 Semaphore_post(obj->semHandle);
633 }
634 else {
635 status = RPMessage_E_MEMORY;
636 Log_print0(Diags_STATUS, FXNN": HeapBuf_alloc failed!");
637 }
638 }
639 }
641 Log_print1(Diags_EXIT, "<-- "FXNN": %d", (IArg)status);
642 return (status);
643 }
644 #undef FXNN
646 /*
647 * ======== RPMessage_unblock ========
648 */
649 #define FXNN "RPMessage_unblock"
650 Void RPMessage_unblock(RPMessage_Handle handle)
651 {
652 RPMessage_Object *obj = (RPMessage_Object *)handle;
654 Log_print1(Diags_ENTRY, "--> "FXNN": (handle=0x%x)", (IArg)handle);
656 Assert_isTrue((!obj->cb), NULL);
657 /* Set instance to 'unblocked' state, and post */
658 obj->unblocked = TRUE;
659 Semaphore_post(obj->semHandle);
660 Log_print0(Diags_EXIT, "<-- "FXNN);
661 }
662 #undef FXNN