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