root/trunk/libffado/src/libieee1394/IsoHandlerManager.cpp

Revision 807, 21.0 kB (checked in by ppalmers, 13 years ago)

more reliability things

Line 
1 /*
2  * Copyright (C) 2005-2007 by Pieter Palmers
3  *
4  * This file is part of FFADO
5  * FFADO = Free Firewire (pro-)audio drivers for linux
6  *
7  * FFADO is based upon FreeBoB.
8  *
9  * This program is free software: you can redistribute it and/or modify
10  * it under the terms of the GNU General Public License as published by
11  * the Free Software Foundation, either version 3 of the License, or
12  * (at your option) any later version.
13  *
14  * This program is distributed in the hope that it will be useful,
15  * but WITHOUT ANY WARRANTY; without even the implied warranty of
16  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17  * GNU General Public License for more details.
18  *
19  * You should have received a copy of the GNU General Public License
20  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
21  *
22  */
23
24 #include "config.h"
25 #include "IsoHandlerManager.h"
26 #include "ieee1394service.h"
27 #include "IsoHandler.h"
28 #include "libstreaming/generic/StreamProcessor.h"
29
30 #include "libutil/Atomic.h"
31
32 #include "libutil/PosixThread.h"
33
34 #include <assert.h>
35
36 IMPL_DEBUG_MODULE( IsoHandlerManager, IsoHandlerManager, DEBUG_LEVEL_NORMAL );
37
38 using namespace Streaming;
39
40 IsoHandlerManager::IsoHandlerManager(Ieee1394Service& service)
41    : m_State(E_Created)
42    , m_service( service )
43    , m_realtime(false), m_priority(0)
44    , m_Thread ( NULL )
45 {}
46
47 IsoHandlerManager::IsoHandlerManager(Ieee1394Service& service, bool run_rt, int rt_prio)
48    : m_State(E_Created)
49    , m_service( service )
50    , m_realtime(run_rt), m_priority(rt_prio)
51    , m_Thread ( NULL )
52 {}
53
54 IsoHandlerManager::~IsoHandlerManager()
55 {
56     stopHandlers();
57     pruneHandlers();
58     if(m_IsoHandlers.size() > 0) {
59         debugError("Still some handlers in use\n");
60     }
61     if (m_Thread) {
62         m_Thread->Stop();
63         delete m_Thread;
64     }
65 }
66
67 bool
68 IsoHandlerManager::setThreadParameters(bool rt, int priority) {
69     debugOutput( DEBUG_LEVEL_VERBOSE, "(%p) switch to: (rt=%d, prio=%d)...\n", this, rt, priority);
70     if (priority > THREAD_MAX_RTPRIO) priority = THREAD_MAX_RTPRIO; // cap the priority
71     m_realtime = rt;
72     m_priority = priority;
73     bool result = true;
74     for ( IsoHandlerVectorIterator it = m_IsoHandlers.begin();
75         it != m_IsoHandlers.end();
76         ++it )
77     {
78         result &= (*it)->setThreadParameters(m_realtime, m_priority);
79     }
80
81     if (m_Thread) {
82         if (m_realtime) {
83             m_Thread->AcquireRealTime(m_priority);
84         } else {
85             m_Thread->DropRealTime();
86         }
87     }
88
89     return result;
90 }
91
92 /**
93  * Update the shadow variables. Should only be called from
94  * the iso handler iteration thread
95  */
96 void
97 IsoHandlerManager::updateShadowVars()
98 {
99     debugOutput( DEBUG_LEVEL_VERY_VERBOSE, "updating shadow vars...\n");
100     unsigned int i;
101     m_poll_nfds_shadow = m_IsoHandlers.size();
102     if(m_poll_nfds_shadow > ISOHANDLERMANAGER_MAX_ISO_HANDLERS_PER_PORT) {
103         debugWarning("Too much ISO Handlers in manager...\n");
104         m_poll_nfds_shadow = ISOHANDLERMANAGER_MAX_ISO_HANDLERS_PER_PORT;
105     }
106     for (i = 0; i < m_poll_nfds_shadow; i++) {
107         IsoHandler *h = m_IsoHandlers.at(i);
108         assert(h);
109         m_IsoHandler_map_shadow[i] = h;
110
111         m_poll_fds_shadow[i].fd = h->getFileDescriptor();
112         m_poll_fds_shadow[i].revents = 0;
113         if (h->isEnabled()) {
114             m_poll_fds_shadow[i].events = POLLIN;
115         } else {
116             m_poll_fds_shadow[i].events = 0;
117         }
118     }
119     debugOutput( DEBUG_LEVEL_VERY_VERBOSE, " updated shadow vars...\n");
120 }
121
122 bool
123 IsoHandlerManager::Init() {
124     debugOutput( DEBUG_LEVEL_VERBOSE, "%p: Init thread...\n", this);
125     bool result = true;
126     for ( IsoHandlerVectorIterator it = m_IsoHandlers.begin();
127         it != m_IsoHandlers.end();
128         ++it )
129     {
130         result &= (*it)->Init();
131     }
132     return result;
133 }
134
135 bool
136 IsoHandlerManager::Execute() {
137     int err;
138     unsigned int i;
139
140     unsigned int m_poll_timeout = 100;
141
142     updateShadowVars();
143     // bypass if no handlers are registered
144     if (m_poll_nfds_shadow == 0) {
145         debugOutput(DEBUG_LEVEL_VERY_VERBOSE, "bypass iterate since no handlers registered\n");
146         usleep(m_poll_timeout * 1000);
147         return true;
148     }
149
150     // Use a shadow map of the fd's such that the poll call is not in a critical section
151     uint64_t poll_enter = m_service.getCurrentTimeAsUsecs();
152     err = poll (m_poll_fds_shadow, m_poll_nfds_shadow, m_poll_timeout);
153     uint64_t poll_exit = m_service.getCurrentTimeAsUsecs();
154
155     if (err == -1) {
156         if (errno == EINTR) {
157             return true;
158         }
159         debugFatal("poll error: %s\n", strerror (errno));
160         return false;
161     }
162
163     int nb_rcv = 0;
164     int nb_xmit = 0;
165     uint64_t iter_enter = m_service.getCurrentTimeAsUsecs();
166     for (i = 0; i < m_poll_nfds_shadow; i++) {
167         if(m_poll_fds_shadow[i].revents) {
168             debugOutput(DEBUG_LEVEL_VERY_VERBOSE, "received events: %08X for (%p)\n",
169                 m_poll_fds_shadow[i].revents, m_IsoHandler_map_shadow[i]);
170         }
171         if (m_poll_fds_shadow[i].revents & POLLERR) {
172             debugWarning("error on fd for %d\n",i);
173         }
174
175         if (m_poll_fds_shadow[i].revents & POLLHUP) {
176             debugWarning("hangup on fd for %d\n",i);
177         }
178
179         if(m_poll_fds_shadow[i].revents & (POLLIN)) {
180             if (m_IsoHandler_map_shadow[i]->getType() == IsoHandler::eHT_Receive) {
181                 m_IsoHandler_map_shadow[i]->iterate();
182                 nb_rcv++;
183             } else {
184                 // only iterate the xmit handler if it makes sense
185                 if(m_IsoHandler_map_shadow[i]->tryWaitForClient()) {
186                     m_IsoHandler_map_shadow[i]->iterate();
187                     nb_xmit++;
188                 }
189             }
190         }
191     }
192     uint64_t iter_exit = m_service.getCurrentTimeAsUsecs();
193
194     debugOutput(DEBUG_LEVEL_VERY_VERBOSE, " poll took %6lldus, iterate took %6lldus, iterated (R: %2d, X: %2d) handlers\n",
195                 poll_exit-poll_enter, iter_exit-iter_enter,
196                 nb_rcv, nb_xmit);
197
198     return true;
199 }
200
201 bool IsoHandlerManager::init()
202 {
203     debugOutput( DEBUG_LEVEL_VERBOSE, "Initializing ISO manager %p...\n", this);
204     // check state
205     if(m_State != E_Created) {
206         debugError("Manager already initialized...\n");
207         return false;
208     }
209
210 #if ISOHANDLER_PER_HANDLER_THREAD
211     // the IsoHandlers will create their own thread.
212 #else
213     // create a thread to iterate our handlers
214     debugOutput( DEBUG_LEVEL_VERBOSE, "Start thread for %p...\n", this);
215     m_Thread = new Util::PosixThread(this, m_realtime, m_priority,
216                                      PTHREAD_CANCEL_DEFERRED);
217     if(!m_Thread) {
218         debugFatal("No thread\n");
219         return false;
220     }
221     if (m_Thread->Start() != 0) {
222         debugFatal("Could not start update thread\n");
223         return false;
224     }
225 #endif
226
227     m_State=E_Running;
228     return true;
229 }
230
231 bool
232 IsoHandlerManager::disable(IsoHandler *h) {
233     bool result;
234     int i=0;
235     debugOutput(DEBUG_LEVEL_VERY_VERBOSE, "Disable on IsoHandler %p\n", h);
236     for ( IsoHandlerVectorIterator it = m_IsoHandlers.begin();
237         it != m_IsoHandlers.end();
238         ++it )
239     {
240         if ((*it) == h) {
241             result = h->disable();
242             debugOutput(DEBUG_LEVEL_VERY_VERBOSE, " disabled\n");
243             return result;
244         }
245         i++;
246     }
247     debugError("Handler not found\n");
248     return false;
249 }
250
251 bool
252 IsoHandlerManager::enable(IsoHandler *h) {
253     bool result;
254     int i=0;
255     debugOutput(DEBUG_LEVEL_VERY_VERBOSE, "Enable on IsoHandler %p\n", h);
256     for ( IsoHandlerVectorIterator it = m_IsoHandlers.begin();
257         it != m_IsoHandlers.end();
258         ++it )
259     {
260         if ((*it) == h) {
261             result = h->enable();
262             debugOutput(DEBUG_LEVEL_VERY_VERBOSE, " enabled\n");
263             return result;
264         }
265         i++;
266     }
267     debugError("Handler not found\n");
268     return false;
269 }
270
271 bool IsoHandlerManager::registerHandler(IsoHandler *handler)
272 {
273     debugOutput( DEBUG_LEVEL_VERBOSE, "enter...\n");
274     assert(handler);
275     handler->setVerboseLevel(getDebugLevel());
276     m_IsoHandlers.push_back(handler);
277     updateShadowVars();
278     return true;
279 }
280
281 bool IsoHandlerManager::unregisterHandler(IsoHandler *handler)
282 {
283     debugOutput( DEBUG_LEVEL_VERBOSE, "enter...\n");
284     assert(handler);
285
286     for ( IsoHandlerVectorIterator it = m_IsoHandlers.begin();
287       it != m_IsoHandlers.end();
288       ++it )
289     {
290         if ( *it == handler ) {
291             m_IsoHandlers.erase(it);
292             updateShadowVars();
293             return true;
294         }
295     }
296     debugFatal("Could not find handler (%p)\n", handler);
297     return false; //not found
298 }
299
300 /**
301  * Registers an StreamProcessor with the IsoHandlerManager.
302  *
303  * If nescessary, an IsoHandler is created to handle this stream.
304  * Once an StreamProcessor is registered to the handler, it will be included
305  * in the ISO streaming cycle (i.e. receive/transmit of it will occur).
306  *
307  * @param stream the stream to register
308  * @return true if registration succeeds
309  *
310  * \todo : currently there is a one-to-one mapping
311  *        between streams and handlers, this is not ok for
312  *        multichannel receive
313  */
314 bool IsoHandlerManager::registerStream(StreamProcessor *stream)
315 {
316     debugOutput( DEBUG_LEVEL_VERBOSE, "Registering stream %p\n",stream);
317     assert(stream);
318
319     IsoHandler* h = NULL;
320
321     // make sure the stream isn't already attached to a handler
322     for ( IsoHandlerVectorIterator it = m_IsoHandlers.begin();
323       it != m_IsoHandlers.end();
324       ++it )
325     {
326         if((*it)->isStreamRegistered(stream)) {
327             debugError( "stream already registered!\n");
328             return false;
329         }
330     }
331
332     // clean up all handlers that aren't used
333     pruneHandlers();
334
335     // allocate a handler for this stream
336     if (stream->getType()==StreamProcessor::ePT_Receive) {
337         // setup the optimal parameters for the raw1394 ISO buffering
338         unsigned int packets_per_period = stream->getPacketsPerPeriod();
339         unsigned int max_packet_size = stream->getMaxPacketSize();
340         unsigned int page_size = getpagesize() - 2; // for one reason or another this is necessary
341
342         // Ensure we don't request a packet size bigger than the
343         // kernel-enforced maximum which is currently 1 page.
344         if (max_packet_size > page_size) {
345             debugError("max packet size (%u) > page size (%u)\n", max_packet_size, page_size);
346             return false;
347         }
348
349         unsigned int irq_interval = packets_per_period / MINIMUM_INTERRUPTS_PER_PERIOD;
350         if(irq_interval <= 0) irq_interval=1;
351        
352         // the receive buffer size doesn't matter for the latency,
353         // but it has a minimal value in order for libraw to operate correctly (300)
354         int buffers=400;
355
356         // create the actual handler
357         h = new IsoHandler(*this, IsoHandler::eHT_Receive,
358                            buffers, max_packet_size, irq_interval);
359
360         debugOutput( DEBUG_LEVEL_VERBOSE, " creating IsoRecvHandler\n");
361
362         if(!h) {
363             debugFatal("Could not create IsoRecvHandler\n");
364             return false;
365         }
366
367     } else if (stream->getType()==StreamProcessor::ePT_Transmit) {
368         // setup the optimal parameters for the raw1394 ISO buffering
369         unsigned int packets_per_period = stream->getPacketsPerPeriod();
370         unsigned int max_packet_size = stream->getMaxPacketSize();
371         unsigned int page_size = getpagesize();
372
373         // Ensure we don't request a packet size bigger than the
374         // kernel-enforced maximum which is currently 1 page.
375         if (max_packet_size > page_size) {
376             debugError("max packet size (%u) > page size (%u)\n", max_packet_size, page_size);
377             return false;
378         }
379
380         max_packet_size = page_size;
381         unsigned int irq_interval = packets_per_period / MINIMUM_INTERRUPTS_PER_PERIOD;
382         if(irq_interval <= 0) irq_interval=1;
383
384         // the SP specifies how many packets to ISO-buffer
385         int buffers = stream->getNbPacketsIsoXmitBuffer();
386
387         debugOutput( DEBUG_LEVEL_VERBOSE, " creating IsoXmitHandler\n");
388
389         // create the actual handler
390         h = new IsoHandler(*this, IsoHandler::eHT_Transmit,
391                            buffers, max_packet_size, irq_interval);
392
393         if(!h) {
394             debugFatal("Could not create IsoXmitHandler\n");
395             return false;
396         }
397     } else {
398         debugFatal("Bad stream type\n");
399         return false;
400     }
401
402     h->setVerboseLevel(getDebugLevel());
403
404     // init the handler
405     if(!h->init()) {
406         debugFatal("Could not initialize receive handler\n");
407         return false;
408     }
409
410     // set the handler's thread parameters
411     // receive handlers have lower priority than the client thread
412     // since they have ISO side buffering
413     // xmit handlers have higher priority since we want client side
414     // frames to be put into the ISO buffers ASAP
415     int thread_prio;
416     if (stream->getType()==StreamProcessor::ePT_Receive) {
417         thread_prio = m_priority - 1;
418         if (thread_prio < THREAD_MIN_RTPRIO) thread_prio = THREAD_MIN_RTPRIO;
419     } else {
420         thread_prio = m_priority + 1;
421         if (thread_prio > THREAD_MAX_RTPRIO) thread_prio = THREAD_MAX_RTPRIO;
422     }
423
424     if(!h->setThreadParameters(m_realtime, thread_prio)) {
425         debugFatal("Could not set handler thread parameters\n");
426         return false;
427     }
428
429     // register the stream with the handler
430     if(!h->registerStream(stream)) {
431         debugFatal("Could not register receive stream with handler\n");
432         return false;
433     }
434
435     // register the handler with the manager
436     if(!registerHandler(h)) {
437         debugFatal("Could not register receive handler with manager\n");
438         return false;
439     }
440     debugOutput( DEBUG_LEVEL_VERBOSE, " registered stream (%p) with handler (%p)\n", stream, h);
441
442     m_StreamProcessors.push_back(stream);
443     debugOutput( DEBUG_LEVEL_VERBOSE, " %d streams, %d handlers registered\n",
444                                       m_StreamProcessors.size(), m_IsoHandlers.size());
445     return true;
446 }
447
448 bool IsoHandlerManager::unregisterStream(StreamProcessor *stream)
449 {
450     debugOutput( DEBUG_LEVEL_VERBOSE, "Unregistering stream %p\n",stream);
451     assert(stream);
452
453     // make sure the stream isn't attached to a handler anymore
454     for ( IsoHandlerVectorIterator it = m_IsoHandlers.begin();
455       it != m_IsoHandlers.end();
456       ++it )
457     {
458         if((*it)->isStreamRegistered(stream)) {
459             if(!(*it)->unregisterStream(stream)) {
460                 debugOutput( DEBUG_LEVEL_VERBOSE, " could not unregister stream (%p) from handler (%p)...\n",stream,*it);
461                 return false;
462             }
463             debugOutput( DEBUG_LEVEL_VERBOSE, " unregistered stream (%p) from handler (%p)...\n",stream,*it);
464         }
465     }
466
467     // clean up all handlers that aren't used
468     pruneHandlers();
469
470     // remove the stream from the registered streams list
471     for ( StreamProcessorVectorIterator it = m_StreamProcessors.begin();
472       it != m_StreamProcessors.end();
473       ++it )
474     {
475         if ( *it == stream ) {
476             m_StreamProcessors.erase(it);
477             debugOutput( DEBUG_LEVEL_VERBOSE, " deleted stream (%p) from list...\n", *it);
478             return true;
479         }
480     }
481     return false; //not found
482 }
483
484 /**
485  * @brief unregister a handler from the manager
486  * @note called without the lock held.
487  */
488 void IsoHandlerManager::pruneHandlers() {
489     debugOutput( DEBUG_LEVEL_VERBOSE, "enter...\n");
490     IsoHandlerVector toUnregister;
491
492     // find all handlers that are not in use
493     for ( IsoHandlerVectorIterator it = m_IsoHandlers.begin();
494           it != m_IsoHandlers.end();
495           ++it )
496     {
497         if(!((*it)->inUse())) {
498             debugOutput( DEBUG_LEVEL_VERBOSE, " handler (%p) not in use\n",*it);
499             toUnregister.push_back(*it);
500         }
501     }
502     // delete them
503     for ( IsoHandlerVectorIterator it = toUnregister.begin();
504           it != toUnregister.end();
505           ++it )
506     {
507         unregisterHandler(*it);
508
509         debugOutput( DEBUG_LEVEL_VERBOSE, " deleting handler (%p)\n",*it);
510
511         // Now the handler's been unregistered it won't be reused
512         // again.  Therefore it really needs to be formally deleted
513         // to free up the raw1394 handle.  Otherwise things fall
514         // apart after several xrun recoveries as the system runs
515         // out of resources to support all the disused but still
516         // allocated raw1394 handles.  At least this is the current
517         // theory as to why we end up with "memory allocation"
518         // failures after several Xrun recoveries.
519         delete *it;
520     }
521 }
522
523 bool
524 IsoHandlerManager::stopHandlerForStream(Streaming::StreamProcessor *stream) {
525     // check state
526     if(m_State != E_Running) {
527         debugError("Incorrect state, expected E_Running, got %s\n", eHSToString(m_State));
528         return false;
529     }
530     for ( IsoHandlerVectorIterator it = m_IsoHandlers.begin();
531       it != m_IsoHandlers.end();
532       ++it )
533     {
534         if((*it)->isStreamRegistered(stream)) {
535             bool result;
536             debugOutput( DEBUG_LEVEL_VERBOSE, " stopping handler %p for stream %p\n", *it, stream);
537             result = (*it)->disable();
538             if(!result) {
539                 debugOutput( DEBUG_LEVEL_VERBOSE, " could not disable handler (%p)\n",*it);
540                 return false;
541             }
542             return true;
543         }
544     }
545     debugError("Stream %p has no attached handler\n", stream);
546     return false;
547 }
548
549 int
550 IsoHandlerManager::getPacketLatencyForStream(Streaming::StreamProcessor *stream) {
551     for ( IsoHandlerVectorIterator it = m_IsoHandlers.begin();
552       it != m_IsoHandlers.end();
553       ++it )
554     {
555         if((*it)->isStreamRegistered(stream)) {
556             return (*it)->getPacketLatency();
557         }
558     }
559     debugError("Stream %p has no attached handler\n", stream);
560     return 0;
561 }
562
563 void
564 IsoHandlerManager::flushHandlerForStream(Streaming::StreamProcessor *stream) {
565     for ( IsoHandlerVectorIterator it = m_IsoHandlers.begin();
566       it != m_IsoHandlers.end();
567       ++it )
568     {
569         if((*it)->isStreamRegistered(stream)) {
570             return (*it)->flush();
571         }
572     }
573     debugError("Stream %p has no attached handler\n", stream);
574     return;
575 }
576
577 bool
578 IsoHandlerManager::startHandlerForStream(Streaming::StreamProcessor *stream) {
579     return startHandlerForStream(stream, -1);
580 }
581
582 bool
583 IsoHandlerManager::startHandlerForStream(Streaming::StreamProcessor *stream, int cycle) {
584     // check state
585     if(m_State != E_Running) {
586         debugError("Incorrect state, expected E_Running, got %s\n", eHSToString(m_State));
587         return false;
588     }
589     for ( IsoHandlerVectorIterator it = m_IsoHandlers.begin();
590       it != m_IsoHandlers.end();
591       ++it )
592     {
593         if((*it)->isStreamRegistered(stream)) {
594             bool result;
595             debugOutput( DEBUG_LEVEL_VERBOSE, " starting handler %p for stream %p\n", *it, stream);
596             result = (*it)->enable(cycle);
597             if(!result) {
598                 debugOutput( DEBUG_LEVEL_VERBOSE, " could not enable handler (%p)\n",*it);
599                 return false;
600             }
601             return true;
602         }
603     }
604     debugError("Stream %p has no attached handler\n", stream);
605     return false;
606 }
607
608 bool IsoHandlerManager::stopHandlers() {
609     debugOutput( DEBUG_LEVEL_VERBOSE, "enter...\n");
610
611     // check state
612     if(m_State != E_Running) {
613         debugError("Incorrect state, expected E_Running, got %s\n", eHSToString(m_State));
614         return false;
615     }
616
617     bool retval=true;
618
619     for ( IsoHandlerVectorIterator it = m_IsoHandlers.begin();
620         it != m_IsoHandlers.end();
621         ++it )
622     {
623         debugOutput( DEBUG_LEVEL_VERBOSE, "Stopping handler (%p)\n",*it);
624         if(!(*it)->disable()){
625             debugOutput( DEBUG_LEVEL_VERBOSE, " could not stop handler (%p)\n",*it);
626             retval=false;
627         }
628     }
629
630     if (retval) {
631         m_State=E_Prepared;
632     } else {
633         m_State=E_Error;
634     }
635     return retval;
636 }
637
638 bool IsoHandlerManager::reset() {
639     debugOutput( DEBUG_LEVEL_VERBOSE, "enter...\n");
640     // check state
641     if(m_State == E_Error) {
642         debugFatal("Resetting from error condition not yet supported...\n");
643         return false;
644     }
645     // if not in an error condition, reset means stop the handlers
646     return stopHandlers();
647 }
648
649 void IsoHandlerManager::setVerboseLevel(int i) {
650     setDebugLevel(i);
651     // propagate the debug level
652     for ( IsoHandlerVectorIterator it = m_IsoHandlers.begin();
653           it != m_IsoHandlers.end();
654           ++it )
655     {
656         (*it)->setVerboseLevel(i);
657     }
658 }
659
660 void IsoHandlerManager::dumpInfo() {
661     int i=0;
662     debugOutputShort( DEBUG_LEVEL_NORMAL, "Dumping IsoHandlerManager Stream handler information...\n");
663     debugOutputShort( DEBUG_LEVEL_NORMAL, " State: %d\n",(int)m_State);
664
665     for ( IsoHandlerVectorIterator it = m_IsoHandlers.begin();
666           it != m_IsoHandlers.end();
667           ++it )
668     {
669         debugOutputShort( DEBUG_LEVEL_NORMAL, " IsoHandler %d (%p)\n",i++,*it);
670         (*it)->dumpInfo();
671     }
672 }
673
674 const char *
675 IsoHandlerManager::eHSToString(enum eHandlerStates s) {
676     switch (s) {
677         default: return "Invalid";
678         case E_Created: return "Created";
679         case E_Prepared: return "Prepared";
680         case E_Running: return "Running";
681         case E_Error: return "Error";
682     }
683 }
Note: See TracBrowser for help on using the browser.