root/trunk/libffado/src/bebob/bebob_avdevice.cpp

Revision 1232, 20.1 kB (checked in by jwoithe, 15 years ago)

Add some #include directives seemingly required for newer versions of gcc.

Line 
1 /*
2  * Copyright (C) 2005-2008 by Daniel Wagner
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 2 of the License, or
12  * (at your option) version 3 of the License.
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
26 #include "bebob/bebob_avdevice.h"
27 #include "bebob/bebob_avdevice_subunit.h"
28 #include "bebob/bebob_mixer.h"
29
30 #include "bebob/focusrite/focusrite_saffire.h"
31 #include "bebob/focusrite/focusrite_saffirepro.h"
32 #include "bebob/terratec/terratec_device.h"
33 #include "bebob/mackie/onyxmixer.h"
34 #include "bebob/edirol/edirol_fa101.h"
35 #include "bebob/edirol/edirol_fa66.h"
36 #include "bebob/esi/quatafire610.h"
37
38 #include "libieee1394/configrom.h"
39 #include "libieee1394/ieee1394service.h"
40
41 #include "genericavc/avc_vendormodel.h"
42
43 #include "libavc/general/avc_plug_info.h"
44 #include "libavc/general/avc_extended_plug_info.h"
45 #include "libavc/general/avc_subunit_info.h"
46 #include "libavc/streamformat/avc_extended_stream_format.h"
47 #include "libutil/cmd_serialize.h"
48 #include "libavc/avc_definitions.h"
49
50 #include "debugmodule/debugmodule.h"
51
52 #include <iostream>
53 #include <sstream>
54 #include <unistd.h>
55 #include <stdlib.h>
56 #include <sys/stat.h>
57
58 using namespace AVC;
59
60 namespace BeBoB {
61
62 AvDevice::AvDevice( DeviceManager& d, std::auto_ptr< ConfigRom >( configRom ) )
63     : GenericAVC::AvDevice( d, configRom )
64     , m_Mixer ( 0 )
65 {
66     debugOutput( DEBUG_LEVEL_VERBOSE, "Created BeBoB::AvDevice (NodeID %d)\n",
67                  getConfigRom().getNodeId() );
68
69     // DM1500 based devices seem to upset the linux1394 stack when commands are
70     // sent too fast.
71     if (AVC::AVCCommand::getSleepAfterAVCCommand() < 200) {
72         AVC::AVCCommand::setSleepAfterAVCCommand( 200 );
73     }
74
75 }
76
77 AvDevice::~AvDevice()
78 {
79     destroyMixer();
80 }
81
82 bool
83 AvDevice::probe( ConfigRom& configRom )
84 {
85     unsigned int vendorId = configRom.getNodeVendorId();
86     unsigned int modelId = configRom.getModelId();
87
88     GenericAVC::VendorModel vendorModel( SHAREDIR "/ffado_driver_bebob.txt" );
89     if ( vendorModel.parse() ) {
90         return vendorModel.isPresent( vendorId, modelId );
91     }
92
93     return false;
94 }
95
96 FFADODevice *
97 AvDevice::createDevice(DeviceManager& d, std::auto_ptr<ConfigRom>( configRom ))
98 {
99     unsigned int vendorId = configRom->getNodeVendorId();
100     unsigned int modelId = configRom->getModelId();
101
102     switch (vendorId) {
103         case FW_VENDORID_MACKIE:
104             if (modelId == 0x00010065 ) {
105                 return new Mackie::OnyxMixerDevice(d, configRom);
106             }
107         case FW_VENDORID_EDIROL:
108             switch (modelId) {
109                 case 0x00010048:
110                     return new Edirol::EdirolFa101Device(d, configRom);
111                 case 0x00010049:
112                     return new Edirol::EdirolFa66Device(d, configRom);
113                 default:
114                     return new AvDevice(d, configRom);
115             }
116         case FW_VENDORID_ESI:
117             if (modelId == 0x00010064) {
118                 return new ESI::QuataFireDevice(d, configRom);
119             }
120             break;
121         case FW_VENDORID_TERRATEC:
122             switch(modelId) {
123                 case 0x00000003:
124                     return new Terratec::Phase88Device(d, configRom);
125                 default: // return a plain BeBoB device
126                     return new AvDevice(d, configRom);
127             }
128         case FW_VENDORID_FOCUSRITE:
129             switch(modelId) {
130                 case 0x00000003:
131                 case 0x00000006:
132                     return new Focusrite::SaffireProDevice(d, configRom);
133                 case 0x00000000:
134                     return new Focusrite::SaffireDevice(d, configRom);
135                 default: // return a plain BeBoB device
136                     return new AvDevice(d, configRom);
137            }
138         default:
139             return new AvDevice(d, configRom);
140     }
141     return NULL;
142 }
143
144 bool
145 AvDevice::discover()
146 {
147     unsigned int vendorId = getConfigRom().getNodeVendorId();
148     unsigned int modelId = getConfigRom().getModelId();
149
150     GenericAVC::VendorModel vendorModel( SHAREDIR "/ffado_driver_bebob.txt" );
151     if ( vendorModel.parse() ) {
152         m_model = vendorModel.find( vendorId, modelId );
153     }
154
155     if (GenericAVC::VendorModel::isValid(m_model)) {
156         debugOutput( DEBUG_LEVEL_VERBOSE, "found %s %s\n",
157                      m_model.vendor_name.c_str(),
158                      m_model.model_name.c_str());
159     } else return false;
160
161     if ( !Unit::discover() ) {
162         debugError( "Could not discover unit\n" );
163         return false;
164     }
165
166     if((getAudioSubunit( 0 ) == NULL)) {
167         debugError( "Unit doesn't have an Audio subunit.\n");
168         return false;
169     }
170     if((getMusicSubunit( 0 ) == NULL)) {
171         debugError( "Unit doesn't have a Music subunit.\n");
172         return false;
173     }
174
175     if(!buildMixer()) {
176         debugWarning("Could not build mixer\n");
177     }
178     return true;
179 }
180
181 bool
182 AvDevice::buildMixer()
183 {
184     debugOutput(DEBUG_LEVEL_VERBOSE, "Building a generic BeBoB mixer...\n");
185     // create a Mixer
186     // this removes the mixer if it already exists
187     // note: a mixer self-registers to it's parent
188     delete m_Mixer;
189
190     // create the mixer & register it
191     if(getAudioSubunit(0) == NULL) {
192         debugWarning("Could not find audio subunit, mixer not available.\n");
193         m_Mixer = NULL;
194     } else {
195         m_Mixer = new Mixer(*this);
196     }
197     if (m_Mixer) m_Mixer->setVerboseLevel(getDebugLevel());
198     return m_Mixer != NULL;
199 }
200
201 bool
202 AvDevice::destroyMixer()
203 {
204     delete m_Mixer;
205     return true;
206 }
207
208 bool
209 AvDevice::setSelectorFBValue(int id, int value) {
210     FunctionBlockCmd fbCmd( get1394Service(),
211                             FunctionBlockCmd::eFBT_Selector,
212                             id,
213                             FunctionBlockCmd::eCA_Current );
214     fbCmd.setNodeId( getNodeId() );
215     fbCmd.setSubunitId( 0x00 );
216     fbCmd.setCommandType( AVCCommand::eCT_Control );
217     fbCmd.m_pFBSelector->m_inputFbPlugNumber = (value & 0xFF);
218     fbCmd.setVerboseLevel( getDebugLevel() );
219
220     if ( !fbCmd.fire() ) {
221         debugError( "cmd failed\n" );
222         return false;
223     }
224
225 //     if ( getDebugLevel() >= DEBUG_LEVEL_NORMAL ) {
226 //         Util::CoutSerializer se;
227 //         fbCmd.serialize( se );
228 //     }
229 //     
230     if((fbCmd.getResponse() != AVCCommand::eR_Accepted)) {
231         debugWarning("fbCmd.getResponse() != AVCCommand::eR_Accepted\n");
232     }
233
234     return (fbCmd.getResponse() == AVCCommand::eR_Accepted);
235 }
236
237 int
238 AvDevice::getSelectorFBValue(int id) {
239
240     FunctionBlockCmd fbCmd( get1394Service(),
241                             FunctionBlockCmd::eFBT_Selector,
242                             id,
243                             FunctionBlockCmd::eCA_Current );
244     fbCmd.setNodeId( getNodeId() );
245     fbCmd.setSubunitId( 0x00 );
246     fbCmd.setCommandType( AVCCommand::eCT_Status );
247     fbCmd.m_pFBSelector->m_inputFbPlugNumber = 0xFF;
248     fbCmd.setVerboseLevel( getDebugLevel() );
249
250     if ( !fbCmd.fire() ) {
251         debugError( "cmd failed\n" );
252         return -1;
253     }
254    
255 //     if ( getDebugLevel() >= DEBUG_LEVEL_NORMAL ) {
256 //         Util::CoutSerializer se;
257 //         fbCmd.serialize( se );
258 //     }
259
260     if((fbCmd.getResponse() != AVCCommand::eR_Implemented)) {
261         debugWarning("fbCmd.getResponse() != AVCCommand::eR_Implemented\n");
262     }
263    
264     return fbCmd.m_pFBSelector->m_inputFbPlugNumber;
265 }
266
267 bool
268 AvDevice::setFeatureFBVolumeValue(int id, int channel, int v) {
269
270     FunctionBlockCmd fbCmd( get1394Service(),
271                             FunctionBlockCmd::eFBT_Feature,
272                             id,
273                             FunctionBlockCmd::eCA_Current );
274     fbCmd.setNodeId( getNodeId() );
275     fbCmd.setSubunitId( 0x00 );
276     fbCmd.setCommandType( AVCCommand::eCT_Control );
277     fbCmd.m_pFBFeature->m_audioChannelNumber = channel;
278     fbCmd.m_pFBFeature->m_controlSelector = FunctionBlockFeature::eCSE_Feature_Volume;
279     fbCmd.m_pFBFeature->m_pVolume->m_volume = v;
280     fbCmd.setVerboseLevel( getDebugLevel() );
281
282     if ( !fbCmd.fire() ) {
283         debugError( "cmd failed\n" );
284         return false;
285     }
286
287 //     if ( getDebugLevel() >= DEBUG_LEVEL_NORMAL ) {
288 //         Util::CoutSerializer se;
289 //         fbCmd.serialize( se );
290 //     }
291    
292     if((fbCmd.getResponse() != AVCCommand::eR_Accepted)) {
293         debugWarning("fbCmd.getResponse() != AVCCommand::eR_Accepted\n");
294     }
295
296     return (fbCmd.getResponse() == AVCCommand::eR_Accepted);
297 }
298
299 int
300 AvDevice::getFeatureFBVolumeValue(int id, int channel) {
301     FunctionBlockCmd fbCmd( get1394Service(),
302                             FunctionBlockCmd::eFBT_Feature,
303                             id,
304                             FunctionBlockCmd::eCA_Current );
305     fbCmd.setNodeId( getNodeId() );
306     fbCmd.setSubunitId( 0x00 );
307     fbCmd.setCommandType( AVCCommand::eCT_Status );
308     fbCmd.m_pFBFeature->m_audioChannelNumber = channel;
309     fbCmd.m_pFBFeature->m_controlSelector = FunctionBlockFeature::eCSE_Feature_Volume;
310     fbCmd.m_pFBFeature->m_pVolume->m_volume = 0;
311     fbCmd.setVerboseLevel( getDebugLevel() );
312
313     if ( !fbCmd.fire() ) {
314         debugError( "cmd failed\n" );
315         return 0;
316     }
317    
318 //     if ( getDebugLevel() >= DEBUG_LEVEL_NORMAL ) {
319 //         Util::CoutSerializer se;
320 //         fbCmd.serialize( se );
321 //     }
322
323     if((fbCmd.getResponse() != AVCCommand::eR_Implemented)) {
324         debugWarning("fbCmd.getResponse() != AVCCommand::eR_Implemented\n");
325     }
326    
327     int16_t volume=(int16_t)(fbCmd.m_pFBFeature->m_pVolume->m_volume);
328    
329     return volume;
330 }
331
332 void
333 AvDevice::showDevice()
334 {
335     debugOutput(DEBUG_LEVEL_NORMAL, "Device is a BeBoB device\n");
336     GenericAVC::AvDevice::showDevice();
337     flushDebugOutput();
338 }
339
340 void
341 AvDevice::setVerboseLevel(int l)
342 {
343     if (m_Mixer) m_Mixer->setVerboseLevel( l );
344     GenericAVC::AvDevice::setVerboseLevel( l );
345     debugOutput( DEBUG_LEVEL_VERBOSE, "Setting verbose level to %d...\n", l );
346 }
347
348 AVC::Subunit*
349 AvDevice::createSubunit(AVC::Unit& unit,
350                         AVC::ESubunitType type,
351                         AVC::subunit_t id )
352 {
353     AVC::Subunit* s=NULL;
354     switch (type) {
355         case eST_Audio:
356             s=new BeBoB::SubunitAudio(unit, id );
357             break;
358         case eST_Music:
359             s=new BeBoB::SubunitMusic(unit, id );
360             break;
361         default:
362             s=NULL;
363             break;
364     }
365     if(s) s->setVerboseLevel(getDebugLevel());
366     return s;
367 }
368
369
370 AVC::Plug *
371 AvDevice::createPlug( AVC::Unit* unit,
372                       AVC::Subunit* subunit,
373                       AVC::function_block_type_t functionBlockType,
374                       AVC::function_block_type_t functionBlockId,
375                       AVC::Plug::EPlugAddressType plugAddressType,
376                       AVC::Plug::EPlugDirection plugDirection,
377                       AVC::plug_id_t plugId )
378 {
379
380     Plug *p= new BeBoB::Plug( unit,
381                               subunit,
382                               functionBlockType,
383                               functionBlockId,
384                               plugAddressType,
385                               plugDirection,
386                               plugId );
387     if (p) p->setVerboseLevel(getDebugLevel());
388     return p;
389 }
390
391 bool
392 AvDevice::propagatePlugInfo() {
393     // we don't have to propagate since we discover things
394     // another way
395     debugOutput(DEBUG_LEVEL_VERBOSE, "Skip plug info propagation\n");
396     return true;
397 }
398
399
400 uint8_t
401 AvDevice::getConfigurationIdSampleRate()
402 {
403     ExtendedStreamFormatCmd extStreamFormatCmd( get1394Service() );
404     UnitPlugAddress unitPlugAddress( UnitPlugAddress::ePT_PCR, 0 );
405     extStreamFormatCmd.setPlugAddress( PlugAddress( PlugAddress::ePD_Input,
406                                                     PlugAddress::ePAM_Unit,
407                                                     unitPlugAddress ) );
408
409     extStreamFormatCmd.setNodeId( getNodeId() );
410     extStreamFormatCmd.setCommandType( AVCCommand::eCT_Status );
411     extStreamFormatCmd.setVerbose( getDebugLevel() );
412
413     if ( !extStreamFormatCmd.fire() ) {
414         debugError( "Stream format command failed\n" );
415         return false;
416     }
417
418     FormatInformation* formatInfo =
419         extStreamFormatCmd.getFormatInformation();
420     FormatInformationStreamsCompound* compoundStream
421         = dynamic_cast< FormatInformationStreamsCompound* > (
422             formatInfo->m_streams );
423     if ( compoundStream ) {
424         debugOutput(DEBUG_LEVEL_VERBOSE, "Sample rate 0x%02x\n",
425                     compoundStream->m_samplingFrequency );
426         return compoundStream->m_samplingFrequency;
427     }
428
429     debugError( "Could not retrieve sample rate\n" );
430     return 0;
431 }
432
433 uint8_t
434 AvDevice::getConfigurationIdNumberOfChannel( PlugAddress::EPlugDirection ePlugDirection )
435 {
436     ExtendedPlugInfoCmd extPlugInfoCmd( get1394Service() );
437     UnitPlugAddress unitPlugAddress( UnitPlugAddress::ePT_PCR,
438                                      getNodeId() );
439     extPlugInfoCmd.setPlugAddress( PlugAddress( ePlugDirection,
440                                                 PlugAddress::ePAM_Unit,
441                                                 unitPlugAddress ) );
442     extPlugInfoCmd.setNodeId( getNodeId() );
443     extPlugInfoCmd.setCommandType( AVCCommand::eCT_Status );
444     extPlugInfoCmd.setVerbose( getDebugLevel() );
445     ExtendedPlugInfoInfoType extendedPlugInfoInfoType(
446         ExtendedPlugInfoInfoType::eIT_NoOfChannels );
447     extendedPlugInfoInfoType.initialize();
448     extPlugInfoCmd.setInfoType( extendedPlugInfoInfoType );
449
450     if ( !extPlugInfoCmd.fire() ) {
451         debugError( "Number of channels command failed\n" );
452         return false;
453     }
454
455     ExtendedPlugInfoInfoType* infoType = extPlugInfoCmd.getInfoType();
456     if ( infoType
457          && infoType->m_plugNrOfChns )
458     {
459         debugOutput(DEBUG_LEVEL_VERBOSE, "Number of channels 0x%02x\n",
460                     infoType->m_plugNrOfChns->m_nrOfChannels );
461         return infoType->m_plugNrOfChns->m_nrOfChannels;
462     }
463
464     debugError( "Could not retrieve number of channels\n" );
465     return 0;
466 }
467
468 uint8_t
469 AvDevice::getConfigurationIdSyncMode()
470 {
471     SignalSourceCmd signalSourceCmd( get1394Service() );
472     SignalUnitAddress signalUnitAddr;
473     signalUnitAddr.m_plugId = 0x01;
474     signalSourceCmd.setSignalDestination( signalUnitAddr );
475     signalSourceCmd.setNodeId( getNodeId() );
476     signalSourceCmd.setSubunitType( eST_Unit  );
477     signalSourceCmd.setSubunitId( 0xff );
478     signalSourceCmd.setVerbose( getDebugLevel() );
479
480     signalSourceCmd.setCommandType( AVCCommand::eCT_Status );
481
482     if ( !signalSourceCmd.fire() ) {
483         debugError( "Signal source command failed\n" );
484         return false;
485     }
486
487     SignalAddress* pSyncPlugSignalAddress = signalSourceCmd.getSignalSource();
488     SignalSubunitAddress* pSyncPlugSubunitAddress
489         = dynamic_cast<SignalSubunitAddress*>( pSyncPlugSignalAddress );
490     if ( pSyncPlugSubunitAddress ) {
491         debugOutput(DEBUG_LEVEL_VERBOSE, "Sync mode 0x%02x\n",
492                     ( pSyncPlugSubunitAddress->m_subunitType << 3
493                       | pSyncPlugSubunitAddress->m_subunitId ) << 8
494                     | pSyncPlugSubunitAddress->m_plugId );
495
496         return ( pSyncPlugSubunitAddress->m_subunitType << 3
497                  | pSyncPlugSubunitAddress->m_subunitId ) << 8
498             | pSyncPlugSubunitAddress->m_plugId;
499     }
500
501     SignalUnitAddress* pSyncPlugUnitAddress
502       = dynamic_cast<SignalUnitAddress*>( pSyncPlugSignalAddress );
503     if ( pSyncPlugUnitAddress ) {
504         debugOutput(DEBUG_LEVEL_VERBOSE, "Sync mode 0x%02x\n",
505                       0xff << 8 | pSyncPlugUnitAddress->m_plugId );
506
507         return ( 0xff << 8 | pSyncPlugUnitAddress->m_plugId );
508     }
509
510     debugError( "Could not retrieve sync mode\n" );
511     return 0;
512 }
513
514 uint64_t
515 AvDevice::getConfigurationId()
516 {
517     // create a unique configuration id.
518     uint64_t id = 0;
519     id = getConfigurationIdSampleRate();
520     id |= ( getConfigurationIdNumberOfChannel( PlugAddress::ePD_Input )
521             + getConfigurationIdNumberOfChannel( PlugAddress::ePD_Output ) ) << 8;
522     id |= getConfigurationIdSyncMode() << 16;
523
524     return id;
525 }
526
527 bool
528 AvDevice::serialize( std::string basePath,
529                      Util::IOSerialize& ser ) const
530 {
531     bool result;
532     result  = GenericAVC::AvDevice::serialize( basePath, ser );
533     return result;
534 }
535
536 bool
537 AvDevice::deserialize( std::string basePath,
538                        Util::IODeserialize& deser )
539 {
540     bool result;
541     result  = GenericAVC::AvDevice::deserialize( basePath, deser );
542     return result;
543 }
544
545 std::string
546 AvDevice::getCachePath()
547 {
548     std::string cachePath;
549     char* pCachePath;
550
551     string path = CACHEDIR;
552     if ( path.size() && path[0] == '~' ) {
553         path.erase( 0, 1 ); // remove ~
554         path.insert( 0, getenv( "HOME" ) ); // prepend the home path
555     }
556
557     if ( asprintf( &pCachePath, "%s/cache/",  path.c_str() ) < 0 ) {
558         debugError( "Could not create path string for cache pool (trying '/var/cache/libffado' instead)\n" );
559         cachePath == "/var/cache/libffado/";
560     } else {
561         cachePath = pCachePath;
562         free( pCachePath );
563     }
564     return cachePath;
565 }
566
567 bool
568 AvDevice::loadFromCache()
569 {
570     std::string sDevicePath = getCachePath() + getConfigRom().getGuidString();
571
572     char* configId;
573     asprintf(&configId, "%016llx", getConfigurationId() );
574     if ( !configId ) {
575         debugError( "could not create id string\n" );
576         return false;
577     }
578
579     std::string sFileName = sDevicePath + "/" + configId + ".xml";
580     free( configId );
581     debugOutput( DEBUG_LEVEL_NORMAL, "filename %s\n", sFileName.c_str() );
582
583     struct stat buf;
584     if ( stat( sFileName.c_str(), &buf ) != 0 ) {
585         debugOutput( DEBUG_LEVEL_NORMAL,  "\"%s\" does not exist\n",  sFileName.c_str() );
586         return false;
587     } else {
588         if ( !S_ISREG( buf.st_mode ) ) {
589             debugOutput( DEBUG_LEVEL_NORMAL,  "\"%s\" is not a regular file\n",  sFileName.c_str() );
590             return false;
591         }
592     }
593
594     Util::XMLDeserialize deser( sFileName, getDebugLevel() );
595
596     if (!deser.isValid()) {
597         debugOutput( DEBUG_LEVEL_NORMAL, "cache not valid: %s\n",
598                      sFileName.c_str() );
599         return false;
600     }
601
602     bool result = deserialize( "", deser );
603     if ( result ) {
604         debugOutput( DEBUG_LEVEL_NORMAL, "could create valid bebob driver from %s\n",
605                      sFileName.c_str() );
606     }
607
608     if(result) {
609         buildMixer();
610     }
611
612     return result;
613 }
614
615 bool
616 AvDevice::saveCache()
617 {
618     // the path looks like this:
619     // PATH_TO_CACHE + GUID + CONFIGURATION_ID
620     string tmp_path = getCachePath() + getConfigRom().getGuidString();
621
622     // the following piece should do something like
623     // 'mkdir -p some/path/with/some/dirs/which/do/not/exist'
624     vector<string> tokens;
625     tokenize( tmp_path, tokens, "/" );
626     string path;
627     for ( vector<string>::const_iterator it = tokens.begin();
628           it != tokens.end();
629           ++it )
630     {
631         path +=  "/" + *it;
632
633         struct stat buf;
634         if ( stat( path.c_str(), &buf ) == 0 ) {
635             if ( !S_ISDIR( buf.st_mode ) ) {
636                 debugError( "\"%s\" is not a directory\n",  path.c_str() );
637                 return false;
638             }
639         } else {
640             if (  mkdir( path.c_str(), S_IRWXU | S_IRWXG ) != 0 ) {
641                 debugError( "Could not create \"%s\" directory\n", path.c_str() );
642                 return false;
643             }
644         }
645     }
646
647     // come up with an unique file name for the current settings
648     char* configId;
649     asprintf(&configId, "%016llx", BeBoB::AvDevice::getConfigurationId() );
650     if ( !configId ) {
651         debugError( "Could not create id string\n" );
652         return false;
653     }
654     string filename = path + "/" + configId + ".xml";
655     free( configId );
656     debugOutput( DEBUG_LEVEL_NORMAL, "filename %s\n", filename.c_str() );
657
658     Util::XMLSerialize ser( filename );
659     return serialize( "", ser );
660 }
661
662 } // end of namespace
Note: See TracBrowser for help on using the browser.