3c3cdd015364ae08e410475c47ed237aa5c04c20
[SCSI2SD.git] / software / scsi2sd-util / ConfigUtil.cc
1 //      Copyright (C) 2014 Michael McMaster <michael@codesrc.com>
2 //
3 //      This file is part of SCSI2SD.
4 //
5 //      SCSI2SD is free software: you can redistribute it and/or modify
6 //      it under the terms of the GNU General Public License as published by
7 //      the Free Software Foundation, either version 3 of the License, or
8 //      (at your option) any later version.
9 //
10 //      SCSI2SD is distributed in the hope that it will be useful,
11 //      but WITHOUT ANY WARRANTY; without even the implied warranty of
12 //      MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13 //      GNU General Public License for more details.
14 //
15 //      You should have received a copy of the GNU General Public License
16 //      along with SCSI2SD.  If not, see <http://www.gnu.org/licenses/>.
17
18 #include "ConfigUtil.hh"
19
20 #include <limits>
21 #include <sstream>
22 #include <stdexcept>
23
24 #include <string.h>
25
26 #include <wx/wxprec.h>
27 #ifndef WX_PRECOMP
28 #include <wx/wx.h>
29 #endif
30 #include <wx/base64.h>
31 #include <wx/buffer.h>
32 #include <wx/xml/xml.h>
33
34
35 using namespace SCSI2SD;
36
37 namespace
38 {
39         // Endian conversion routines.
40         // The Cortex-M3 inside the Cypress PSoC 5LP is a
41         // little-endian device.
42
43         bool isHostLE()
44         {
45                 union
46                 {
47                         int i;
48                         char c[sizeof(int)];
49                 } x;
50                 x.i = 1;
51                 return (x.c[0] == 1);
52         }
53
54         uint16_t toLE16(uint16_t in)
55         {
56                 if (isHostLE())
57                 {
58                         return in;
59                 }
60                 else
61                 {
62                         return (in >> 8) | (in << 8);
63                 }
64         }
65         uint16_t fromLE16(uint16_t in)
66         {
67                 return toLE16(in);
68         }
69
70         uint32_t toLE32(uint32_t in)
71         {
72                 if (isHostLE())
73                 {
74                         return in;
75                 }
76                 else
77                 {
78                         return (in >> 24) |
79                                 ((in >> 8) & 0xff00) |
80                                 ((in << 8) & 0xff0000) |
81                                 (in << 24);
82                 }
83         }
84         uint32_t fromLE32(uint32_t in)
85         {
86                 return toLE32(in);
87         }
88
89         std::vector<uint8_t> getModePages(const TargetConfig& cfg)
90         {
91                 std::vector<uint8_t> result;
92                 int i = 0;
93                 while (i < sizeof(cfg.modePages) - 2)
94                 {
95                         int pageLen = cfg.modePages[i+1];
96                         if (pageLen == 0) break;
97                         std::copy(
98                                 &cfg.modePages[i],
99                                 &cfg.modePages[i+pageLen+2],
100                                 std::back_inserter(result));
101                         i += pageLen + 2;
102                 }
103                 return result;
104         }
105
106         std::vector<uint8_t> getVPDPages(const TargetConfig& cfg)
107         {
108                 std::vector<uint8_t> result;
109                 int i = 0;
110                 while (i < sizeof(cfg.vpd) - 4)
111                 {
112                         int pageLen = cfg.vpd[i+3];
113                         if (pageLen == 0) break;
114                         std::copy(
115                                 &cfg.vpd[i],
116                                 &cfg.vpd[i+pageLen+4],
117                                 std::back_inserter(result));
118                         i += pageLen + 4;
119                 }
120                 return result;
121         }
122 }
123
124 BoardConfig
125 ConfigUtil::DefaultBoardConfig()
126 {
127         BoardConfig config;
128         memset(&config, 0, sizeof(config));
129
130         memcpy(config.magic, "BCFG", 4);
131
132
133         // Default to maximum fail-safe options.
134         config.flags = 0;
135         config.selectionDelay = 255; // auto
136
137         return config;
138 }
139
140 TargetConfig
141 ConfigUtil::Default(size_t targetIdx)
142 {
143         TargetConfig config;
144         memset(&config, 0, sizeof(config));
145
146         config.scsiId = targetIdx;
147         if (targetIdx == 0)
148         {
149                 config.scsiId = config.scsiId | CONFIG_TARGET_ENABLED;
150         }
151         config.deviceType = CONFIG_FIXED;
152
153         // Default to maximum fail-safe options.
154         config.flagsDEPRECATED = 0;
155         config.deviceTypeModifier = 0;
156         config.sdSectorStart = 0;
157
158         // Default to 2GB. Many systems have trouble with > 2GB disks, and
159         // a few start to complain at 1GB.
160         config.scsiSectors = 4194303; // 2GB - 1 sector
161         config.bytesPerSector = 512;
162         config.sectorsPerTrack = 63;
163         config.headsPerCylinder = 255;
164         memcpy(config.vendor, " codesrc", 8);
165         memcpy(config.prodId, "         SCSI2SD", 16);
166         memcpy(config.revision, " 4.2", 4);
167         memcpy(config.serial, "1234567812345678", 16);
168
169         // Reserved fields, already set to 0
170         // config.reserved
171
172         // not supported yet.
173         // config.vpd
174
175         return config;
176 }
177
178
179 TargetConfig
180 ConfigUtil::fromBytes(const uint8_t* data)
181 {
182         TargetConfig result;
183         memcpy(&result, data, sizeof(TargetConfig));
184         result.sdSectorStart = toLE32(result.sdSectorStart);
185         result.scsiSectors = toLE32(result.scsiSectors);
186         result.bytesPerSector = toLE16(result.bytesPerSector);
187         result.sectorsPerTrack = toLE16(result.sectorsPerTrack);
188         result.headsPerCylinder = toLE16(result.headsPerCylinder);
189         return result;
190 }
191
192
193 std::vector<uint8_t>
194 ConfigUtil::toBytes(const TargetConfig& _config)
195 {
196         TargetConfig config(_config);
197         config.sdSectorStart = fromLE32(config.sdSectorStart);
198         config.scsiSectors = fromLE32(config.scsiSectors);
199         config.bytesPerSector = fromLE16(config.bytesPerSector);
200         config.sectorsPerTrack = fromLE16(config.sectorsPerTrack);
201         config.headsPerCylinder = fromLE16(config.headsPerCylinder);
202
203         const uint8_t* begin = reinterpret_cast<const uint8_t*>(&config);
204         return std::vector<uint8_t>(begin, begin + sizeof(config));
205 }
206
207 BoardConfig
208 ConfigUtil::boardConfigFromBytes(const uint8_t* data)
209 {
210         BoardConfig result;
211         memcpy(&result, data, sizeof(BoardConfig));
212
213         if (memcmp("BCFG", result.magic, 4))
214         {
215                 return DefaultBoardConfig();
216         }
217
218         return result;
219 }
220
221
222 std::vector<uint8_t>
223 ConfigUtil::boardConfigToBytes(const BoardConfig& _config)
224 {
225         BoardConfig config(_config);
226
227         memcpy(config.magic, "BCFG", 4);
228         const uint8_t* begin = reinterpret_cast<const uint8_t*>(&config);
229         return std::vector<uint8_t>(begin, begin + sizeof(config));
230 }
231
232 std::string
233 ConfigUtil::toXML(const TargetConfig& config)
234 {
235         std::stringstream s;
236         std::vector<uint8_t> modePages(getModePages(config));
237         std::vector<uint8_t> vpd(getVPDPages(config));
238
239         s <<
240                 "<SCSITarget id=\"" <<
241                         static_cast<int>(config.scsiId & CONFIG_TARGET_ID_BITS) << "\">\n" <<
242
243                 "       <enabled>" <<
244                         (config.scsiId & CONFIG_TARGET_ENABLED ? "true" : "false") <<
245                         "</enabled>\n" <<
246
247                 "\n" <<
248                 "       <!-- ********************************************************\n" <<
249                 "       Space separated list. Available options:\n" <<
250                 "       apple\t\tReturns Apple-specific mode pages\n" <<
251                 "       ********************************************************* -->\n" <<
252                 "       <quirks>" <<
253                         (config.quirks & CONFIG_QUIRKS_APPLE ? "apple" : "") <<
254                         "</quirks>\n" <<
255
256                 "\n\n" <<
257                 "       <!-- ********************************************************\n" <<
258                 "       0x0    Fixed hard drive.\n" <<
259                 "       0x1    Removable drive.\n" <<
260                 "       0x2    Optical drive  (ie. CD drive).\n" <<
261                 "       0x3    1.44MB Floppy Drive.\n" <<
262                 "       ********************************************************* -->\n" <<
263                 "       <deviceType>0x" <<
264                                 std::hex << static_cast<int>(config.deviceType) <<
265                         "</deviceType>\n" <<
266
267                 "\n\n" <<
268                 "       <!-- ********************************************************\n" <<
269                 "       Device type modifier is usually 0x00. Only change this if your\n" <<
270                 "       OS requires some special value.\n" <<
271                 "\n" <<
272                 "       0x4C    Data General Micropolis disk\n" <<
273                 "       ********************************************************* -->\n" <<
274                 "       <deviceTypeModifier>0x" <<
275                                 std::hex << static_cast<int>(config.deviceTypeModifier) <<
276                         "</deviceTypeModifier>\n" <<
277
278                 "\n\n" <<
279                 "       <!-- ********************************************************\n" <<
280                 "       SD card offset, as a sector number (always 512 bytes).\n" <<
281                 "       ********************************************************* -->\n" <<
282                 "       <sdSectorStart>" << std::dec << config.sdSectorStart << "</sdSectorStart>\n" <<
283                 "\n\n" <<
284                 "       <!-- ********************************************************\n" <<
285                 "       Drive geometry settings.\n" <<
286                 "       ********************************************************* -->\n" <<
287                 "\n"
288                 "       <scsiSectors>" << std::dec << config.scsiSectors << "</scsiSectors>\n" <<
289                 "       <bytesPerSector>" << std::dec << config.bytesPerSector << "</bytesPerSector>\n" <<
290                 "       <sectorsPerTrack>" << std::dec << config.sectorsPerTrack<< "</sectorsPerTrack>\n" <<
291                 "       <headsPerCylinder>" << std::dec << config.headsPerCylinder << "</headsPerCylinder>\n" <<
292                 "\n\n" <<
293                 "       <!-- ********************************************************\n" <<
294                 "       Drive identification information. The SCSI2SD doesn't\n" <<
295                 "       care what these are set to. Use these strings to trick a OS\n" <<
296                 "       thinking a specific hard drive model is attached.\n" <<
297                 "       ********************************************************* -->\n" <<
298                 "\n"
299                 "       <!-- 8 character vendor string -->\n" <<
300                 "       <!-- For Apple HD SC Setup/Drive Setup, use ' SEAGATE' -->\n" <<
301                 "       <vendor>" << std::string(config.vendor, 8) << "</vendor>\n" <<
302                 "\n" <<
303                 "       <!-- 16 character produce identifier -->\n" <<
304                 "       <!-- For Apple HD SC Setup/Drive Setup, use '          ST225N' -->\n" <<
305                 "       <prodId>" << std::string(config.prodId, 16) << "</prodId>\n" <<
306                 "\n" <<
307                 "       <!-- 4 character product revision number -->\n" <<
308                 "       <!-- For Apple HD SC Setup/Drive Setup, use '1.0 ' -->\n" <<
309                 "       <revision>" << std::string(config.revision, 4) << "</revision>\n" <<
310                 "\n" <<
311                 "       <!-- 16 character serial number -->\n" <<
312                 "       <serial>" << std::string(config.serial, 16) << "</serial>\n" <<
313                 "\n" <<
314                 "       <!-- Custom mode pages, base64 encoded, up to 1024 bytes.-->\n" <<
315                 "       <modePages>\n" <<
316                         (modePages.size() == 0 ? "" :
317                                 wxBase64Encode(&modePages[0], modePages.size())) <<
318                                 "\n" <<
319                 "       </modePages>\n" <<
320                 "\n" <<
321                 "       <!-- Custom inquiry VPD pages, base64 encoded, up to 1024 bytes.-->\n" <<
322                 "       <vpd>\n" <<
323                         (vpd.size() == 0 ? "" :
324                                 wxBase64Encode(&vpd[0], vpd.size())) <<
325                                 "\n" <<
326                 "       </vpd>\n" <<
327                 "</SCSITarget>\n";
328
329         return s.str();
330 }
331
332 std::string
333 ConfigUtil::toXML(const BoardConfig& config)
334 {
335         std::stringstream s;
336
337         s << "<BoardConfig>\n" <<
338
339                 "       <unitAttention>" <<
340                         (config.flags & CONFIG_ENABLE_UNIT_ATTENTION ? "true" : "false") <<
341                         "</unitAttention>\n" <<
342
343                 "       <parity>" <<
344                         (config.flags & CONFIG_ENABLE_PARITY ? "true" : "false") <<
345                         "</parity>\n" <<
346
347                 "       <!-- ********************************************************\n" <<
348                 "       Only set to true when using with a fast SCSI2 host\n " <<
349                 "       controller. This can cause problems with older/slower\n" <<
350                 "       hardware.\n" <<
351                 "       ********************************************************* -->\n" <<
352                 "       <enableScsi2>" <<
353                         (config.flags & CONFIG_ENABLE_SCSI2 ? "true" : "false") <<
354                         "</enableScsi2>\n" <<
355
356                 "       <!-- ********************************************************\n" <<
357                 "       Setting to 'true' will result in increased performance at the\n" <<
358                 "       cost of lower noise immunity.\n" <<
359                 "       Only set to true when using short cables with only 1 or two\n" <<
360                 "       devices. This should remain off when using external SCSI1 DB25\n" <<
361                 "       cables.\n" <<
362                 "       ********************************************************* -->\n" <<
363                 "       <disableGlitchFilter>" <<
364                         (config.flags & CONFIG_DISABLE_GLITCH ? "true" : "false") <<
365                         "</disableGlitchFilter>\n" <<
366
367                 "       <enableCache>" <<
368                         (config.flags & CONFIG_ENABLE_CACHE ? "true" : "false") <<
369                         "</enableCache>\n" <<
370
371                 "       <enableDisconnect>" <<
372                         (config.flags & CONFIG_ENABLE_DISCONNECT ? "true" : "false") <<
373                         "</enableDisconnect>\n" <<
374
375                 "       <!-- ********************************************************\n" <<
376                 "       Respond to very short duration selection attempts. This supports\n" <<
377                 "       non-standard hardware, but is generally safe to enable.\n" <<
378                 "       Required for Philips P2000C.\n" <<
379                 "       ********************************************************* -->\n" <<
380                 "       <selLatch>" <<
381                         (config.flags & CONFIG_ENABLE_SEL_LATCH? "true" : "false") <<
382                         "</selLatch>\n" <<
383
384
385                 "       <!-- ********************************************************\n" <<
386                 "       Convert luns to IDs. The unit must already be configured to respond\n" <<
387                 "       on the ID. Allows dual drives to be accessed from a \n" <<
388                 "       XEBEC S1410 SASI bridge.\n" <<
389                 "       eg. Configured for dual drives as IDs 0 and 1, but the XEBEC will\n" <<
390                 "       access the second disk as ID0, lun 1.\n" <<
391                 "       See ttp://bitsavers.trailing-edge.com/pdf/xebec/104524C_S1410Man_Aug83.pdf\n" <<
392                 "       ********************************************************* -->\n" <<
393                 "       <mapLunsToIds>" <<
394                         (config.flags & CONFIG_MAP_LUNS_TO_IDS ? "true" : "false") <<
395                         "</mapLunsToIds>\n" <<
396                 "</BoardConfig>\n";
397
398         return s.str();
399 }
400
401
402 static uint64_t parseInt(wxXmlNode* node, uint64_t limit)
403 {
404         std::string str(node->GetNodeContent().mb_str());
405         if (str.empty())
406         {
407                 throw std::runtime_error("Empty " + node->GetName());
408         }
409
410         std::stringstream s;
411         if (str.find("0x") == 0)
412         {
413                 s << std::hex << str.substr(2);
414         }
415         else
416         {
417                 s << str;
418         }
419
420         uint64_t result;
421         s >> result;
422         if (!s)
423         {
424                 throw std::runtime_error("Invalid value for " + node->GetName());
425         }
426
427         if (result > limit)
428         {
429                 std::stringstream msg;
430                 msg << "Invalid value for " << node->GetName() <<
431                         " (max=" << limit << ")";
432                 throw std::runtime_error(msg.str());
433         }
434         return result;
435 }
436
437 static TargetConfig
438 parseTarget(wxXmlNode* node)
439 {
440         int id;
441         {
442                 std::stringstream s;
443                 s << node->GetAttribute("id", "7");
444                 s >> id;
445                 if (!s) throw std::runtime_error("Could not parse SCSITarget id attr");
446         }
447         TargetConfig result = ConfigUtil::Default(id & 0x7);
448
449         wxXmlNode *child = node->GetChildren();
450         while (child)
451         {
452                 if (child->GetName() == "enabled")
453                 {
454                         std::string s(child->GetNodeContent().mb_str());
455                         if (s == "true")
456                         {
457                                 result.scsiId |= CONFIG_TARGET_ENABLED;
458                         }
459                         else
460                         {
461                                 result.scsiId = result.scsiId & ~CONFIG_TARGET_ENABLED;
462                         }
463                 }
464                 else if (child->GetName() == "quirks")
465                 {
466                         std::stringstream s(std::string(child->GetNodeContent().mb_str()));
467                         std::string quirk;
468                         while (s >> quirk)
469                         {
470                                 if (quirk == "apple")
471                                 {
472                                         result.quirks |= CONFIG_QUIRKS_APPLE;
473                                 }
474                         }
475                 }
476                 else if (child->GetName() == "deviceType")
477                 {
478                         result.deviceType = parseInt(child, 0xFF);
479                 }
480                 else if (child->GetName() == "deviceTypeModifier")
481                 {
482                         result.deviceTypeModifier = parseInt(child, 0xFF);
483                 }
484                 else if (child->GetName() == "sdSectorStart")
485                 {
486                         result.sdSectorStart = parseInt(child, 0xFFFFFFFF);
487                 }
488                 else if (child->GetName() == "scsiSectors")
489                 {
490                         result.scsiSectors = parseInt(child, 0xFFFFFFFF);
491                 }
492                 else if (child->GetName() == "bytesPerSector")
493                 {
494                         result.bytesPerSector = parseInt(child, 8192);
495                 }
496                 else if (child->GetName() == "sectorsPerTrack")
497                 {
498                         result.sectorsPerTrack = parseInt(child, 255);
499                 }
500                 else if (child->GetName() == "headsPerCylinder")
501                 {
502                         result.headsPerCylinder = parseInt(child, 255);
503                 }
504                 else if (child->GetName() == "vendor")
505                 {
506                         std::string s(child->GetNodeContent().mb_str());
507                         s = s.substr(0, sizeof(result.vendor));
508                         memset(result.vendor, ' ', sizeof(result.vendor));
509                         memcpy(result.vendor, s.c_str(), s.size());
510                 }
511                 else if (child->GetName() == "prodId")
512                 {
513                         std::string s(child->GetNodeContent().mb_str());
514                         s = s.substr(0, sizeof(result.prodId));
515                         memset(result.prodId, ' ', sizeof(result.prodId));
516                         memcpy(result.prodId, s.c_str(), s.size());
517                 }
518                 else if (child->GetName() == "revision")
519                 {
520                         std::string s(child->GetNodeContent().mb_str());
521                         s = s.substr(0, sizeof(result.revision));
522                         memset(result.revision, ' ', sizeof(result.revision));
523                         memcpy(result.revision, s.c_str(), s.size());
524                 }
525                 else if (child->GetName() == "serial")
526                 {
527                         std::string s(child->GetNodeContent().mb_str());
528                         s = s.substr(0, sizeof(result.serial));
529                         memset(result.serial, ' ', sizeof(result.serial));
530                         memcpy(result.serial, s.c_str(), s.size());
531                 }
532                 else if (child->GetName() == "modePages")
533                 {
534                         wxMemoryBuffer buf =
535                                 wxBase64Decode(child->GetNodeContent(), wxBase64DecodeMode_SkipWS);
536                         size_t len = std::min(buf.GetDataLen(), sizeof(result.modePages));
537                         memcpy(result.modePages, buf.GetData(), len);
538                 }
539                 else if (child->GetName() == "vpd")
540                 {
541                         wxMemoryBuffer buf =
542                                 wxBase64Decode(child->GetNodeContent(), wxBase64DecodeMode_SkipWS);
543                         size_t len = std::min(buf.GetDataLen(), sizeof(result.vpd));
544                         memcpy(result.vpd, buf.GetData(), len);
545                 }
546
547
548
549                 child = child->GetNext();
550         }
551         return result;
552 }
553
554 static BoardConfig
555 parseBoardConfig(wxXmlNode* node)
556 {
557         BoardConfig result = ConfigUtil::DefaultBoardConfig();
558
559         wxXmlNode *child = node->GetChildren();
560         while (child)
561         {
562                 if (child->GetName() == "unitAttention")
563                 {
564                         std::string s(child->GetNodeContent().mb_str());
565                         if (s == "true")
566                         {
567                                 result.flags |= CONFIG_ENABLE_UNIT_ATTENTION;
568                         }
569                         else
570                         {
571                                 result.flags = result.flags & ~CONFIG_ENABLE_UNIT_ATTENTION;
572                         }
573                 }
574                 else if (child->GetName() == "parity")
575                 {
576                         std::string s(child->GetNodeContent().mb_str());
577                         if (s == "true")
578                         {
579                                 result.flags |= CONFIG_ENABLE_PARITY;
580                         }
581                         else
582                         {
583                                 result.flags = result.flags & ~CONFIG_ENABLE_PARITY;
584                         }
585                 }
586                 else if (child->GetName() == "enableScsi2")
587                 {
588                         std::string s(child->GetNodeContent().mb_str());
589                         if (s == "true")
590                         {
591                                 result.flags |= CONFIG_ENABLE_SCSI2;
592                         }
593                         else
594                         {
595                                 result.flags = result.flags & ~CONFIG_ENABLE_SCSI2;
596                         }
597                 }
598                 else if (child->GetName() == "disableGlitchFilter")
599                 {
600                         std::string s(child->GetNodeContent().mb_str());
601                         if (s == "true")
602                         {
603                                 result.flags |= CONFIG_DISABLE_GLITCH;
604                         }
605                         else
606                         {
607                                 result.flags = result.flags & ~CONFIG_DISABLE_GLITCH;
608                         }
609                 }
610                 else if (child->GetName() == "enableCache")
611                 {
612                         std::string s(child->GetNodeContent().mb_str());
613                         if (s == "true")
614                         {
615                                 result.flags |= CONFIG_ENABLE_CACHE;
616                         }
617                         else
618                         {
619                                 result.flags = result.flags & ~CONFIG_ENABLE_CACHE;
620                         }
621                 }
622                 else if (child->GetName() == "enableDisconnect")
623                 {
624                         std::string s(child->GetNodeContent().mb_str());
625                         if (s == "true")
626                         {
627                                 result.flags |= CONFIG_ENABLE_DISCONNECT;
628                         }
629                         else
630                         {
631                                 result.flags = result.flags & ~CONFIG_ENABLE_DISCONNECT;
632                         }
633                 }
634                 else if (child->GetName() == "selLatch")
635                 {
636                         std::string s(child->GetNodeContent().mb_str());
637                         if (s == "true")
638                         {
639                                 result.flags |= CONFIG_ENABLE_SEL_LATCH;
640                         }
641                         else
642                         {
643                                 result.flags = result.flags & ~CONFIG_ENABLE_SEL_LATCH;
644                         }
645                 }
646                 else if (child->GetName() == "mapLunsToIds")
647                 {
648                         std::string s(child->GetNodeContent().mb_str());
649                         if (s == "true")
650                         {
651                                 result.flags |= CONFIG_MAP_LUNS_TO_IDS;
652                         }
653                         else
654                         {
655                                 result.flags = result.flags & ~CONFIG_MAP_LUNS_TO_IDS;
656                         }
657                 }
658                 child = child->GetNext();
659         }
660         return result;
661 }
662
663
664 std::pair<BoardConfig, std::vector<TargetConfig>>
665 ConfigUtil::fromXML(const std::string& filename)
666 {
667         wxXmlDocument doc;
668         if (!doc.Load(filename))
669         {
670                 throw std::runtime_error("Could not load XML file");
671         }
672
673         // start processing the XML file
674         if (doc.GetRoot()->GetName() != "SCSI2SD")
675         {
676                 throw std::runtime_error("Invalid root node, expected <SCSI2SD>");
677         }
678
679         BoardConfig boardConfig = DefaultBoardConfig();
680         int boardConfigFound = 0;
681
682         std::vector<TargetConfig> targets;
683         wxXmlNode *child = doc.GetRoot()->GetChildren();
684         while (child)
685         {
686                 if (child->GetName() == "SCSITarget")
687                 {
688                         targets.push_back(parseTarget(child));
689                 }
690                 else if (child->GetName() == "BoardConfig")
691                 {
692                         boardConfig = parseBoardConfig(child);
693                         boardConfigFound = 1;
694                 }
695                 child = child->GetNext();
696         }
697
698         if (!boardConfigFound && targets.size() > 0)
699         {
700                 boardConfig.flags = targets[0].flagsDEPRECATED;
701         }
702         return make_pair(boardConfig, targets);
703 }
704