Support custom mode pages
[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
107 BoardConfig
108 ConfigUtil::DefaultBoardConfig()
109 {
110         BoardConfig config;
111         memset(&config, 0, sizeof(config));
112
113         memcpy(config.magic, "BCFG", 4);
114
115
116         // Default to maximum fail-safe options.
117         config.flags = 0;
118         config.selectionDelay = 255; // auto
119
120         return config;
121 }
122
123 TargetConfig
124 ConfigUtil::Default(size_t targetIdx)
125 {
126         TargetConfig config;
127         memset(&config, 0, sizeof(config));
128
129         config.scsiId = targetIdx;
130         if (targetIdx == 0)
131         {
132                 config.scsiId = config.scsiId | CONFIG_TARGET_ENABLED;
133         }
134         config.deviceType = CONFIG_FIXED;
135
136         // Default to maximum fail-safe options.
137         config.flagsDEPRECATED = 0;
138         config.deviceTypeModifier = 0;
139         config.sdSectorStart = 0;
140
141         // Default to 2GB. Many systems have trouble with > 2GB disks, and
142         // a few start to complain at 1GB.
143         config.scsiSectors = 4194303; // 2GB - 1 sector
144         config.bytesPerSector = 512;
145         config.sectorsPerTrack = 63;
146         config.headsPerCylinder = 255;
147         memcpy(config.vendor, " codesrc", 8);
148         memcpy(config.prodId, "         SCSI2SD", 16);
149         memcpy(config.revision, " 4.2", 4);
150         memcpy(config.serial, "1234567812345678", 16);
151
152         // Reserved fields, already set to 0
153         // config.reserved
154
155         // not supported yet.
156         // config.vpd
157
158         return config;
159 }
160
161
162 TargetConfig
163 ConfigUtil::fromBytes(const uint8_t* data)
164 {
165         TargetConfig result;
166         memcpy(&result, data, sizeof(TargetConfig));
167         result.sdSectorStart = toLE32(result.sdSectorStart);
168         result.scsiSectors = toLE32(result.scsiSectors);
169         result.bytesPerSector = toLE16(result.bytesPerSector);
170         result.sectorsPerTrack = toLE16(result.sectorsPerTrack);
171         result.headsPerCylinder = toLE16(result.headsPerCylinder);
172         return result;
173 }
174
175
176 std::vector<uint8_t>
177 ConfigUtil::toBytes(const TargetConfig& _config)
178 {
179         TargetConfig config(_config);
180         config.sdSectorStart = fromLE32(config.sdSectorStart);
181         config.scsiSectors = fromLE32(config.scsiSectors);
182         config.bytesPerSector = fromLE16(config.bytesPerSector);
183         config.sectorsPerTrack = fromLE16(config.sectorsPerTrack);
184         config.headsPerCylinder = fromLE16(config.headsPerCylinder);
185
186         const uint8_t* begin = reinterpret_cast<const uint8_t*>(&config);
187         return std::vector<uint8_t>(begin, begin + sizeof(config));
188 }
189
190 BoardConfig
191 ConfigUtil::boardConfigFromBytes(const uint8_t* data)
192 {
193         BoardConfig result;
194         memcpy(&result, data, sizeof(BoardConfig));
195
196         if (memcmp("BCFG", result.magic, 4))
197         {
198                 return DefaultBoardConfig();
199         }
200
201         return result;
202 }
203
204
205 std::vector<uint8_t>
206 ConfigUtil::boardConfigToBytes(const BoardConfig& _config)
207 {
208         BoardConfig config(_config);
209
210         memcpy(config.magic, "BCFG", 4);
211         const uint8_t* begin = reinterpret_cast<const uint8_t*>(&config);
212         return std::vector<uint8_t>(begin, begin + sizeof(config));
213 }
214
215 std::string
216 ConfigUtil::toXML(const TargetConfig& config)
217 {
218         std::stringstream s;
219         std::vector<uint8_t> modePages(getModePages(config));
220
221         s <<
222                 "<SCSITarget id=\"" <<
223                         static_cast<int>(config.scsiId & CONFIG_TARGET_ID_BITS) << "\">\n" <<
224
225                 "       <enabled>" <<
226                         (config.scsiId & CONFIG_TARGET_ENABLED ? "true" : "false") <<
227                         "</enabled>\n" <<
228
229                 "\n" <<
230                 "       <!-- ********************************************************\n" <<
231                 "       Space separated list. Available options:\n" <<
232                 "       apple\t\tReturns Apple-specific mode pages\n" <<
233                 "       ********************************************************* -->\n" <<
234                 "       <quirks>" <<
235                         (config.quirks & CONFIG_QUIRKS_APPLE ? "apple" : "") <<
236                         "</quirks>\n" <<
237
238                 "\n\n" <<
239                 "       <!-- ********************************************************\n" <<
240                 "       0x0    Fixed hard drive.\n" <<
241                 "       0x1    Removable drive.\n" <<
242                 "       0x2    Optical drive  (ie. CD drive).\n" <<
243                 "       0x3    1.44MB Floppy Drive.\n" <<
244                 "       ********************************************************* -->\n" <<
245                 "       <deviceType>0x" <<
246                                 std::hex << static_cast<int>(config.deviceType) <<
247                         "</deviceType>\n" <<
248
249                 "\n\n" <<
250                 "       <!-- ********************************************************\n" <<
251                 "       Device type modifier is usually 0x00. Only change this if your\n" <<
252                 "       OS requires some special value.\n" <<
253                 "\n" <<
254                 "       0x4C    Data General Micropolis disk\n" <<
255                 "       ********************************************************* -->\n" <<
256                 "       <deviceTypeModifier>0x" <<
257                                 std::hex << static_cast<int>(config.deviceTypeModifier) <<
258                         "</deviceTypeModifier>\n" <<
259
260                 "\n\n" <<
261                 "       <!-- ********************************************************\n" <<
262                 "       SD card offset, as a sector number (always 512 bytes).\n" <<
263                 "       ********************************************************* -->\n" <<
264                 "       <sdSectorStart>" << std::dec << config.sdSectorStart << "</sdSectorStart>\n" <<
265                 "\n\n" <<
266                 "       <!-- ********************************************************\n" <<
267                 "       Drive geometry settings.\n" <<
268                 "       ********************************************************* -->\n" <<
269                 "\n"
270                 "       <scsiSectors>" << std::dec << config.scsiSectors << "</scsiSectors>\n" <<
271                 "       <bytesPerSector>" << std::dec << config.bytesPerSector << "</bytesPerSector>\n" <<
272                 "       <sectorsPerTrack>" << std::dec << config.sectorsPerTrack<< "</sectorsPerTrack>\n" <<
273                 "       <headsPerCylinder>" << std::dec << config.headsPerCylinder << "</headsPerCylinder>\n" <<
274                 "\n\n" <<
275                 "       <!-- ********************************************************\n" <<
276                 "       Drive identification information. The SCSI2SD doesn't\n" <<
277                 "       care what these are set to. Use these strings to trick a OS\n" <<
278                 "       thinking a specific hard drive model is attached.\n" <<
279                 "       ********************************************************* -->\n" <<
280                 "\n"
281                 "       <!-- 8 character vendor string -->\n" <<
282                 "       <!-- For Apple HD SC Setup/Drive Setup, use ' SEAGATE' -->\n" <<
283                 "       <vendor>" << std::string(config.vendor, 8) << "</vendor>\n" <<
284                 "\n" <<
285                 "       <!-- 16 character produce identifier -->\n" <<
286                 "       <!-- For Apple HD SC Setup/Drive Setup, use '          ST225N' -->\n" <<
287                 "       <prodId>" << std::string(config.prodId, 16) << "</prodId>\n" <<
288                 "\n" <<
289                 "       <!-- 4 character product revision number -->\n" <<
290                 "       <!-- For Apple HD SC Setup/Drive Setup, use '1.0 ' -->\n" <<
291                 "       <revision>" << std::string(config.revision, 4) << "</revision>\n" <<
292                 "\n" <<
293                 "       <!-- 16 character serial number -->\n" <<
294                 "       <serial>" << std::string(config.serial, 16) << "</serial>\n" <<
295                 "\n" <<
296                 "       <!-- Custom mode pages, base64 encoded, up to 1024 bytes.-->\n" <<
297                 "       <modePages>\n" <<
298                         (modePages.size() == 0 ? "" :
299                                 wxBase64Encode(&modePages[0], modePages.size())) <<
300                                 "\n" <<
301                 "       </modePages>\n" <<
302                 "</SCSITarget>\n";
303
304         return s.str();
305 }
306
307 std::string
308 ConfigUtil::toXML(const BoardConfig& config)
309 {
310         std::stringstream s;
311
312         s << "<BoardConfig>\n" <<
313
314                 "       <unitAttention>" <<
315                         (config.flags & CONFIG_ENABLE_UNIT_ATTENTION ? "true" : "false") <<
316                         "</unitAttention>\n" <<
317
318                 "       <parity>" <<
319                         (config.flags & CONFIG_ENABLE_PARITY ? "true" : "false") <<
320                         "</parity>\n" <<
321
322                 "       <!-- ********************************************************\n" <<
323                 "       Only set to true when using with a fast SCSI2 host\n " <<
324                 "       controller. This can cause problems with older/slower\n" <<
325                 "       hardware.\n" <<
326                 "       ********************************************************* -->\n" <<
327                 "       <enableScsi2>" <<
328                         (config.flags & CONFIG_ENABLE_SCSI2 ? "true" : "false") <<
329                         "</enableScsi2>\n" <<
330
331                 "       <!-- ********************************************************\n" <<
332                 "       Setting to 'true' will result in increased performance at the\n" <<
333                 "       cost of lower noise immunity.\n" <<
334                 "       Only set to true when using short cables with only 1 or two\n" <<
335                 "       devices. This should remain off when using external SCSI1 DB25\n" <<
336                 "       cables.\n" <<
337                 "       ********************************************************* -->\n" <<
338                 "       <disableGlitchFilter>" <<
339                         (config.flags & CONFIG_DISABLE_GLITCH ? "true" : "false") <<
340                         "</disableGlitchFilter>\n" <<
341
342                 "       <enableCache>" <<
343                         (config.flags & CONFIG_ENABLE_CACHE ? "true" : "false") <<
344                         "</enableCache>\n" <<
345
346                 "       <enableDisconnect>" <<
347                         (config.flags & CONFIG_ENABLE_DISCONNECT ? "true" : "false") <<
348                         "</enableDisconnect>\n" <<
349
350                 "       <!-- ********************************************************\n" <<
351                 "       Respond to very short duration selection attempts. This supports\n" <<
352                 "       non-standard hardware, but is generally safe to enable.\n" <<
353                 "       Required for Philips P2000C.\n" <<
354                 "       ********************************************************* -->\n" <<
355                 "       <selLatch>" <<
356                         (config.flags & CONFIG_ENABLE_SEL_LATCH? "true" : "false") <<
357                         "</selLatch>\n" <<
358
359
360                 "       <!-- ********************************************************\n" <<
361                 "       Convert luns to IDs. The unit must already be configured to respond\n" <<
362                 "       on the ID. Allows dual drives to be accessed from a \n" <<
363                 "       XEBEC S1410 SASI bridge.\n" <<
364                 "       eg. Configured for dual drives as IDs 0 and 1, but the XEBEC will\n" <<
365                 "       access the second disk as ID0, lun 1.\n" <<
366                 "       See ttp://bitsavers.trailing-edge.com/pdf/xebec/104524C_S1410Man_Aug83.pdf\n" <<
367                 "       ********************************************************* -->\n" <<
368                 "       <mapLunsToIds>" <<
369                         (config.flags & CONFIG_MAP_LUNS_TO_IDS ? "true" : "false") <<
370                         "</mapLunsToIds>\n" <<
371                 "</BoardConfig>\n";
372
373         return s.str();
374 }
375
376
377 static uint64_t parseInt(wxXmlNode* node, uint64_t limit)
378 {
379         std::string str(node->GetNodeContent().mb_str());
380         if (str.empty())
381         {
382                 throw std::runtime_error("Empty " + node->GetName());
383         }
384
385         std::stringstream s;
386         if (str.find("0x") == 0)
387         {
388                 s << std::hex << str.substr(2);
389         }
390         else
391         {
392                 s << str;
393         }
394
395         uint64_t result;
396         s >> result;
397         if (!s)
398         {
399                 throw std::runtime_error("Invalid value for " + node->GetName());
400         }
401
402         if (result > limit)
403         {
404                 std::stringstream msg;
405                 msg << "Invalid value for " << node->GetName() <<
406                         " (max=" << limit << ")";
407                 throw std::runtime_error(msg.str());
408         }
409         return result;
410 }
411
412 static TargetConfig
413 parseTarget(wxXmlNode* node)
414 {
415         int id;
416         {
417                 std::stringstream s;
418                 s << node->GetAttribute("id", "7");
419                 s >> id;
420                 if (!s) throw std::runtime_error("Could not parse SCSITarget id attr");
421         }
422         TargetConfig result = ConfigUtil::Default(id & 0x7);
423
424         wxXmlNode *child = node->GetChildren();
425         while (child)
426         {
427                 if (child->GetName() == "enabled")
428                 {
429                         std::string s(child->GetNodeContent().mb_str());
430                         if (s == "true")
431                         {
432                                 result.scsiId |= CONFIG_TARGET_ENABLED;
433                         }
434                         else
435                         {
436                                 result.scsiId = result.scsiId & ~CONFIG_TARGET_ENABLED;
437                         }
438                 }
439                 else if (child->GetName() == "quirks")
440                 {
441                         std::stringstream s(std::string(child->GetNodeContent().mb_str()));
442                         std::string quirk;
443                         while (s >> quirk)
444                         {
445                                 if (quirk == "apple")
446                                 {
447                                         result.quirks |= CONFIG_QUIRKS_APPLE;
448                                 }
449                         }
450                 }
451                 else if (child->GetName() == "deviceType")
452                 {
453                         result.deviceType = parseInt(child, 0xFF);
454                 }
455                 else if (child->GetName() == "deviceTypeModifier")
456                 {
457                         result.deviceTypeModifier = parseInt(child, 0xFF);
458                 }
459                 else if (child->GetName() == "sdSectorStart")
460                 {
461                         result.sdSectorStart = parseInt(child, 0xFFFFFFFF);
462                 }
463                 else if (child->GetName() == "scsiSectors")
464                 {
465                         result.scsiSectors = parseInt(child, 0xFFFFFFFF);
466                 }
467                 else if (child->GetName() == "bytesPerSector")
468                 {
469                         result.bytesPerSector = parseInt(child, 8192);
470                 }
471                 else if (child->GetName() == "sectorsPerTrack")
472                 {
473                         result.sectorsPerTrack = parseInt(child, 255);
474                 }
475                 else if (child->GetName() == "headsPerCylinder")
476                 {
477                         result.headsPerCylinder = parseInt(child, 255);
478                 }
479                 else if (child->GetName() == "vendor")
480                 {
481                         std::string s(child->GetNodeContent().mb_str());
482                         s = s.substr(0, sizeof(result.vendor));
483                         memset(result.vendor, ' ', sizeof(result.vendor));
484                         memcpy(result.vendor, s.c_str(), s.size());
485                 }
486                 else if (child->GetName() == "prodId")
487                 {
488                         std::string s(child->GetNodeContent().mb_str());
489                         s = s.substr(0, sizeof(result.prodId));
490                         memset(result.prodId, ' ', sizeof(result.prodId));
491                         memcpy(result.prodId, s.c_str(), s.size());
492                 }
493                 else if (child->GetName() == "revision")
494                 {
495                         std::string s(child->GetNodeContent().mb_str());
496                         s = s.substr(0, sizeof(result.revision));
497                         memset(result.revision, ' ', sizeof(result.revision));
498                         memcpy(result.revision, s.c_str(), s.size());
499                 }
500                 else if (child->GetName() == "serial")
501                 {
502                         std::string s(child->GetNodeContent().mb_str());
503                         s = s.substr(0, sizeof(result.serial));
504                         memset(result.serial, ' ', sizeof(result.serial));
505                         memcpy(result.serial, s.c_str(), s.size());
506                 }
507                 else if (child->GetName() == "modePages")
508                 {
509                         wxMemoryBuffer buf =
510                                 wxBase64Decode(child->GetNodeContent(), wxBase64DecodeMode_SkipWS);
511                         size_t len = std::min(buf.GetDataLen(), sizeof(result.modePages));
512                         memcpy(result.modePages, buf.GetData(), len);
513                 }
514
515                 child = child->GetNext();
516         }
517         return result;
518 }
519
520 static BoardConfig
521 parseBoardConfig(wxXmlNode* node)
522 {
523         BoardConfig result = ConfigUtil::DefaultBoardConfig();
524
525         wxXmlNode *child = node->GetChildren();
526         while (child)
527         {
528                 if (child->GetName() == "unitAttention")
529                 {
530                         std::string s(child->GetNodeContent().mb_str());
531                         if (s == "true")
532                         {
533                                 result.flags |= CONFIG_ENABLE_UNIT_ATTENTION;
534                         }
535                         else
536                         {
537                                 result.flags = result.flags & ~CONFIG_ENABLE_UNIT_ATTENTION;
538                         }
539                 }
540                 else if (child->GetName() == "parity")
541                 {
542                         std::string s(child->GetNodeContent().mb_str());
543                         if (s == "true")
544                         {
545                                 result.flags |= CONFIG_ENABLE_PARITY;
546                         }
547                         else
548                         {
549                                 result.flags = result.flags & ~CONFIG_ENABLE_PARITY;
550                         }
551                 }
552                 else if (child->GetName() == "enableScsi2")
553                 {
554                         std::string s(child->GetNodeContent().mb_str());
555                         if (s == "true")
556                         {
557                                 result.flags |= CONFIG_ENABLE_SCSI2;
558                         }
559                         else
560                         {
561                                 result.flags = result.flags & ~CONFIG_ENABLE_SCSI2;
562                         }
563                 }
564                 else if (child->GetName() == "disableGlitchFilter")
565                 {
566                         std::string s(child->GetNodeContent().mb_str());
567                         if (s == "true")
568                         {
569                                 result.flags |= CONFIG_DISABLE_GLITCH;
570                         }
571                         else
572                         {
573                                 result.flags = result.flags & ~CONFIG_DISABLE_GLITCH;
574                         }
575                 }
576                 else if (child->GetName() == "enableCache")
577                 {
578                         std::string s(child->GetNodeContent().mb_str());
579                         if (s == "true")
580                         {
581                                 result.flags |= CONFIG_ENABLE_CACHE;
582                         }
583                         else
584                         {
585                                 result.flags = result.flags & ~CONFIG_ENABLE_CACHE;
586                         }
587                 }
588                 else if (child->GetName() == "enableDisconnect")
589                 {
590                         std::string s(child->GetNodeContent().mb_str());
591                         if (s == "true")
592                         {
593                                 result.flags |= CONFIG_ENABLE_DISCONNECT;
594                         }
595                         else
596                         {
597                                 result.flags = result.flags & ~CONFIG_ENABLE_DISCONNECT;
598                         }
599                 }
600                 else if (child->GetName() == "selLatch")
601                 {
602                         std::string s(child->GetNodeContent().mb_str());
603                         if (s == "true")
604                         {
605                                 result.flags |= CONFIG_ENABLE_SEL_LATCH;
606                         }
607                         else
608                         {
609                                 result.flags = result.flags & ~CONFIG_ENABLE_SEL_LATCH;
610                         }
611                 }
612                 else if (child->GetName() == "mapLunsToIds")
613                 {
614                         std::string s(child->GetNodeContent().mb_str());
615                         if (s == "true")
616                         {
617                                 result.flags |= CONFIG_MAP_LUNS_TO_IDS;
618                         }
619                         else
620                         {
621                                 result.flags = result.flags & ~CONFIG_MAP_LUNS_TO_IDS;
622                         }
623                 }
624                 child = child->GetNext();
625         }
626         return result;
627 }
628
629
630 std::pair<BoardConfig, std::vector<TargetConfig>>
631 ConfigUtil::fromXML(const std::string& filename)
632 {
633         wxXmlDocument doc;
634         if (!doc.Load(filename))
635         {
636                 throw std::runtime_error("Could not load XML file");
637         }
638
639         // start processing the XML file
640         if (doc.GetRoot()->GetName() != "SCSI2SD")
641         {
642                 throw std::runtime_error("Invalid root node, expected <SCSI2SD>");
643         }
644
645         BoardConfig boardConfig = DefaultBoardConfig();
646         int boardConfigFound = 0;
647
648         std::vector<TargetConfig> targets;
649         wxXmlNode *child = doc.GetRoot()->GetChildren();
650         while (child)
651         {
652                 if (child->GetName() == "SCSITarget")
653                 {
654                         targets.push_back(parseTarget(child));
655                 }
656                 else if (child->GetName() == "BoardConfig")
657                 {
658                         boardConfig = parseBoardConfig(child);
659                         boardConfigFound = 1;
660                 }
661                 child = child->GetNext();
662         }
663
664         if (!boardConfigFound && targets.size() > 0)
665         {
666                 boardConfig.flags = targets[0].flagsDEPRECATED;
667         }
668         return make_pair(boardConfig, targets);
669 }
670