1 /*
2 * Copyright (c) 2011-2014, 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/GateHwi.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 GateHwi_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": Received 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 GateHwi_Params gatePrms;
262 HeapBuf_Params prms;
263 Semaphore_Params semParams;
264 int i;
265 Registry_Result result;
266 Bool isHost;
267 VirtQueue_Params vqParams;
269 if (curInit++) {
270 Log_print1(Diags_ENTRY, "--> "FXNN": (remoteProcId=%d)",
271 (IArg)remoteProcId);
272 goto exit; /* module already initialized */
273 }
275 /* register with xdc.runtime to get a diags mask */
276 result = Registry_addModule(&Registry_CURDESC, MODULE_NAME);
277 Assert_isTrue(result == Registry_SUCCESS, (Assert_Id)NULL);
279 /* Log should be after the Registry_CURDESC is initialized */
280 Log_print1(Diags_ENTRY, "--> "FXNN": (remoteProcId=%d)",
281 (IArg)remoteProcId);
283 /* Gate to protect module object and lists: */
284 GateHwi_Params_init(&gatePrms);
285 module.gateH = GateHwi_create(&gatePrms, NULL);
287 /* Initialize Module State: */
288 for (i = 0; i < MAXMESSAGEQOBJECTS; i++) {
289 module.msgqObjects[i] = NULL;
290 }
292 HeapBuf_Params_init(&prms);
293 prms.blockSize = MSGBUFFERSIZE;
294 prms.numBlocks = MAXMESSAGEBUFFERS;
295 prms.buf = recv_buffers;
296 prms.bufSize = MAXHEAPSIZE;
297 prms.align = HEAPALIGNMENT;
298 module.heap = HeapBuf_create(&prms, NULL);
299 if (module.heap == 0) {
300 System_abort("RPMessage_init: HeapBuf_create returned 0\n");
301 }
303 Semaphore_Params_init(&semParams);
304 semParams.mode = Semaphore_Mode_BINARY;
305 transport.semHandle_toHost = Semaphore_create(0, &semParams, NULL);
307 isHost = (MultiProc_self() == MultiProc_getId("HOST"));
309 /* Initialize Transport related objects: */
311 VirtQueue_Params_init(&vqParams);
312 if (isHost) {
313 /* We don't handle this case currently! Host would need to prime vq. */
314 Assert_isTrue(FALSE, NULL);
315 }
316 else {
317 vqParams.callback = callback_availBufReady;
318 }
320 /*
321 * Create a pair VirtQueues (one for sending, one for receiving).
322 * Note: First one gets an even, second gets odd vq ID.
323 */
324 vqParams.vqId = ID_SELF_TO_HOST;
325 transport.virtQueue_toHost = (Ptr)VirtQueue_create(remoteProcId,
326 &vqParams, NULL);
327 vqParams.vqId = ID_HOST_TO_SELF;
328 transport.virtQueue_fromHost = (Ptr)VirtQueue_create(remoteProcId,
329 &vqParams, NULL);
331 /* Plug Vring Interrupts, and wait for host ready to recv kick: */
332 VirtQueue_startup(remoteProcId, isHost);
334 /* construct the Swi to process incoming messages: */
335 transport.swiHandle = Swi_create(RPMessage_swiFxn, NULL, NULL);
337 exit:
338 Log_print0(Diags_EXIT, "<-- "FXNN);
339 }
340 #undef FXNN
342 /*
343 * ======== MessasgeQCopy_finalize ========
344 */
345 #define FXNN "RPMessage_finalize"
346 Void RPMessage_finalize()
347 {
348 Log_print0(Diags_ENTRY, "--> "FXNN);
350 if (!curInit || --curInit) {
351 goto exit; /* module still in use, or uninitialized */
352 }
354 /* Tear down Module */
355 HeapBuf_delete(&(module.heap));
357 Swi_delete(&(transport.swiHandle));
359 GateHwi_delete(&module.gateH);
361 exit:
362 Log_print0(Diags_EXIT, "<-- "FXNN);
363 }
364 #undef FXNN
366 /*
367 * ======== RPMessage_create ========
368 */
369 #define FXNN "RPMessage_create"
370 RPMessage_Handle RPMessage_create(UInt32 reserved,
371 RPMessage_callback cb,
372 UArg arg,
373 UInt32 * endpoint)
374 {
375 RPMessage_Object *obj = NULL;
376 Bool found = FALSE;
377 Int i;
378 UInt16 queueIndex = 0;
379 IArg key;
381 Log_print4(Diags_ENTRY, "--> "FXNN": "
382 "(reserved=%d, cb=0x%x, arg=0x%x, endpoint=0x%x)",
383 (IArg)reserved, (IArg)cb, (IArg)arg, (IArg)endpoint);
385 Assert_isTrue((curInit > 0) , NULL);
387 key = GateHwi_enter(module.gateH);
389 if (reserved == RPMessage_ASSIGN_ANY) {
390 /* Search the array for a free slot above reserved: */
391 for (i = RPMessage_MAX_RESERVED_ENDPOINT + 1;
392 (i < MAXMESSAGEQOBJECTS) && (found == FALSE) ; i++) {
393 if (module.msgqObjects[i] == NULL) {
394 queueIndex = i;
395 found = TRUE;
396 break;
397 }
398 }
399 }
400 else if ((queueIndex = reserved) <= RPMessage_MAX_RESERVED_ENDPOINT) {
401 if (module.msgqObjects[queueIndex] == NULL) {
402 found = TRUE;
403 }
404 }
406 if (found) {
407 obj = Memory_alloc(NULL, sizeof(RPMessage_Object), 0, NULL);
408 if (obj != NULL) {
409 if (cb) {
410 /* Store callback and it's arg instead of semaphore: */
411 obj->cb = cb;
412 obj->arg= arg;
413 }
414 else {
415 obj->cb = NULL;
417 /* Allocate a semaphore to signal when messages received: */
418 obj->semHandle = Semaphore_create(0, NULL, NULL);
420 /* Create our queue of to be received messages: */
421 obj->queue = List_create(NULL, NULL);
422 }
424 /* Store our endpoint, and object: */
425 obj->queueId = queueIndex;
426 module.msgqObjects[queueIndex] = obj;
428 /* See RPMessage_unblock() */
429 obj->unblocked = FALSE;
431 *endpoint = queueIndex;
432 Log_print1(Diags_LIFECYCLE, FXNN": endPt created: %d",
433 (IArg)queueIndex);
434 }
435 }
437 GateHwi_leave(module.gateH, key);
439 Log_print1(Diags_EXIT, "<-- "FXNN": 0x%x", (IArg)obj);
440 return (obj);
441 }
442 #undef FXNN
444 /*
445 * ======== RPMessage_delete ========
446 */
447 #define FXNN "RPMessage_delete"
448 Int RPMessage_delete(RPMessage_Handle *handlePtr)
449 {
450 Int status = RPMessage_S_SUCCESS;
451 RPMessage_Object *obj;
452 Queue_elem *payload;
453 IArg key;
455 Log_print1(Diags_ENTRY, "--> "FXNN": (handlePtr=0x%x)", (IArg)handlePtr);
457 Assert_isTrue((curInit > 0) , NULL);
459 if (handlePtr && (obj = (RPMessage_Object *)(*handlePtr))) {
461 if (obj->cb) {
462 obj->cb = NULL;
463 obj->arg= NULL;
464 }
465 else {
466 Semaphore_delete(&(obj->semHandle));
468 /* Free/discard all queued message buffers: */
469 while ((payload = (Queue_elem *)List_get(obj->queue)) != NULL) {
470 HeapBuf_free(module.heap, (Ptr)payload, MSGBUFFERSIZE);
471 }
473 List_delete(&(obj->queue));
474 }
476 /* Null out our slot: */
477 key = GateHwi_enter(module.gateH);
478 module.msgqObjects[obj->queueId] = NULL;
479 GateHwi_leave(module.gateH, key);
481 Log_print1(Diags_LIFECYCLE, FXNN": endPt deleted: %d",
482 (IArg)obj->queueId);
484 /* Now free the obj */
485 Memory_free(NULL, obj, sizeof(RPMessage_Object));
487 *handlePtr = NULL;
488 }
490 Log_print1(Diags_EXIT, "<-- "FXNN": %d", (IArg)status);
491 return(status);
492 }
493 #undef FXNN
495 /*
496 * ======== RPMessage_recv ========
497 */
498 #define FXNN "RPMessage_recv"
499 Int RPMessage_recv(RPMessage_Handle handle, Ptr data, UInt16 *len,
500 UInt32 *rplyEndpt, UInt timeout)
501 {
502 Int status = RPMessage_S_SUCCESS;
503 RPMessage_Object *obj = (RPMessage_Object *)handle;
504 Bool semStatus;
505 Queue_elem *payload;
507 Log_print5(Diags_ENTRY, "--> "FXNN": (handle=0x%x, data=0x%x, len=0x%x,"
508 "rplyEndpt=0x%x, timeout=%d)", (IArg)handle, (IArg)data,
509 (IArg)len, (IArg)rplyEndpt, (IArg)timeout);
511 Assert_isTrue((curInit > 0) , NULL);
512 /* A callback was set: client should not be calling this fxn! */
513 Assert_isTrue((!obj->cb), NULL);
515 /* Check vring for pending messages before we block: */
516 Swi_post(transport.swiHandle);
518 /* Block until notified. */
519 semStatus = Semaphore_pend(obj->semHandle, timeout);
521 if (semStatus == FALSE) {
522 status = RPMessage_E_TIMEOUT;
523 Log_print0(Diags_STATUS, FXNN": Sem pend timeout!");
524 }
525 else if (obj->unblocked) {
526 status = RPMessage_E_UNBLOCKED;
527 }
528 else {
529 payload = (Queue_elem *)List_get(obj->queue);
530 Assert_isTrue((!payload), NULL);
531 }
533 if (status == RPMessage_S_SUCCESS) {
534 /* Now, copy payload to client and free our internal msg */
535 memcpy(data, payload->data, payload->len);
536 *len = payload->len;
537 *rplyEndpt = payload->src;
539 HeapBuf_free(module.heap, (Ptr)payload,
540 (payload->len + sizeof(Queue_elem)));
541 }
543 Log_print1(Diags_EXIT, "<-- "FXNN": %d", (IArg)status);
544 return (status);
545 }
546 #undef FXNN
548 /*
549 * ======== RPMessage_send ========
550 */
551 #define FXNN "RPMessage_send"
552 Int RPMessage_send(UInt16 dstProc,
553 UInt32 dstEndpt,
554 UInt32 srcEndpt,
555 Ptr data,
556 UInt16 len)
557 {
558 Int status = RPMessage_S_SUCCESS;
559 RPMessage_Object *obj;
560 Int16 token = 0;
561 RPMessage_Msg msg;
562 Queue_elem *payload;
563 UInt size;
564 IArg key;
565 int length;
567 Log_print5(Diags_ENTRY, "--> "FXNN": (dstProc=%d, dstEndpt=%d, "
568 "srcEndpt=%d, data=0x%x, len=%d", (IArg)dstProc, (IArg)dstEndpt,
569 (IArg)srcEndpt, (IArg)data, (IArg)len);
571 Assert_isTrue((curInit > 0) , NULL);
573 if (dstProc != MultiProc_self()) {
574 /* Send to remote processor: */
575 do {
576 token = VirtQueue_getAvailBuf(transport.virtQueue_toHost,
577 (Void **)&msg, &length);
578 } while (token < 0 && Semaphore_pend(transport.semHandle_toHost,
579 BIOS_WAIT_FOREVER));
580 if (token >= 0) {
581 /* Copy the payload and set message header: */
582 memcpy(msg->payload, data, len);
583 msg->dataLen = len;
584 msg->dstAddr = dstEndpt;
585 msg->srcAddr = srcEndpt;
586 msg->flags = 0;
587 msg->reserved = 0;
589 VirtQueue_addUsedBuf(transport.virtQueue_toHost, token,
590 RPMSG_BUF_SIZE);
591 VirtQueue_kick(transport.virtQueue_toHost);
592 }
593 else {
594 status = RPMessage_E_FAIL;
595 Log_print0(Diags_STATUS, FXNN": getAvailBuf failed!");
596 }
597 }
598 else {
599 /* Put on a Message queue on this processor: */
601 /* Protect from RPMessage_delete */
602 key = GateHwi_enter(module.gateH);
603 obj = module.msgqObjects[dstEndpt];
604 GateHwi_leave(module.gateH, key);
606 if (obj == NULL) {
607 Log_print1(Diags_STATUS, FXNN": no object for endpoint: %d",
608 (IArg)dstEndpt);
609 status = RPMessage_E_NOENDPT;
610 return status;
611 }
613 /* If callback registered, call it: */
614 if (obj->cb) {
615 Log_print2(Diags_INFO, FXNN": calling callback with data len: "
616 "%d, from: %d", len, srcEndpt);
617 obj->cb(obj, obj->arg, data, len, srcEndpt);
618 }
619 else {
620 /* else, put on a Message queue on this processor: */
621 /* Allocate a buffer to copy the payload: */
622 size = len + sizeof(Queue_elem);
624 /* HeapBuf_alloc() is non-blocking, so needs protection: */
625 key = GateHwi_enter(module.gateH);
626 payload = (Queue_elem *)HeapBuf_alloc(module.heap, size, 0, NULL);
627 GateHwi_leave(module.gateH, key);
629 if (payload != NULL) {
630 memcpy(payload->data, data, len);
631 payload->len = len;
632 payload->src = srcEndpt;
634 /* Put on the endpoint's queue and signal: */
635 List_put(obj->queue, (List_Elem *)payload);
636 Semaphore_post(obj->semHandle);
637 }
638 else {
639 status = RPMessage_E_MEMORY;
640 Log_print0(Diags_STATUS, FXNN": HeapBuf_alloc failed!");
641 }
642 }
643 }
645 Log_print1(Diags_EXIT, "<-- "FXNN": %d", (IArg)status);
646 return (status);
647 }
648 #undef FXNN
650 /*
651 * ======== RPMessage_unblock ========
652 */
653 #define FXNN "RPMessage_unblock"
654 Void RPMessage_unblock(RPMessage_Handle handle)
655 {
656 RPMessage_Object *obj = (RPMessage_Object *)handle;
658 Log_print1(Diags_ENTRY, "--> "FXNN": (handle=0x%x)", (IArg)handle);
660 Assert_isTrue((!obj->cb), NULL);
661 /* Set instance to 'unblocked' state, and post */
662 obj->unblocked = TRUE;
663 Semaphore_post(obj->semHandle);
664 Log_print0(Diags_EXIT, "<-- "FXNN);
665 }
666 #undef FXNN