targets/Xenomai/plc_Xenomai_main.c
changeset 615 72bc3e53a1fa
parent 446 1edde533db19
child 616 b9271faec96e
equal deleted inserted replaced
614:eed1dcf311a1 615:72bc3e53a1fa
     1 /**
     1 /**
     2  * Linux specific code
     2  * Linux specific code
     3  **/
     3  **/
     4 
     4 
     5 #include <stdio.h>
     5 #include <stdio.h>
       
     6 #include <unistd.h>
     6 #include <string.h>
     7 #include <string.h>
     7 #include <time.h>
     8 #include <time.h>
     8 #include <signal.h>
     9 #include <signal.h>
     9 #include <stdlib.h>
    10 #include <stdlib.h>
    10 #include <sys/mman.h>
    11 #include <sys/mman.h>
    15 #include <native/mutex.h>
    16 #include <native/mutex.h>
    16 #include <native/sem.h>
    17 #include <native/sem.h>
    17 #include <native/pipe.h>
    18 #include <native/pipe.h>
    18 
    19 
    19 unsigned int PLC_state = 0;
    20 unsigned int PLC_state = 0;
    20 #define PLC_STATE_TASK_CREATED                  1
    21 #define PLC_STATE_TASK_CREATED                 1
    21 #define PLC_STATE_PYTHON_MUTEX_CREATED          2
    22 #define PLC_STATE_DEBUG_FILE_OPENED            2 
    22 #define PLC_STATE_PYTHON_WAIT_SEM_CREATED       4
    23 #define PLC_STATE_DEBUG_PIPE_CREATED           4 
    23 #define PLC_STATE_DEBUG_MUTEX_CREATED           8
    24 #define PLC_STATE_PYTHON_FILE_OPENED           8 
    24 #define PLC_STATE_DEBUG_FILE_OPENED             16
    25 #define PLC_STATE_PYTHON_PIPE_CREATED          16   
    25 #define PLC_STATE_DEBUG_PIPE_CREATED            32
    26 #define PLC_STATE_WAITDEBUG_FILE_OPENED        32   
    26 
    27 #define PLC_STATE_WAITDEBUG_PIPE_CREATED       64
    27 #define WAITDEBUG_PIPE_DEVICE       "/dev/rtp0"
    28 #define PLC_STATE_WAITPYTHON_FILE_OPENED       128
    28 #define WAITDEBUG_PIPE_MINOR        0
    29 #define PLC_STATE_WAITPYTHON_PIPE_CREATED      256
    29 #define WAITDEBUG_PIPE_SIZE         500
    30 
       
    31 #define WAITDEBUG_PIPE_DEVICE        "/dev/rtp0"
       
    32 #define WAITDEBUG_PIPE_MINOR         0
       
    33 #define DEBUG_PIPE_DEVICE            "/dev/rtp1"
       
    34 #define DEBUG_PIPE_MINOR             1
       
    35 #define WAITPYTHON_PIPE_DEVICE       "/dev/rtp2"
       
    36 #define WAITPYTHON_PIPE_MINOR        2
       
    37 #define PYTHON_PIPE_DEVICE           "/dev/rtp3"
       
    38 #define PYTHON_PIPE_MINOR            3
       
    39 #define PIPE_SIZE                    1 
    30 
    40 
    31 /* provided by POUS.C */
    41 /* provided by POUS.C */
    32 extern unsigned long common_ticktime__;
    42 extern unsigned long common_ticktime__;
    33 
    43 
    34 long AtomicCompareExchange(long* atomicvar,long compared, long exchange)
    44 long AtomicCompareExchange(long* atomicvar,long compared, long exchange)
    43     CURRENT_TIME->tv_nsec = current_time % 1000000000;
    53     CURRENT_TIME->tv_nsec = current_time % 1000000000;
    44 }
    54 }
    45 
    55 
    46 RT_TASK PLC_task;
    56 RT_TASK PLC_task;
    47 RT_PIPE WaitDebug_pipe;
    57 RT_PIPE WaitDebug_pipe;
    48 RT_TASK SuspendDebug_task;
    58 RT_PIPE WaitPython_pipe;
    49 RT_TASK ResumeDebug_task;
    59 RT_PIPE Debug_pipe;
    50 RT_TASK WaitPythonCommand_task;
    60 RT_PIPE Python_pipe;
    51 RT_TASK UnLockPython_task;
    61 int WaitDebug_pipe_fd;
    52 RT_TASK LockPython_task;
    62 int WaitPython_pipe_fd;
       
    63 int Debug_pipe_fd;
       
    64 int Python_pipe_fd;
       
    65 
    53 int PLC_shutdown = 0;
    66 int PLC_shutdown = 0;
    54 
    67 
    55 int WaitDebug_pipe_fd = -1;
    68 void PLC_SetTimer(unsigned long long next, unsigned long long period)
    56 
       
    57 void PLC_SetTimer(long long next, long long period)
       
    58 {
    69 {
    59   RTIME current_time = rt_timer_read();
    70   RTIME current_time = rt_timer_read();
    60   rt_task_set_periodic(&PLC_task, current_time + next, rt_timer_ns2ticks(period));
    71   rt_task_set_periodic(&PLC_task, current_time + next, rt_timer_ns2ticks(period));
    61 }
    72 }
    62 
    73 
    72     }
    83     }
    73 }
    84 }
    74 
    85 
    75 static unsigned long __debug_tick;
    86 static unsigned long __debug_tick;
    76 
    87 
    77 RT_SEM python_wait_sem;
       
    78 RT_MUTEX python_mutex;
       
    79 RT_MUTEX debug_mutex;
       
    80 
       
    81 void PLC_cleanup_all(void)
    88 void PLC_cleanup_all(void)
    82 {
    89 {
    83     if (PLC_state & PLC_STATE_TASK_CREATED) {
    90     if (PLC_state & PLC_STATE_TASK_CREATED) {
    84         rt_task_delete(&PLC_task);
    91         rt_task_delete(&PLC_task);
    85         PLC_state &= ~PLC_STATE_TASK_CREATED;
    92         PLC_state &= ~PLC_STATE_TASK_CREATED;
    86     }
    93     }
    87 
    94 
    88     if (PLC_state & PLC_STATE_PYTHON_WAIT_SEM_CREATED) {
    95     if (PLC_state & PLC_STATE_WAITDEBUG_PIPE_CREATED) {
    89         rt_sem_v(&python_wait_sem);
    96         rt_pipe_delete(&WaitDebug_pipe);
    90         rt_sem_delete(&python_wait_sem);
    97         PLC_state &= ~PLC_STATE_WAITDEBUG_PIPE_CREATED;
    91         PLC_state &= ~ PLC_STATE_PYTHON_WAIT_SEM_CREATED;
    98     }
    92     }
    99 
    93 
   100     if (PLC_state & PLC_STATE_WAITDEBUG_FILE_OPENED) {
    94     if (PLC_state & PLC_STATE_PYTHON_MUTEX_CREATED) {
   101         close(WaitDebug_pipe_fd);
    95         rt_mutex_delete(&python_mutex);
   102         PLC_state &= ~PLC_STATE_WAITDEBUG_FILE_OPENED;
    96         PLC_state &= ~ PLC_STATE_PYTHON_MUTEX_CREATED;
   103     }
       
   104 
       
   105     if (PLC_state & PLC_STATE_WAITPYTHON_PIPE_CREATED) {
       
   106         rt_pipe_delete(&WaitPython_pipe);
       
   107         PLC_state &= ~PLC_STATE_WAITDEBUG_PIPE_CREATED;
       
   108     }
       
   109 
       
   110     if (PLC_state & PLC_STATE_WAITPYTHON_PIPE_CREATED) {
       
   111         close(WaitPython_pipe_fd);
       
   112         PLC_state &= ~PLC_STATE_WAITPYTHON_FILE_OPENED;
    97     }
   113     }
    98 
   114 
    99     if (PLC_state & PLC_STATE_DEBUG_PIPE_CREATED) {
   115     if (PLC_state & PLC_STATE_DEBUG_PIPE_CREATED) {
   100         rt_pipe_delete(&WaitDebug_pipe);
   116         rt_pipe_delete(&Debug_pipe);
   101         PLC_state &= ~PLC_STATE_DEBUG_PIPE_CREATED;
   117         PLC_state &= ~PLC_STATE_DEBUG_PIPE_CREATED;
   102     }
   118     }
   103 
   119 
   104     if (PLC_state & PLC_STATE_DEBUG_FILE_OPENED) {
   120     if (PLC_state & PLC_STATE_DEBUG_FILE_OPENED) {
   105         close(WaitDebug_pipe_fd);
   121         close(Debug_pipe_fd);
   106         PLC_state &= ~PLC_STATE_DEBUG_FILE_OPENED;
   122         PLC_state &= ~PLC_STATE_DEBUG_FILE_OPENED;
   107     }
   123     }
   108 
   124 
   109     if (PLC_state & PLC_STATE_DEBUG_MUTEX_CREATED) {
   125     if (PLC_state & PLC_STATE_PYTHON_PIPE_CREATED) {
   110         rt_mutex_delete(&debug_mutex);
   126         rt_pipe_delete(&Python_pipe);
   111         PLC_state &= ~ PLC_STATE_DEBUG_MUTEX_CREATED;
   127         PLC_state &= ~PLC_STATE_DEBUG_PIPE_CREATED;
   112     }
   128     }
       
   129 
       
   130     if (PLC_state & PLC_STATE_PYTHON_PIPE_CREATED) {
       
   131         close(Python_pipe_fd);
       
   132         PLC_state &= ~PLC_STATE_PYTHON_FILE_OPENED;
       
   133     }
       
   134 
   113 }
   135 }
   114 
   136 
   115 int stopPLC()
   137 int stopPLC()
   116 {
   138 {
   117     PLC_shutdown = 1;
   139     PLC_shutdown = 1;
   118     /* Stop the PLC */
   140     /* Stop the PLC */
   119     PLC_SetTimer(0, 0);
   141     PLC_SetTimer(0, 0);
   120     __cleanup();
   142     __cleanup();
   121     PLC_cleanup_all();
   143     PLC_cleanup_all();
   122     __debug_tick = -1;
   144     __debug_tick = -1;
       
   145     return 0;
   123 }
   146 }
   124 
   147 
   125 //
   148 //
   126 void catch_signal(int sig)
   149 void catch_signal(int sig)
   127 {
   150 {
   133 }
   156 }
   134 
   157 
   135 #define max_val(a,b) ((a>b)?a:b)
   158 #define max_val(a,b) ((a>b)?a:b)
   136 int startPLC(int argc,char **argv)
   159 int startPLC(int argc,char **argv)
   137 {
   160 {
   138     int ret = 0;
       
   139 
       
   140     signal(SIGINT, catch_signal);
   161     signal(SIGINT, catch_signal);
   141 
   162 
   142     /* ne-memory-swapping for this program */
   163     /* no memory swapping for that process */
   143     mlockall(MCL_CURRENT | MCL_FUTURE);
   164     mlockall(MCL_CURRENT | MCL_FUTURE);
   144 
   165 
   145     /* Define Ttick to 1ms if common_ticktime not defined */
   166     /* Define Ttick to 1ms if common_ticktime not defined */
   146     Ttick = common_ticktime__?common_ticktime__:1000000;
   167     Ttick = common_ticktime__?common_ticktime__:1000000;
   147 
   168 
   148     /* create python_wait_sem */
   169     /*** RT Pipes creation and opening ***/
   149     ret = rt_sem_create(&python_wait_sem, "python_wait_sem", 0, S_FIFO);
   170     /* create Debug_pipe */
   150     if (ret) goto error;
   171     if(rt_pipe_create(&Debug_pipe, "Debug_pipe", DEBUG_PIPE_MINOR, PIPE_SIZE)) 
   151     PLC_state |= PLC_STATE_PYTHON_WAIT_SEM_CREATED;
   172         goto error;
   152 
   173     PLC_state |= PLC_STATE_DEBUG_PIPE_CREATED;
   153     /* create python_mutex */
   174 
   154     ret = rt_mutex_create(&python_mutex, "python_mutex");
   175     /* open Debug_pipe*/
   155     if (ret) goto error;
   176     if((Debug_pipe_fd = open(DEBUG_PIPE_DEVICE, O_RDWR)) == -1) goto error;
   156     PLC_state |= PLC_STATE_PYTHON_MUTEX_CREATED;
   177     PLC_state |= PLC_STATE_DEBUG_FILE_OPENED;
       
   178 
       
   179     /* create Python_pipe */
       
   180     if(rt_pipe_create(&Python_pipe, "Python_pipe", PYTHON_PIPE_MINOR, PIPE_SIZE)) 
       
   181         goto error;
       
   182     PLC_state |= PLC_STATE_PYTHON_PIPE_CREATED;
       
   183 
       
   184     /* open Python_pipe*/
       
   185     if((Python_pipe_fd = open(PYTHON_PIPE_DEVICE, O_RDWR)) == -1) goto error;
       
   186     PLC_state |= PLC_STATE_PYTHON_FILE_OPENED;
   157 
   187 
   158     /* create WaitDebug_pipe */
   188     /* create WaitDebug_pipe */
   159     ret = rt_pipe_create(&WaitDebug_pipe, "WaitDebug_pipe", WAITDEBUG_PIPE_MINOR,
   189     if(rt_pipe_create(&WaitDebug_pipe, "Debug_pipe", WAITDEBUG_PIPE_MINOR, PIPE_SIZE))
   160           WAITDEBUG_PIPE_SIZE * sizeof(char));
   190         goto error;
   161     if (ret) goto error;
   191     PLC_state |= PLC_STATE_WAITDEBUG_PIPE_CREATED;
   162     PLC_state |= PLC_STATE_DEBUG_PIPE_CREATED;
       
   163 
   192 
   164     /* open WaitDebug_pipe*/
   193     /* open WaitDebug_pipe*/
   165     WaitDebug_pipe_fd = open(WAITDEBUG_PIPE_DEVICE, O_RDWR);
   194     if((WaitDebug_pipe_fd = open(WAITDEBUG_PIPE_DEVICE, O_RDWR)) == -1) goto error;
   166     if (WaitDebug_pipe_fd == -1) {
   195     PLC_state |= PLC_STATE_WAITDEBUG_FILE_OPENED;
   167         ret = -EBADF;
   196 
       
   197     /* create WaitPython_pipe */
       
   198     if(rt_pipe_create(&WaitPython_pipe, "Python_pipe", WAITPYTHON_PIPE_MINOR, PIPE_SIZE))
   168         goto error;
   199         goto error;
   169     }
   200     PLC_state |= PLC_STATE_WAITPYTHON_PIPE_CREATED;
   170     PLC_state |= PLC_STATE_DEBUG_FILE_OPENED;
   201 
   171 
   202     /* open WaitPython_pipe*/
   172     /* create debug_mutex */
   203     if((WaitPython_pipe_fd = open(WAITPYTHON_PIPE_DEVICE, O_RDWR)) == -1) goto error;
   173     ret = rt_mutex_create(&debug_mutex, "debug_mutex");
   204     PLC_state |= PLC_STATE_WAITPYTHON_FILE_OPENED;
   174     if (ret) goto error;
   205 
   175     PLC_state |= PLC_STATE_DEBUG_MUTEX_CREATED;
   206     /*** create PLC task ***/
   176 
   207     if(rt_task_create(&PLC_task, "PLC_task", 0, 50, 0)) goto error;
   177     /* create can_driver_task */
       
   178     ret = rt_task_create(&PLC_task, "PLC_task", 0, 50, 0);
       
   179     if (ret) goto error;
       
   180     PLC_state |= PLC_STATE_TASK_CREATED;
   208     PLC_state |= PLC_STATE_TASK_CREATED;
   181 
   209 
   182     ret = __init(argc,argv);
   210     if(__init(argc,argv)) goto error;
   183     if (ret) goto error;
   211 
   184 
   212     /* start PLC task */
   185     /* start can_driver_task */
   213     if(rt_task_start(&PLC_task, &PLC_task_proc, NULL)) goto error;
   186     ret = rt_task_start(&PLC_task, &PLC_task_proc, NULL);
       
   187     if (ret) goto error;
       
   188 
   214 
   189     return 0;
   215     return 0;
   190 
   216 
   191 error:
   217 error:
   192     PLC_cleanup_all();
   218     PLC_cleanup_all();
   193     return 1;
   219     return 1;
   194 }
   220 }
   195 
   221 
       
   222 #define DEBUG_FREE 0
       
   223 #define DEBUG_BUSY 1
       
   224 static long debug_state = DEBUG_FREE;
       
   225 
   196 int TryEnterDebugSection(void)
   226 int TryEnterDebugSection(void)
   197 {
   227 {
   198     return rt_mutex_acquire(&debug_mutex, TM_NONBLOCK) == 0;
   228     long old_debug_state = AtomicCompareExchange(
   199 }
   229         &debug_state,
   200 
   230         DEBUG_FREE,
       
   231         DEBUG_BUSY);
       
   232     return old_debug_state == DEBUG_FREE;
       
   233 }
       
   234 
       
   235 #define DEBUG_UNLOCK 1
   201 void LeaveDebugSection(void)
   236 void LeaveDebugSection(void)
   202 {
   237 {
   203     rt_mutex_release(&debug_mutex);
   238     if(AtomicCompareExchange( &debug_state, 
       
   239         DEBUG_BUSY, DEBUG_FREE) == DEBUG_BUSY){
       
   240         char msg = DEBUG_UNLOCK;
       
   241         /* signal to NRT for wakeup */
       
   242         rt_pipe_write(&Debug_pipe, &msg, sizeof(msg), P_NORMAL);
       
   243     }
   204 }
   244 }
   205 
   245 
   206 extern unsigned long __tick;
   246 extern unsigned long __tick;
   207 /* from plc_debugger.c */
   247 
       
   248 #define DEBUG_PENDING_DATA 1
   208 int WaitDebugData(unsigned long *tick)
   249 int WaitDebugData(unsigned long *tick)
   209 {
   250 {
   210     char message;
   251     char cmd;
   211     int res;
   252     int res;
   212     *tick = __debug_tick;
   253     *tick = __debug_tick;
   213     /* Wait signal from PLC thread */
   254     /* Wait signal from PLC thread */
   214     if (PLC_state & PLC_STATE_DEBUG_FILE_OPENED) {
   255     res = read(WaitDebug_pipe_fd, &cmd, sizeof(cmd));
   215         res = read(WaitDebug_pipe_fd, &message, sizeof(char));
   256     if (res == sizeof(cmd) && cmd == DEBUG_PENDING_DATA)
   216         if (res == sizeof(char))
   257         return 0;
   217             return 0;
       
   218     }
       
   219     return -1;
   258     return -1;
   220 }
   259 }
   221 
   260 
   222 /* Called by PLC thread when debug_publish finished
   261 /* Called by PLC thread when debug_publish finished
   223  * This is supposed to unlock debugger thread in WaitDebugData*/
   262  * This is supposed to unlock debugger thread in WaitDebugData*/
   224 void InitiateDebugTransfer()
   263 void InitiateDebugTransfer()
   225 {
   264 {
   226     char message = 1;
   265     char msg = DEBUG_PENDING_DATA;
   227     /* remember tick */
   266     /* remember tick */
   228     __debug_tick = __tick;
   267     __debug_tick = __tick;
   229     /* signal debugger thread it can read data */
   268     /* signal debugger thread it can read data */
   230     if (PLC_state & PLC_STATE_DEBUG_PIPE_CREATED)
   269     rt_pipe_write(&WaitDebug_pipe, &msg, sizeof(msg), P_NORMAL);
   231         rt_pipe_write(&WaitDebug_pipe, &message, sizeof(char), P_NORMAL);
   270 }
   232 }
   271 
   233 
   272 int suspendDebug(int disable)
   234 void suspendDebug(void)
   273 {
   235 {
   274     char cmd = DEBUG_UNLOCK;
   236     __DEBUG = 0;
   275     while(AtomicCompareExchange(
   237     if (PLC_state & PLC_STATE_DEBUG_MUTEX_CREATED) {
   276             &debug_state,
   238         rt_task_shadow(&SuspendDebug_task, "SuspendDebug_task", 0, 0);
   277             DEBUG_FREE,
   239         /* Prevent PLC to enter debug code */
   278             DEBUG_BUSY) != DEBUG_FREE &&
   240         rt_mutex_acquire(&debug_mutex, TM_INFINITE);
   279             cmd == DEBUG_UNLOCK){
   241     }
   280        if(read(Debug_pipe_fd, &cmd, sizeof(cmd)) == sizeof(cmd)){
       
   281            return -1;
       
   282        }
       
   283     }
       
   284     __DEBUG = !disable;
       
   285     return 0;
   242 }
   286 }
   243 
   287 
   244 void resumeDebug(void)
   288 void resumeDebug(void)
   245 {
   289 {
   246     __DEBUG = 1;
   290     AtomicCompareExchange( &debug_state, DEBUG_BUSY, DEBUG_FREE);
   247     if (PLC_state & PLC_STATE_DEBUG_MUTEX_CREATED) {
   291 }
   248         rt_task_shadow(&ResumeDebug_task, "ResumeDebug_task", 0, 0);
   292 
   249         /* Let PLC enter debug code */
   293 #define PYTHON_PENDING_COMMAND 1
   250         rt_mutex_release(&debug_mutex);
   294 
   251     }
   295 #define PYTHON_FREE 0
   252 }
   296 #define PYTHON_BUSY 1
   253 
   297 static long python_state = PYTHON_FREE;
   254 /* from plc_python.c */
   298 
   255 int WaitPythonCommands(void)
   299 int WaitPythonCommands(void)
   256 {
   300 { 
       
   301     char cmd;
   257     /* Wait signal from PLC thread */
   302     /* Wait signal from PLC thread */
   258     if (PLC_state & PLC_STATE_PYTHON_WAIT_SEM_CREATED) {
   303     if(read(WaitPython_pipe_fd, &cmd, sizeof(cmd))==sizeof(cmd) && cmd==PYTHON_PENDING_COMMAND){
   259         rt_task_shadow(&WaitPythonCommand_task, "WaitPythonCommand_task", 0, 0);
   304         return 0;
   260         return rt_sem_p(&python_wait_sem, TM_INFINITE);
       
   261     }
   305     }
   262     return -1;
   306     return -1;
   263 }
   307 }
   264 
   308 
   265 /* Called by PLC thread on each new python command*/
   309 /* Called by PLC thread on each new python command*/
   266 void UnBlockPythonCommands(void)
   310 void UnBlockPythonCommands(void)
   267 {
   311 {
   268     /* signal debugger thread it can read data */
   312     char msg = PYTHON_PENDING_COMMAND;
   269     rt_sem_v(&python_wait_sem);
   313     rt_pipe_write(&WaitPython_pipe, &msg, sizeof(msg), P_NORMAL);
   270 }
   314 }
   271 
   315 
   272 int TryLockPython(void)
   316 int TryLockPython(void)
   273 {
   317 {
   274     return rt_mutex_acquire(&python_mutex, TM_NONBLOCK) == 0;
   318     return AtomicCompareExchange(
       
   319         &python_state,
       
   320         PYTHON_FREE,
       
   321         PYTHON_BUSY) == PYTHON_FREE;
       
   322 }
       
   323 
       
   324 #define UNLOCK_PYTHON 1
       
   325 void LockPython(void)
       
   326 {
       
   327     char cmd = UNLOCK_PYTHON;
       
   328     while(AtomicCompareExchange(
       
   329             &python_state,
       
   330             PYTHON_FREE,
       
   331             PYTHON_BUSY) != PYTHON_FREE &&
       
   332             cmd == UNLOCK_PYTHON){
       
   333        read(Python_pipe_fd, &cmd, sizeof(cmd));
       
   334     }
   275 }
   335 }
   276 
   336 
   277 void UnLockPython(void)
   337 void UnLockPython(void)
   278 {
   338 {
   279     if (PLC_state & PLC_STATE_PYTHON_MUTEX_CREATED) {
   339     if(AtomicCompareExchange(
   280         rt_task_shadow(&UnLockPython_task, "UnLockPython_task", 0, 0);
   340             &python_state,
   281         rt_mutex_release(&python_mutex);
   341             PYTHON_BUSY,
   282     }
   342             PYTHON_FREE) == PYTHON_BUSY){
   283 }
   343         if(rt_task_self()){/*is that the real time task ?*/
   284 
   344            char cmd = UNLOCK_PYTHON;
   285 void LockPython(void)
   345            rt_pipe_write(&Python_pipe, &cmd, sizeof(cmd), P_NORMAL);
   286 {
   346         }/* otherwise, no signaling from non real time */
   287     if (PLC_state & PLC_STATE_PYTHON_MUTEX_CREATED) {
   347     }    /* as plc does not wait for lock. */
   288         rt_task_shadow(&LockPython_task, "LockPython_task", 0, 0);
   348 }
   289         rt_mutex_acquire(&python_mutex, TM_INFINITE);
   349 
   290     }
       
   291 }