Emulation of multiple SCSI targets now working.
[SCSI2SD.git] / software / scsi2sd-util / TargetPanel.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 // For compilers that support precompilation, includes "wx/wx.h".
19 #include <wx/wxprec.h>
20 #ifndef WX_PRECOMP
21 #include <wx/wx.h>
22 #endif
23
24 #include <wx/wrapsizer.h>
25
26 #include "ConfigUtil.hh"
27 #include "TargetPanel.hh"
28
29 #include <limits>
30 #include <sstream>
31
32 #include <math.h>
33 #include <string.h>
34
35 using namespace SCSI2SD;
36
37 wxDEFINE_EVENT(SCSI2SD::ConfigChangedEvent, wxCommandEvent);
38
39 namespace
40 {
41         template<typename IntType, class WXCTRL> std::pair<IntType, bool>
42         CtrlGetValue(WXCTRL* ctrl)
43         {
44                 IntType value;
45                 std::stringstream conv;
46                 conv << ctrl->GetValue();
47                 conv >> value;
48                 return std::make_pair(value, static_cast<bool>(conv));
49         }
50
51         void CtrlGetFixedString(wxTextEntry* ctrl, char* dest, size_t len)
52         {
53                 memset(dest, ' ', len);
54                 strncpy(dest, ctrl->GetValue().ToAscii(), len);
55         }
56
57         bool CtrlIsAscii(wxTextEntry* ctrl)
58         {
59                 return ctrl->GetValue().IsAscii();
60         }
61
62 }
63
64 TargetPanel::TargetPanel(wxWindow* parent, const TargetConfig& initialConfig) :
65         wxPanel(parent),
66         myParent(parent),
67         myStartSDSectorValidator(new wxIntegerValidator<uint32_t>),
68         mySectorSizeValidator(new wxIntegerValidator<uint16_t>),
69         myNumSectorValidator(new wxIntegerValidator<uint32_t>),
70         mySizeValidator(new wxFloatingPointValidator<float>(2))
71 {
72         wxFlexGridSizer *fgs = new wxFlexGridSizer(13, 3, 9, 25);
73
74         fgs->Add(new wxStaticText(this, wxID_ANY, wxT("")));
75         myEnableCtrl =
76                 new wxCheckBox(
77                         this,
78                         ID_enableCtrl,
79                         wxT("Enable SCSI Target"));
80         fgs->Add(myEnableCtrl);
81         // Set a non-visible string to leave room in the column for future messages
82         fgs->Add(new wxStaticText(this, wxID_ANY, wxT("                                        ")));
83         Bind(wxEVT_CHECKBOX, &TargetPanel::onInput<wxCommandEvent>, this, ID_enableCtrl);
84
85
86         fgs->Add(new wxStaticText(this, wxID_ANY, wxT("SCSI ID")));
87         myScsiIdCtrl =
88                 new wxSpinCtrl
89                         (this,
90                         ID_scsiIdCtrl,
91                         wxEmptyString,
92                         wxDefaultPosition,
93                         wxDefaultSize,
94                         wxSP_WRAP | wxSP_ARROW_KEYS,
95                         0,
96                         7,
97                         0);
98         fgs->Add(myScsiIdCtrl);
99         myScsiIdMsg = new wxStaticText(this, wxID_ANY, wxT(""));
100         fgs->Add(myScsiIdMsg);
101         Bind(wxEVT_SPINCTRL, &TargetPanel::onInput<wxSpinEvent>, this, ID_scsiIdCtrl);
102
103         fgs->Add(new wxStaticText(this, wxID_ANY, wxT("Device Type")));
104         wxString deviceTypes[] = {wxT("Hard Drive"), wxT("Removable"), wxT("CDROM")};
105         myDeviceTypeCtrl =
106                 new wxChoice(
107                         this,
108                         ID_deviceTypeCtrl,
109                         wxDefaultPosition,
110                         wxDefaultSize,
111                         sizeof(deviceTypes) / sizeof(wxString),
112                         deviceTypes
113                         );
114         myDeviceTypeCtrl->SetSelection(0);
115         fgs->Add(myDeviceTypeCtrl);
116         fgs->Add(new wxStaticText(this, wxID_ANY, wxT("")));
117         Bind(wxEVT_CHOICE, &TargetPanel::onInput<wxCommandEvent>, this, ID_deviceTypeCtrl);
118
119         fgs->Add(new wxStaticText(this, wxID_ANY, wxT("")));
120         myParityCtrl =
121                 new wxCheckBox(
122                         this,
123                         ID_parityCtrl,
124                         wxT("Enable Parity"));
125         fgs->Add(myParityCtrl);
126         fgs->Add(new wxStaticText(this, wxID_ANY, wxT("")));
127         Bind(wxEVT_CHECKBOX, &TargetPanel::onInput<wxCommandEvent>, this, ID_parityCtrl);
128
129         fgs->Add(new wxStaticText(this, wxID_ANY, wxT("")));
130         myUnitAttCtrl =
131                 new wxCheckBox(
132                         this,
133                         ID_unitAttCtrl,
134                         wxT("Enable Unit Attention"));
135
136         fgs->Add(myUnitAttCtrl);
137         fgs->Add(new wxStaticText(this, wxID_ANY, wxT("")));
138         Bind(wxEVT_CHECKBOX, &TargetPanel::onInput<wxCommandEvent>, this, ID_unitAttCtrl);
139
140         fgs->Add(new wxStaticText(this, wxID_ANY, wxT("SD card start sector")));
141         myStartSDSectorCtrl =
142                 new wxTextCtrl(
143                         this,
144                         ID_startSDSectorCtrl,
145                         "0",
146                         wxDefaultPosition,
147                         wxDefaultSize,
148                         0,
149                         *myStartSDSectorValidator);
150         myStartSDSectorCtrl->SetToolTip(wxT("Supports multiple SCSI targets "
151                 "on a single memory card. In units of 512-byte sectors."));
152         fgs->Add(myStartSDSectorCtrl);
153         myStartSDSectorMsg = new wxStaticText(this, wxID_ANY, wxT(""));
154         fgs->Add(myStartSDSectorMsg);
155         Bind(wxEVT_TEXT, &TargetPanel::onInput<wxCommandEvent>, this, ID_startSDSectorCtrl);
156
157         fgs->Add(new wxStaticText(this, wxID_ANY, wxT("Sector size (bytes)")));
158         mySectorSizeCtrl =
159                 new wxTextCtrl(
160                         this,
161                         ID_sectorSizeCtrl,
162                         "512",
163                         wxDefaultPosition,
164                         wxDefaultSize,
165                         0,
166                         *mySectorSizeValidator);
167         mySectorSizeCtrl->SetToolTip(wxT("Between 64 and 8192. Default of 512 is suitable in most cases."));
168         fgs->Add(mySectorSizeCtrl);
169         mySectorSizeMsg = new wxStaticText(this, wxID_ANY, wxT(""));
170         fgs->Add(mySectorSizeMsg);
171         Bind(wxEVT_TEXT, &TargetPanel::onSizeInput, this, ID_sectorSizeCtrl);
172
173
174         fgs->Add(new wxStaticText(this, wxID_ANY, wxT("Sector count")));
175         myNumSectorCtrl =
176                 new wxTextCtrl(
177                         this,
178                         ID_numSectorCtrl,
179                         "",
180                         wxDefaultPosition,
181                         wxDefaultSize,
182                         0,
183                         *myNumSectorValidator);
184         myNumSectorCtrl->SetToolTip(wxT("Number of sectors (device size)"));
185         fgs->Add(myNumSectorCtrl);
186         myNumSectorMsg = new wxStaticText(this, wxID_ANY, wxT(""));
187         fgs->Add(myNumSectorMsg);
188         Bind(wxEVT_TEXT, &TargetPanel::onSizeInput, this, ID_numSectorCtrl);
189
190
191         fgs->Add(new wxStaticText(this, wxID_ANY, wxT("Device size")));
192         wxWrapSizer* sizeContainer = new wxWrapSizer();
193         mySizeCtrl =
194                 new wxTextCtrl(
195                         this,
196                         ID_sizeCtrl,
197                         "",
198                         wxDefaultPosition,
199                         wxDefaultSize,
200                         0,
201                         *mySizeValidator);
202         mySizeCtrl->SetToolTip(wxT("Device size"));
203         sizeContainer->Add(mySizeCtrl);
204         wxString units[] = {wxT("KB"), wxT("MB"), wxT("GB")};
205         mySizeUnitCtrl =
206                 new wxChoice(
207                         this,
208                         ID_sizeUnitCtrl,
209                         wxDefaultPosition,
210                         wxDefaultSize,
211                         sizeof(units) / sizeof(wxString),
212                         units
213                         );
214         sizeContainer->Add(mySizeUnitCtrl);
215         fgs->Add(sizeContainer);
216         fgs->Add(new wxStaticText(this, wxID_ANY, wxT("")));
217         Bind(wxEVT_TEXT, &TargetPanel::onSizeInput, this, ID_sizeCtrl);
218         Bind(wxEVT_CHOICE, &TargetPanel::onSizeInput, this, ID_sizeUnitCtrl);
219
220         fgs->Add(new wxStaticText(this, wxID_ANY, wxT("Vendor")));
221         myVendorCtrl =
222                 new wxTextCtrl(
223                         this,
224                         ID_vendorCtrl);
225         myVendorCtrl->SetMaxLength(8);
226         myVendorCtrl->SetToolTip(wxT("SCSI Vendor string. eg. ' codesrc'"));
227         fgs->Add(myVendorCtrl);
228         myVendorMsg = new wxStaticText(this, wxID_ANY, wxT(""));
229         fgs->Add(myVendorMsg);
230         Bind(wxEVT_TEXT, &TargetPanel::onInput<wxCommandEvent>, this, ID_vendorCtrl);
231
232         fgs->Add(new wxStaticText(this, wxID_ANY, wxT("Product ID")));
233         myProductCtrl =
234                 new wxTextCtrl(
235                         this,
236                         ID_productCtrl);
237         myProductCtrl->SetMaxLength(16);
238         myProductCtrl->SetToolTip(wxT("SCSI Product ID string. eg. 'SCSI2SD'"));
239         fgs->Add(myProductCtrl);
240         myProductMsg = new wxStaticText(this, wxID_ANY, wxT(""));
241         fgs->Add(myProductMsg);
242         Bind(wxEVT_TEXT, &TargetPanel::onInput<wxCommandEvent>, this, ID_productCtrl);
243
244         fgs->Add(new wxStaticText(this, wxID_ANY, wxT("Revision")));
245         myRevisionCtrl =
246                 new wxTextCtrl(
247                         this,
248                         ID_revisionCtrl);
249         myRevisionCtrl->SetMaxLength(4);
250         myRevisionCtrl->SetToolTip(wxT("SCSI device revision string. eg. '3.5a'"));
251         fgs->Add(myRevisionCtrl);
252         myRevisionMsg = new wxStaticText(this, wxID_ANY, wxT(""));
253         fgs->Add(myRevisionMsg);
254         Bind(wxEVT_TEXT, &TargetPanel::onInput<wxCommandEvent>, this, ID_revisionCtrl);
255
256         fgs->Add(new wxStaticText(this, wxID_ANY, wxT("Serial number")));
257         mySerialCtrl =
258                 new wxTextCtrl(
259                         this,
260                         ID_serialCtrl);
261         mySerialCtrl->SetMaxLength(16);
262         mySerialCtrl->SetToolTip(wxT("SCSI serial number. eg. '13eab5632a'"));
263         fgs->Add(mySerialCtrl);
264         mySerialMsg = new wxStaticText(this, wxID_ANY, wxT(""));
265         fgs->Add(mySerialMsg);
266         Bind(wxEVT_TEXT, &TargetPanel::onInput<wxCommandEvent>, this, ID_serialCtrl);
267
268         wxBoxSizer* hbox = new wxBoxSizer(wxHORIZONTAL);
269         hbox->Add(fgs, 1, wxALL | wxEXPAND, 15);
270         this->SetSizer(hbox);
271         Centre();
272
273
274         setConfig(initialConfig);
275         evaluate();
276 }
277
278 bool
279 TargetPanel::evaluate()
280 {
281         bool valid = true;
282         std::stringstream conv;
283
284         uint32_t startSDsector;
285         {
286                 conv << myStartSDSectorCtrl->GetValue();
287                 conv >> startSDsector;
288         }
289
290         if (!conv)
291                 // TODO check if it is beyond the current SD card.
292         {
293                 myStartSDSectorMsg->SetLabelMarkup(wxT("<span foreground='red' weight='bold'>Number &gt;= 0</span>"));
294                 valid = false;
295         }
296         else
297         {
298                 myStartSDSectorMsg->SetLabelMarkup("");
299         }
300         conv.str(std::string()); conv.clear();
301
302         uint16_t sectorSize(CtrlGetValue<uint16_t>(mySectorSizeCtrl).first);
303         if (sectorSize < 64 || sectorSize > 8192)
304         {
305                 mySectorSizeMsg->SetLabelMarkup(wxT("<span foreground='red' weight='bold'>Must be between 64 and 8192</span>"));
306                 valid = false;
307         }
308         else
309         {
310                 mySectorSizeMsg->SetLabelMarkup("");
311         }
312         conv.str(std::string()); conv.clear();
313
314         std::pair<uint32_t, bool> numSectors(CtrlGetValue<uint32_t>(myNumSectorCtrl));
315         if (!numSectors.second ||
316                 numSectors.first == 0 ||
317                 !convertUnitsToSectors().second)
318         {
319                 myNumSectorMsg->SetLabelMarkup(wxT("<span foreground='red' weight='bold'>Invalid size</span>"));
320                 valid = false;
321         }
322         else
323         {
324                 myNumSectorMsg->SetLabelMarkup("");
325         }
326
327         if (!CtrlIsAscii(myVendorCtrl))
328         {
329                 myVendorMsg->SetLabelMarkup(wxT("<span foreground='red' weight='bold'>Invalid characters</span>"));
330                 valid = false;
331         }
332         else
333         {
334                 myVendorMsg->SetLabelMarkup("");
335         }
336
337         if (!CtrlIsAscii(myProductCtrl))
338         {
339                 myProductMsg->SetLabelMarkup(wxT("<span foreground='red' weight='bold'>Invalid characters</span>"));
340                 valid = false;
341         }
342         else
343         {
344                 myProductMsg->SetLabelMarkup("");
345         }
346
347         if (!CtrlIsAscii(myRevisionCtrl))
348         {
349                 myRevisionMsg->SetLabelMarkup(wxT("<span foreground='red' weight='bold'>Invalid characters</span>"));
350                 valid = false;
351         }
352         else
353         {
354                 myRevisionMsg->SetLabelMarkup("");
355         }
356
357         if (!CtrlIsAscii(mySerialCtrl))
358         {
359                 mySerialMsg->SetLabelMarkup(wxT("<span foreground='red' weight='bold'>Invalid characters</span>"));
360                 valid = false;
361         }
362         else
363         {
364                 mySerialMsg->SetLabelMarkup("");
365         }
366
367         bool enabled = myEnableCtrl->IsChecked();
368         {
369                 myScsiIdCtrl->Enable(enabled);
370                 myDeviceTypeCtrl->Enable(enabled);
371                 myParityCtrl->Enable(enabled);
372                 myUnitAttCtrl->Enable(enabled);
373                 myStartSDSectorCtrl->Enable(enabled);
374                 mySectorSizeCtrl->Enable(enabled);
375                 myNumSectorCtrl->Enable(enabled);
376                 mySizeCtrl->Enable(enabled);
377                 mySizeUnitCtrl->Enable(enabled);
378                 myVendorCtrl->Enable(enabled);
379                 myProductCtrl->Enable(enabled);
380                 myRevisionCtrl->Enable(enabled);
381                 mySerialCtrl->Enable(enabled);
382         }
383         return valid || !enabled;
384 }
385
386 template<typename EvtType> void
387 TargetPanel::onInput(EvtType& event)
388 {
389         wxCommandEvent changeEvent(ConfigChangedEvent);
390         wxPostEvent(myParent, changeEvent);
391 }
392
393 void
394 TargetPanel::onSizeInput(wxCommandEvent& event)
395 {
396         if (event.GetId() == ID_numSectorCtrl)
397         {
398                 uint32_t numSectors;
399                 std::stringstream conv;
400                 conv << myNumSectorCtrl->GetValue();
401                 conv >> numSectors;
402
403                 conv.str(""); conv.clear();
404
405                 if (conv)
406                 {
407                         uint64_t bytes =
408                                 uint64_t(numSectors) *
409                                         CtrlGetValue<uint16_t>(mySectorSizeCtrl).first;
410
411                         if (bytes >= 1024 * 1024 * 1024)
412                         {
413                                 conv << (bytes / (1024.0 * 1024 * 1024));
414                                 mySizeUnitCtrl->SetSelection(UNIT_GB);
415                         }
416                         else if (bytes >= 1024 * 1024)
417                         {
418                                 conv << (bytes / (1024.0 * 1024));
419                                 mySizeUnitCtrl->SetSelection(UNIT_MB);
420                         }
421                         else
422                         {
423                                 conv << (bytes / 1024.0);
424                                 mySizeUnitCtrl->SetSelection(UNIT_KB);
425                         }
426                         mySizeCtrl->ChangeValue(conv.str());
427                 }
428         }
429         else
430         {
431                 std::stringstream ss;
432                 ss << convertUnitsToSectors().first;
433                 myNumSectorCtrl->ChangeValue(ss.str());
434         }
435
436         onInput(event); // propagate
437 }
438
439 std::pair<uint32_t, bool>
440 TargetPanel::convertUnitsToSectors() const
441 {
442         bool valid = true;
443
444         uint64_t multiplier(0);
445         switch (mySizeUnitCtrl->GetSelection())
446         {
447                 case UNIT_KB: multiplier = 1024; break;
448                 case UNIT_MB: multiplier = 1024 * 1024; break;
449                 case UNIT_GB: multiplier = 1024 * 1024 * 1024; break;
450                 default: valid = false;
451         }
452
453         double size;
454         std::stringstream conv;
455         conv << mySizeCtrl->GetValue();
456         conv >> size;
457         valid = valid && conv;
458
459         uint16_t sectorSize = CtrlGetValue<uint16_t>(mySectorSizeCtrl).first;
460         uint64_t sectors = ceil(multiplier * size / sectorSize);
461
462         if (sectors > std::numeric_limits<uint32_t>::max())
463         {
464                 sectors = std::numeric_limits<uint32_t>::max();
465                 valid = false;
466         }
467
468         return std::make_pair(static_cast<uint32_t>(sectors), valid);
469 }
470
471
472 TargetConfig
473 TargetPanel::getConfig() const
474 {
475         TargetConfig config;
476
477         // Try and keep unknown/unused fields as-is to support newer firmware
478         // versions.
479         memcpy(&config, &myConfig, sizeof(config));
480
481         bool valid = true;
482
483         auto scsiId = CtrlGetValue<uint8_t>(myScsiIdCtrl);
484         config.scsiId = scsiId.first & CONFIG_TARGET_ID_BITS;
485         valid = valid && scsiId.second;
486         if (myEnableCtrl->IsChecked())
487         {
488                 config.scsiId = config.scsiId | CONFIG_TARGET_ENABLED;
489         }
490
491         config.deviceType = myDeviceTypeCtrl->GetSelection();
492
493         config.flags =
494                 (myParityCtrl->IsChecked() ? CONFIG_ENABLE_PARITY : 0) |
495                 (myUnitAttCtrl->IsChecked() ? CONFIG_ENABLE_UNIT_ATTENTION : 0);
496
497         auto startSDSector = CtrlGetValue<uint32_t>(myStartSDSectorCtrl);
498         config.sdSectorStart = startSDSector.first;
499         valid = valid && startSDSector.second;
500
501         auto numSectors = CtrlGetValue<uint32_t>(myNumSectorCtrl);
502         config.scsiSectors = numSectors.first;
503         valid = valid && numSectors.second;
504
505         auto sectorSize = CtrlGetValue<uint16_t>(mySectorSizeCtrl);
506         config.bytesPerSector = sectorSize.first;
507         valid = valid && sectorSize.second;
508
509         CtrlGetFixedString(myVendorCtrl, config.vendor, sizeof(config.vendor));
510         CtrlGetFixedString(myProductCtrl, config.prodId, sizeof(config.prodId));
511         CtrlGetFixedString(myRevisionCtrl, config.revision, sizeof(config.revision));
512         CtrlGetFixedString(mySerialCtrl, config.serial, sizeof(config.serial));
513
514         return config;
515 }
516
517 void
518 TargetPanel::setConfig(const TargetConfig& config)
519 {
520         memcpy(&myConfig, &config, sizeof(config));
521
522         myScsiIdCtrl->SetValue(config.scsiId & CONFIG_TARGET_ID_BITS);
523         myEnableCtrl->SetValue(config.scsiId & CONFIG_TARGET_ENABLED);
524
525         myDeviceTypeCtrl->SetSelection(config.deviceType);
526
527         myParityCtrl->SetValue(config.flags & CONFIG_ENABLE_PARITY);
528         myUnitAttCtrl->SetValue(config.flags & CONFIG_ENABLE_UNIT_ATTENTION);
529
530         {
531                 std::stringstream ss; ss << config.sdSectorStart;
532                 myStartSDSectorCtrl->ChangeValue(ss.str());
533         }
534
535         {
536                 std::stringstream ss; ss << config.scsiSectors;
537                 myNumSectorCtrl->ChangeValue(ss.str());
538         }
539
540         {
541                 std::stringstream ss; ss << config.bytesPerSector;
542                 mySectorSizeCtrl->ChangeValue(ss.str());
543         }
544
545         myVendorCtrl->ChangeValue(std::string(config.vendor, sizeof(config.vendor)));
546         myProductCtrl->ChangeValue(std::string(config.prodId, sizeof(config.prodId)));
547         myRevisionCtrl->ChangeValue(std::string(config.revision, sizeof(config.revision)));
548         mySerialCtrl->ChangeValue(std::string(config.serial, sizeof(config.serial)));
549
550         // Set the size fields based on sector size, and evaluate inputs.
551         wxCommandEvent fakeEvent(wxEVT_NULL, ID_numSectorCtrl);
552         onSizeInput(fakeEvent);
553 }
554
555 bool
556 TargetPanel::isEnabled() const
557 {
558         return myEnableCtrl->IsChecked();
559 }
560
561 uint8_t
562 TargetPanel::getSCSIId() const
563 {
564         return CtrlGetValue<uint8_t>(myScsiIdCtrl).first & CONFIG_TARGET_ID_BITS;
565 }
566
567 std::pair<uint32_t, uint64_t>
568 TargetPanel::getSDSectorRange() const
569 {
570         std::pair<uint32_t, uint64_t> result;
571         result.first = CtrlGetValue<uint32_t>(myStartSDSectorCtrl).first;
572
573         uint32_t numSCSISectors = CtrlGetValue<uint32_t>(myNumSectorCtrl).first;
574         uint16_t scsiSectorSize = CtrlGetValue<uint16_t>(mySectorSizeCtrl).first;
575
576         const int sdSector = 512; // Always 512 for SDHC/SDXC
577         result.second = result.first +
578                 (
579                         ((uint64_t(numSCSISectors) * scsiSectorSize) + (sdSector - 1))
580                                 / sdSector
581                 );
582         return result;
583 }
584
585 void
586 TargetPanel::setDuplicateID(bool duplicate)
587 {
588         if (duplicate)
589         {
590                 myScsiIdMsg->SetLabelMarkup(wxT("<span foreground='red' weight='bold'>Duplicate ID</span>"));
591         }
592         else
593         {
594                 myScsiIdMsg->SetLabelMarkup("");
595         }
596 }
597
598 void
599 TargetPanel::setSDSectorOverlap(bool overlap)
600 {
601         if (overlap)
602         {
603                 myStartSDSectorMsg->SetLabelMarkup(wxT("<span foreground='red' weight='bold'>Overlapping data</span>"));
604         }
605         else
606         {
607                 myStartSDSectorMsg->SetLabelMarkup("");
608         }
609 }