lbessard@321: /**
Edouard@954:  * Xenomai Linux specific code
greg@336:  **/
lbessard@321: 
lbessard@321: #include <stdio.h>
Edouard@615: #include <unistd.h>
lbessard@321: #include <string.h>
lbessard@321: #include <time.h>
lbessard@321: #include <signal.h>
lbessard@321: #include <stdlib.h>
lbessard@321: #include <sys/mman.h>
greg@342: #include <sys/fcntl.h>
lbessard@321: 
Edouard@1974: #include <alchemy/task.h>
Edouard@1974: #include <alchemy/timer.h>
Edouard@1974: #include <alchemy/sem.h>
Edouard@1974: #include <alchemy/pipe.h>
lbessard@321: 
lbessard@321: unsigned int PLC_state = 0;
Edouard@615: #define PLC_STATE_TASK_CREATED                 1
Edouard@615: #define PLC_STATE_DEBUG_FILE_OPENED            2 
Edouard@615: #define PLC_STATE_DEBUG_PIPE_CREATED           4 
Edouard@615: #define PLC_STATE_PYTHON_FILE_OPENED           8 
Edouard@615: #define PLC_STATE_PYTHON_PIPE_CREATED          16   
Edouard@615: #define PLC_STATE_WAITDEBUG_FILE_OPENED        32   
Edouard@615: #define PLC_STATE_WAITDEBUG_PIPE_CREATED       64
Edouard@615: #define PLC_STATE_WAITPYTHON_FILE_OPENED       128
Edouard@615: #define PLC_STATE_WAITPYTHON_PIPE_CREATED      256
Edouard@2820: #define PLC_STATE_SVGHMI_FILE_OPENED           512
Edouard@2820: #define PLC_STATE_SVGHMI_PIPE_CREATED          1024
Edouard@615: 
Edouard@615: #define WAITDEBUG_PIPE_DEVICE        "/dev/rtp0"
Edouard@615: #define WAITDEBUG_PIPE_MINOR         0
Edouard@615: #define DEBUG_PIPE_DEVICE            "/dev/rtp1"
Edouard@615: #define DEBUG_PIPE_MINOR             1
Edouard@615: #define WAITPYTHON_PIPE_DEVICE       "/dev/rtp2"
Edouard@615: #define WAITPYTHON_PIPE_MINOR        2
Edouard@615: #define PYTHON_PIPE_DEVICE           "/dev/rtp3"
Edouard@615: #define PYTHON_PIPE_MINOR            3
Edouard@2820: #define SVGHMI_PIPE_DEVICE           "/dev/rtp4"
Edouard@2820: #define SVGHMI_PIPE_MINOR            4
Edouard@615: #define PIPE_SIZE                    1 
lbessard@321: 
Edouard@1990: // rt-pipes commands
Edouard@1990: 
Edouard@1990: #define PYTHON_PENDING_COMMAND 1
Edouard@1990: #define PYTHON_FINISH 2
Edouard@1990: 
Edouard@1990: #define DEBUG_FINISH 2
Edouard@1990: 
Edouard@1990: #define DEBUG_PENDING_DATA 1
Edouard@1990: #define DEBUG_UNLOCK 1
lbessard@321: 
lbessard@321: long AtomicCompareExchange(long* atomicvar,long compared, long exchange)
lbessard@321: {
lbessard@321:     return __sync_val_compare_and_swap(atomicvar, compared, exchange);
lbessard@321: }
Edouard@954: long long AtomicCompareExchange64(long long* atomicvar, long long compared, long long exchange)
Edouard@954: {
Edouard@954:     return __sync_val_compare_and_swap(atomicvar, compared, exchange);
Edouard@954: }
lbessard@321: 
lbessard@321: void PLC_GetTime(IEC_TIME *CURRENT_TIME)
lbessard@321: {
lbessard@321:     RTIME current_time = rt_timer_read();
lbessard@321:     CURRENT_TIME->tv_sec = current_time / 1000000000;
lbessard@321:     CURRENT_TIME->tv_nsec = current_time % 1000000000;
lbessard@321: }
lbessard@321: 
lbessard@321: RT_TASK PLC_task;
greg@342: RT_PIPE WaitDebug_pipe;
Edouard@615: RT_PIPE WaitPython_pipe;
Edouard@615: RT_PIPE Debug_pipe;
Edouard@615: RT_PIPE Python_pipe;
Edouard@2820: RT_PIPE svghmi_pipe;
Edouard@615: int WaitDebug_pipe_fd;
Edouard@615: int WaitPython_pipe_fd;
Edouard@615: int Debug_pipe_fd;
Edouard@615: int Python_pipe_fd;
Edouard@2820: int svghmi_pipe_fd;
Edouard@615: 
lbessard@321: int PLC_shutdown = 0;
lbessard@321: 
Edouard@615: void PLC_SetTimer(unsigned long long next, unsigned long long period)
lbessard@321: {
lbessard@321:   RTIME current_time = rt_timer_read();
lbessard@321:   rt_task_set_periodic(&PLC_task, current_time + next, rt_timer_ns2ticks(period));
lbessard@321: }
lbessard@321: 
lbessard@321: void PLC_task_proc(void *arg)
lbessard@321: {
Edouard@1428:     PLC_SetTimer(common_ticktime__, common_ticktime__);
greg@336: 
Edouard@695:     while (!PLC_shutdown) {
lbessard@321:         PLC_GetTime(&__CURRENT_TIME);
lbessard@321:         __run();
lbessard@321:         if (PLC_shutdown) break;
lbessard@321:         rt_task_wait_period(NULL);
lbessard@321:     }
Edouard@1990:     /* since xenomai 3 it is not enough to close() 
Edouard@1990:        file descriptor to unblock read()... */
Edouard@1990:     {
Edouard@1990:         /* explicitely finish python thread */
Edouard@1990:         char msg = PYTHON_FINISH;
Edouard@1990:         rt_pipe_write(&WaitPython_pipe, &msg, sizeof(msg), P_NORMAL);
Edouard@1990:     }
Edouard@1990:     {
Edouard@1990:         /* explicitely finish debug thread */
Edouard@1990:         char msg = DEBUG_FINISH;
Edouard@1990:         rt_pipe_write(&WaitDebug_pipe, &msg, sizeof(msg), P_NORMAL);
Edouard@1990:     }
lbessard@321: }
lbessard@321: 
laurent@397: static unsigned long __debug_tick;
lbessard@321: 
lbessard@321: void PLC_cleanup_all(void)
lbessard@321: {
lbessard@321:     if (PLC_state & PLC_STATE_TASK_CREATED) {
lbessard@321:         rt_task_delete(&PLC_task);
lbessard@321:         PLC_state &= ~PLC_STATE_TASK_CREATED;
lbessard@321:     }
lbessard@321: 
Edouard@2820:     if (PLC_state & PLC_STATE_SVGHMI_PIPE_CREATED) {
Edouard@2820:         rt_pipe_delete(&svghmi_pipe);
Edouard@2820:         PLC_state &= ~PLC_STATE_SVGHMI_PIPE_CREATED;
Edouard@2820:     }
Edouard@2820: 
Edouard@2820:     if (PLC_state & PLC_STATE_SVGHMI_FILE_OPENED) {
Edouard@2820:         close(svghmi_pipe_fd);
Edouard@2820:         PLC_state &= ~PLC_STATE_SVGHMI_FILE_OPENED;
Edouard@2820:     }
Edouard@2820: 
Edouard@615:     if (PLC_state & PLC_STATE_WAITDEBUG_PIPE_CREATED) {
Edouard@615:         rt_pipe_delete(&WaitDebug_pipe);
Edouard@615:         PLC_state &= ~PLC_STATE_WAITDEBUG_PIPE_CREATED;
Edouard@615:     }
Edouard@615: 
Edouard@615:     if (PLC_state & PLC_STATE_WAITDEBUG_FILE_OPENED) {
Edouard@615:         close(WaitDebug_pipe_fd);
Edouard@615:         PLC_state &= ~PLC_STATE_WAITDEBUG_FILE_OPENED;
Edouard@615:     }
Edouard@615: 
Edouard@615:     if (PLC_state & PLC_STATE_WAITPYTHON_PIPE_CREATED) {
Edouard@615:         rt_pipe_delete(&WaitPython_pipe);
laurent@745:         PLC_state &= ~PLC_STATE_WAITPYTHON_PIPE_CREATED;
laurent@745:     }
laurent@745: 
laurent@745:     if (PLC_state & PLC_STATE_WAITPYTHON_FILE_OPENED) {
Edouard@615:         close(WaitPython_pipe_fd);
Edouard@615:         PLC_state &= ~PLC_STATE_WAITPYTHON_FILE_OPENED;
lbessard@321:     }
greg@336: 
greg@342:     if (PLC_state & PLC_STATE_DEBUG_PIPE_CREATED) {
Edouard@615:         rt_pipe_delete(&Debug_pipe);
greg@342:         PLC_state &= ~PLC_STATE_DEBUG_PIPE_CREATED;
greg@342:     }
greg@342: 
greg@342:     if (PLC_state & PLC_STATE_DEBUG_FILE_OPENED) {
Edouard@615:         close(Debug_pipe_fd);
greg@342:         PLC_state &= ~PLC_STATE_DEBUG_FILE_OPENED;
lbessard@321:     }
lbessard@321: 
Edouard@615:     if (PLC_state & PLC_STATE_PYTHON_PIPE_CREATED) {
Edouard@615:         rt_pipe_delete(&Python_pipe);
laurent@745:         PLC_state &= ~PLC_STATE_PYTHON_PIPE_CREATED;
laurent@745:     }
laurent@745: 
laurent@745:     if (PLC_state & PLC_STATE_PYTHON_FILE_OPENED) {
Edouard@615:         close(Python_pipe_fd);
Edouard@615:         PLC_state &= ~PLC_STATE_PYTHON_FILE_OPENED;
Edouard@615:     }
Edouard@615: 
lbessard@321: }
lbessard@321: 
lbessard@321: int stopPLC()
lbessard@321: {
laurent@745:     /* Stop the PLC */
lbessard@321:     PLC_shutdown = 1;
laurent@745: 
laurent@745:     /* Wait until PLC task stops */
laurent@745:     rt_task_join(&PLC_task);
laurent@745: 
laurent@745:     PLC_cleanup_all();
greg@342:     __cleanup();
lbessard@321:     __debug_tick = -1;
Edouard@615:     return 0;
lbessard@321: }
lbessard@321: 
lbessard@321: //
lbessard@321: void catch_signal(int sig)
lbessard@321: {
lbessard@321:     stopPLC();
lbessard@321: //  signal(SIGTERM, catch_signal);
lbessard@321:     signal(SIGINT, catch_signal);
lbessard@321:     printf("Got Signal %d\n",sig);
lbessard@321:     exit(0);
lbessard@321: }
lbessard@321: 
Edouard@1981: #define _startPLCLog(text) \
Edouard@1981:     {\
Edouard@1981:     	char mstr[] = text;\
Edouard@1981:         LogMessage(LOG_CRITICAL, mstr, sizeof(mstr));\
Edouard@1981:         goto error;\
Edouard@1981:     }
Edouard@1981: 
Edouard@1981: #define FO "Failed opening "
Edouard@1981: 
lbessard@321: #define max_val(a,b) ((a>b)?a:b)
lbessard@321: int startPLC(int argc,char **argv)
lbessard@321: {
lbessard@321:     signal(SIGINT, catch_signal);
greg@336: 
Edouard@615:     /* no memory swapping for that process */
lbessard@321:     mlockall(MCL_CURRENT | MCL_FUTURE);
greg@336: 
laurent@745:     PLC_shutdown = 0;
laurent@745: 
Edouard@615:     /*** RT Pipes creation and opening ***/
Edouard@615:     /* create Debug_pipe */
Edouard@1980:     if(rt_pipe_create(&Debug_pipe, "Debug_pipe", DEBUG_PIPE_MINOR, PIPE_SIZE) < 0) 
Edouard@1981:         _startPLCLog(FO "Debug_pipe real-time end");
Edouard@615:     PLC_state |= PLC_STATE_DEBUG_PIPE_CREATED;
Edouard@615: 
Edouard@615:     /* open Debug_pipe*/
Edouard@1981:     if((Debug_pipe_fd = open(DEBUG_PIPE_DEVICE, O_RDWR)) == -1)
Edouard@1981:         _startPLCLog(FO DEBUG_PIPE_DEVICE);
Edouard@615:     PLC_state |= PLC_STATE_DEBUG_FILE_OPENED;
Edouard@615: 
Edouard@615:     /* create Python_pipe */
Edouard@1980:     if(rt_pipe_create(&Python_pipe, "Python_pipe", PYTHON_PIPE_MINOR, PIPE_SIZE) < 0) 
Edouard@1981:         _startPLCLog(FO "Python_pipe real-time end");
Edouard@615:     PLC_state |= PLC_STATE_PYTHON_PIPE_CREATED;
Edouard@615: 
Edouard@615:     /* open Python_pipe*/
Edouard@1981:     if((Python_pipe_fd = open(PYTHON_PIPE_DEVICE, O_RDWR)) == -1)
Edouard@1981:         _startPLCLog(FO PYTHON_PIPE_DEVICE);
Edouard@615:     PLC_state |= PLC_STATE_PYTHON_FILE_OPENED;
greg@336: 
greg@342:     /* create WaitDebug_pipe */
Edouard@1980:     if(rt_pipe_create(&WaitDebug_pipe, "WaitDebug_pipe", WAITDEBUG_PIPE_MINOR, PIPE_SIZE) < 0)
Edouard@1981:         _startPLCLog(FO "WaitDebug_pipe real-time end");
Edouard@615:     PLC_state |= PLC_STATE_WAITDEBUG_PIPE_CREATED;
greg@342: 
greg@342:     /* open WaitDebug_pipe*/
Edouard@1981:     if((WaitDebug_pipe_fd = open(WAITDEBUG_PIPE_DEVICE, O_RDWR)) == -1)
Edouard@1981:         _startPLCLog(FO WAITDEBUG_PIPE_DEVICE);
Edouard@615:     PLC_state |= PLC_STATE_WAITDEBUG_FILE_OPENED;
Edouard@615: 
Edouard@615:     /* create WaitPython_pipe */
Edouard@1980:     if(rt_pipe_create(&WaitPython_pipe, "WaitPython_pipe", WAITPYTHON_PIPE_MINOR, PIPE_SIZE) < 0)
Edouard@1981:         _startPLCLog(FO "WaitPython_pipe real-time end");
Edouard@615:     PLC_state |= PLC_STATE_WAITPYTHON_PIPE_CREATED;
Edouard@615: 
Edouard@615:     /* open WaitPython_pipe*/
Edouard@1981:     if((WaitPython_pipe_fd = open(WAITPYTHON_PIPE_DEVICE, O_RDWR)) == -1)
Edouard@1981:         _startPLCLog(FO WAITPYTHON_PIPE_DEVICE);
Edouard@615:     PLC_state |= PLC_STATE_WAITPYTHON_FILE_OPENED;
Edouard@615: 
Edouard@2820:     /* create svghmi_pipe */
Edouard@2820:     if(rt_pipe_create(&svghmi_pipe, "svghmi_pipe", SVGHMI_PIPE_MINOR, PIPE_SIZE) < 0)
Edouard@2820:         _startPLCLog(FO "svghmi_pipe real-time end");
Edouard@2820:     PLC_state |= PLC_STATE_SVGHMI_PIPE_CREATED;
Edouard@2820: 
Edouard@2820:     /* open svghmi_pipe*/
Edouard@2820:     if((svghmi_pipe_fd = open(SVGHMI_PIPE_DEVICE, O_RDWR)) == -1)
Edouard@2820:         _startPLCLog(FO SVGHMI_PIPE_DEVICE);
Edouard@2820:     PLC_state |= PLC_STATE_SVGHMI_FILE_OPENED;
Edouard@2820: 
Edouard@615:     /*** create PLC task ***/
Edouard@1981:     if(rt_task_create(&PLC_task, "PLC_task", 0, 50, T_JOINABLE))
Edouard@1981:         _startPLCLog("Failed creating PLC task");
lbessard@321:     PLC_state |= PLC_STATE_TASK_CREATED;
greg@336: 
Edouard@615:     if(__init(argc,argv)) goto error;
Edouard@615: 
Edouard@615:     /* start PLC task */
Edouard@1981:     if(rt_task_start(&PLC_task, &PLC_task_proc, NULL))
Edouard@1981:         _startPLCLog("Failed starting PLC task");
lbessard@321: 
lbessard@321:     return 0;
lbessard@321: 
lbessard@321: error:
Edouard@616: 
lbessard@321:     PLC_cleanup_all();
lbessard@321:     return 1;
lbessard@321: }
lbessard@321: 
Edouard@615: #define DEBUG_FREE 0
Edouard@615: #define DEBUG_BUSY 1
Edouard@615: static long debug_state = DEBUG_FREE;
Edouard@615: 
lbessard@321: int TryEnterDebugSection(void)
lbessard@321: {
Edouard@616:     if(AtomicCompareExchange(
Edouard@615:         &debug_state,
Edouard@615:         DEBUG_FREE,
Edouard@616:         DEBUG_BUSY) == DEBUG_FREE){
Edouard@616:         if(__DEBUG){
Edouard@616:             return 1;
Edouard@616:         }
Edouard@616:         AtomicCompareExchange( &debug_state, DEBUG_BUSY, DEBUG_FREE);
Edouard@616:     }
Edouard@616:     return 0;
Edouard@615: }
Edouard@615: 
lbessard@321: void LeaveDebugSection(void)
lbessard@321: {
Edouard@615:     if(AtomicCompareExchange( &debug_state, 
Edouard@615:         DEBUG_BUSY, DEBUG_FREE) == DEBUG_BUSY){
Edouard@615:         char msg = DEBUG_UNLOCK;
Edouard@615:         /* signal to NRT for wakeup */
Edouard@615:         rt_pipe_write(&Debug_pipe, &msg, sizeof(msg), P_NORMAL);
Edouard@615:     }
lbessard@321: }
lbessard@321: 
laurent@397: extern unsigned long __tick;
Edouard@615: 
ed@446: int WaitDebugData(unsigned long *tick)
lbessard@321: {
Edouard@615:     char cmd;
greg@345:     int res;
laurent@745:     if (PLC_shutdown) return -1;
lbessard@321:     /* Wait signal from PLC thread */
Edouard@615:     res = read(WaitDebug_pipe_fd, &cmd, sizeof(cmd));
Edouard@617:     if (res == sizeof(cmd) && cmd == DEBUG_PENDING_DATA){
Edouard@617:         *tick = __debug_tick;
Edouard@615:         return 0;
Edouard@617:     }
greg@345:     return -1;
lbessard@321: }
greg@336: 
lbessard@321: /* Called by PLC thread when debug_publish finished
lbessard@321:  * This is supposed to unlock debugger thread in WaitDebugData*/
lbessard@321: void InitiateDebugTransfer()
lbessard@321: {
Edouard@615:     char msg = DEBUG_PENDING_DATA;
lbessard@321:     /* remember tick */
lbessard@321:     __debug_tick = __tick;
lbessard@321:     /* signal debugger thread it can read data */
Edouard@615:     rt_pipe_write(&WaitDebug_pipe, &msg, sizeof(msg), P_NORMAL);
Edouard@615: }
Edouard@615: 
Edouard@615: int suspendDebug(int disable)
Edouard@615: {
Edouard@615:     char cmd = DEBUG_UNLOCK;
laurent@745:     if (PLC_shutdown) return -1;
Edouard@615:     while(AtomicCompareExchange(
Edouard@615:             &debug_state,
Edouard@615:             DEBUG_FREE,
Edouard@615:             DEBUG_BUSY) != DEBUG_FREE &&
Edouard@615:             cmd == DEBUG_UNLOCK){
Edouard@616:        if(read(Debug_pipe_fd, &cmd, sizeof(cmd)) != sizeof(cmd)){
Edouard@615:            return -1;
Edouard@615:        }
Edouard@615:     }
Edouard@615:     __DEBUG = !disable;
Edouard@617:     if (disable)
Edouard@617:         AtomicCompareExchange( &debug_state, DEBUG_BUSY, DEBUG_FREE);
Edouard@615:     return 0;
lbessard@321: }
lbessard@321: 
lbessard@321: void resumeDebug(void)
lbessard@321: {
Edouard@615:     AtomicCompareExchange( &debug_state, DEBUG_BUSY, DEBUG_FREE);
Edouard@615: }
Edouard@615: 
Edouard@615: #define PYTHON_FREE 0
Edouard@615: #define PYTHON_BUSY 1
Edouard@615: static long python_state = PYTHON_FREE;
Edouard@615: 
lbessard@321: int WaitPythonCommands(void)
Edouard@615: { 
Edouard@615:     char cmd;
laurent@745:     if (PLC_shutdown) return -1;
lbessard@321:     /* Wait signal from PLC thread */
Edouard@615:     if(read(WaitPython_pipe_fd, &cmd, sizeof(cmd))==sizeof(cmd) && cmd==PYTHON_PENDING_COMMAND){
Edouard@615:         return 0;
greg@345:     }
greg@345:     return -1;
lbessard@321: }
greg@336: 
lbessard@321: /* Called by PLC thread on each new python command*/
lbessard@321: void UnBlockPythonCommands(void)
lbessard@321: {
Edouard@615:     char msg = PYTHON_PENDING_COMMAND;
Edouard@615:     rt_pipe_write(&WaitPython_pipe, &msg, sizeof(msg), P_NORMAL);
lbessard@321: }
lbessard@321: 
lbessard@321: int TryLockPython(void)
lbessard@321: {
Edouard@615:     return AtomicCompareExchange(
Edouard@615:         &python_state,
Edouard@615:         PYTHON_FREE,
Edouard@615:         PYTHON_BUSY) == PYTHON_FREE;
Edouard@615: }
Edouard@615: 
Edouard@615: #define UNLOCK_PYTHON 1
Edouard@615: void LockPython(void)
Edouard@615: {
Edouard@615:     char cmd = UNLOCK_PYTHON;
laurent@745:     if (PLC_shutdown) return;
Edouard@615:     while(AtomicCompareExchange(
Edouard@615:             &python_state,
Edouard@615:             PYTHON_FREE,
Edouard@615:             PYTHON_BUSY) != PYTHON_FREE &&
Edouard@615:             cmd == UNLOCK_PYTHON){
Edouard@615:        read(Python_pipe_fd, &cmd, sizeof(cmd));
Edouard@615:     }
lbessard@321: }
lbessard@321: 
lbessard@321: void UnLockPython(void)
lbessard@321: {
Edouard@615:     if(AtomicCompareExchange(
Edouard@615:             &python_state,
Edouard@615:             PYTHON_BUSY,
Edouard@615:             PYTHON_FREE) == PYTHON_BUSY){
Edouard@615:         if(rt_task_self()){/*is that the real time task ?*/
Edouard@615:            char cmd = UNLOCK_PYTHON;
Edouard@615:            rt_pipe_write(&Python_pipe, &cmd, sizeof(cmd), P_NORMAL);
Edouard@615:         }/* otherwise, no signaling from non real time */
Edouard@615:     }    /* as plc does not wait for lock. */
Edouard@615: }
Edouard@615: 
Edouard@2820: void SVGHMI_SuspendFromPythonThread(void)
Edouard@2820: {
Edouard@2820:     char cmd = 1; /*whatever*/
Edouard@2820:     read(svghmi_pipe_fd, &cmd, sizeof(cmd));
Edouard@2820: }
Edouard@2820: 
Edouard@2820: void SVGHMI_WakeupFromRTThread(void)
Edouard@2820: {
Edouard@2820:     char cmd;
Edouard@2820:     rt_pipe_write(&svghmi_pipe, &cmd, sizeof(cmd), P_NORMAL);
Edouard@2820: }
Edouard@2820: 
Edouard@1903: #ifndef HAVE_RETAIN
wuyangtang@1717: int CheckRetainBuffer(void)
wuyangtang@1717: {
wuyangtang@1717: 	return 1;
wuyangtang@1717: }
wuyangtang@1717: 
wuyangtang@1717: void ValidateRetainBuffer(void)
wuyangtang@1717: {
wuyangtang@1717: }
wuyangtang@1717: 
wuyangtang@1717: void InValidateRetainBuffer(void)
wuyangtang@1717: {
wuyangtang@1717: }
wuyangtang@1717: 
wuyangtang@1717: void Retain(unsigned int offset, unsigned int count, void *p)
wuyangtang@1717: {
wuyangtang@1717: }
wuyangtang@1717: 
wuyangtang@1717: void Remind(unsigned int offset, unsigned int count, void *p)
wuyangtang@1717: {
wuyangtang@1717: }
wuyangtang@1717: 
wuyangtang@1717: void CleanupRetain(void)
wuyangtang@1717: {
wuyangtang@1717: }
wuyangtang@1717: 
wuyangtang@1717: void InitRetain(void)
wuyangtang@1717: {
wuyangtang@1717: }
Edouard@1903: #endif // !HAVE_RETAIN