Emulation of multiple SCSI targets now working.
[SCSI2SD-V6.git] / software / scsi2sd-util / scsi2sd-util.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
19 // For compilers that support precompilation, includes "wx/wx.h".
20 #include <wx/wxprec.h>
21 #ifndef WX_PRECOMP
22 #include <wx/wx.h>
23 #endif
24
25 #include <wx/filedlg.h>
26 #include <wx/notebook.h>
27 #include <wx/progdlg.h>
28 #include <wx/utils.h>
29 #include <wx/windowptr.h>
30 #include <wx/thread.h>
31
32 #include <zipper.hh>
33
34 #include "ConfigUtil.hh"
35 #include "TargetPanel.hh"
36 #include "SCSI2SD_Bootloader.hh"
37 #include "SCSI2SD_HID.hh"
38 #include "Firmware.hh"
39
40 #include <algorithm>
41 #include <vector>
42 #include <set>
43 #include <sstream>
44
45 #if __cplusplus >= 201103L
46 #include <cstdint>
47 #include <memory>
48 using std::shared_ptr;
49 #else
50 #include <stdint.h>
51 #include <tr1/memory>
52 using std::tr1::shared_ptr;
53 #endif
54
55
56 using namespace SCSI2SD;
57
58 class ProgressWrapper
59 {
60 public:
61         void setProgressDialog(
62                 const wxWindowPtr<wxGenericProgressDialog>& dlg,
63                 size_t maxRows)
64         {
65                 myProgressDialog = dlg;
66                 myMaxRows = maxRows;
67                 myNumRows = 0;
68         }
69
70         void clearProgressDialog()
71         {
72                 myProgressDialog.reset();
73         }
74
75         void update(unsigned char arrayId, unsigned short rowNum)
76         {
77                 if (!myProgressDialog) return;
78
79                 myNumRows++;
80
81                 std::stringstream ss;
82                 ss << "Writing flash array " <<
83                         static_cast<int>(arrayId) << " row " <<
84                         static_cast<int>(rowNum);
85                 std::clog << ss.str() << std::endl;
86                 myProgressDialog->Update(myNumRows, ss.str());
87         }
88
89 private:
90         wxWindowPtr<wxGenericProgressDialog> myProgressDialog;
91         size_t myMaxRows;
92         size_t myNumRows;
93 };
94 static ProgressWrapper TheProgressWrapper;
95
96 extern "C"
97 void ProgressUpdate(unsigned char arrayId, unsigned short rowNum)
98 {
99         TheProgressWrapper.update(arrayId, rowNum);
100 }
101
102 namespace
103 {
104
105 class TimerLock
106 {
107 public:
108         TimerLock(wxTimer* timer) :
109                 myTimer(timer),
110                 myInterval(myTimer->GetInterval())
111         {
112                 myTimer->Stop();
113         };
114
115         virtual ~TimerLock()
116         {
117                 if (myTimer && myInterval > 0)
118                 {
119                         myTimer->Start(myInterval);
120                 }
121         }
122 private:
123         wxTimer* myTimer;
124         int myInterval;
125 };
126
127 class AppFrame : public wxFrame
128 {
129 public:
130         AppFrame() :
131                 wxFrame(NULL, wxID_ANY, "scsi2sd-util", wxPoint(50, 50), wxSize(600, 650)),
132                 myInitialConfig(false),
133                 myTickCounter(0)
134         {
135                 wxMenu *menuFile = new wxMenu();
136                 menuFile->Append(
137                         ID_ConfigDefaults,
138                         "Load &Defaults",
139                         "Load default configuration options.");
140                 menuFile->Append(
141                         ID_Firmware,
142                         "&Upgrade Firmware...",
143                         "Upgrade or inspect device firmware version.");
144                 menuFile->AppendSeparator();
145                 menuFile->Append(wxID_EXIT);
146                 wxMenu *menuHelp = new wxMenu();
147                 menuHelp->Append(wxID_ABOUT);
148                 wxMenuBar *menuBar = new wxMenuBar();
149                 menuBar->Append( menuFile, "&File" );
150                 menuBar->Append( menuHelp, "&Help" );
151                 SetMenuBar( menuBar );
152
153                 CreateStatusBar();
154                 SetStatusText( "Searching for SCSI2SD" );
155
156                 {
157                         wxPanel* cfgPanel = new wxPanel(this);
158                         wxFlexGridSizer *fgs = new wxFlexGridSizer(2, 1, 5, 5);
159                         wxNotebook* tabs = new wxNotebook(cfgPanel, ID_Notebook);
160
161                         for (int i = 0; i < MAX_SCSI_TARGETS; ++i)
162                         {
163                                 TargetPanel* target =
164                                         new TargetPanel(tabs, ConfigUtil::Default(i));
165                                 myTargets.push_back(target);
166                                 std::stringstream ss;
167                                 ss << "Device " << (i + 1);
168                                 tabs->AddPage(target, ss.str());
169                         }
170                         fgs->Add(tabs);
171
172
173                         wxPanel* btnPanel = new wxPanel(cfgPanel);
174                         wxFlexGridSizer *btnFgs = new wxFlexGridSizer(1, 2, 5, 5);
175                         btnFgs->Add(new wxButton(btnPanel, ID_BtnLoad, wxT("Load from device")));
176                         mySaveButton =
177                                 new wxButton(btnPanel, ID_BtnSave, wxT("Save to device"));
178                         btnFgs->Add(mySaveButton);
179                         {
180                                 wxBoxSizer* hbox = new wxBoxSizer(wxHORIZONTAL);
181                                 hbox->Add(btnFgs, 1, wxALL | wxEXPAND, 15);
182                                 btnPanel->SetSizer(hbox);
183                                 fgs->Add(btnPanel);
184                         }
185
186                         {
187                                 wxBoxSizer* hbox = new wxBoxSizer(wxHORIZONTAL);
188                                 hbox->Add(fgs, 1, wxALL | wxEXPAND, 15);
189                                 cfgPanel->SetSizer(hbox);
190                         }
191                 }
192
193                 myTimer = new wxTimer(this, ID_Timer);
194                 myTimer->Start(1000); //ms
195
196         }
197 private:
198         std::vector<TargetPanel*> myTargets;
199         wxButton* mySaveButton;
200         wxTimer* myTimer;
201         shared_ptr<HID> myHID;
202         shared_ptr<Bootloader> myBootloader;
203         bool myInitialConfig;
204
205         uint8_t myTickCounter;
206
207         void onConfigChanged(wxCommandEvent& event)
208         {
209                 evaluate();
210         }
211
212         void evaluate()
213         {
214                 bool valid = true;
215
216                 // Check for duplicate SCSI IDs
217                 std::set<uint8_t> enabledID;
218
219                 // Check for overlapping SD sectors.
220                 std::vector<std::pair<uint32_t, uint64_t> > sdSectors;
221
222                 bool isTargetEnabled = false; // Need at least one enabled
223
224                 for (size_t i = 0; i < myTargets.size(); ++i)
225                 {
226                         valid = myTargets[i]->evaluate() && valid;
227
228                         if (myTargets[i]->isEnabled())
229                         {
230                                 isTargetEnabled = true;
231                                 uint8_t scsiID = myTargets[i]->getSCSIId();
232                                 if (enabledID.find(scsiID) != enabledID.end())
233                                 {
234                                         myTargets[i]->setDuplicateID(true);
235                                         valid = false;
236                                 }
237                                 else
238                                 {
239                                         enabledID.insert(scsiID);
240                                         myTargets[i]->setDuplicateID(false);
241                                 }
242
243                                 auto sdSectorRange = myTargets[i]->getSDSectorRange();
244                                 for (auto it(sdSectors.begin()); it != sdSectors.end(); ++it)
245                                 {
246                                         if (sdSectorRange.first < it->second &&
247                                                 sdSectorRange.second > it->first)
248                                         {
249                                                 valid = false;
250                                                 myTargets[i]->setSDSectorOverlap(true);
251                                         }
252                                         else
253                                         {
254                                                 myTargets[i]->setSDSectorOverlap(false);
255                                         }
256                                 }
257                                 sdSectors.push_back(sdSectorRange);
258                         }
259                         else
260                         {
261                                 myTargets[i]->setDuplicateID(false);
262                                 myTargets[i]->setSDSectorOverlap(false);
263                         }
264                 }
265
266                 valid = valid && isTargetEnabled; // Need at least one.
267
268                 mySaveButton->Enable(valid && myHID);
269         }
270
271
272         enum
273         {
274                 ID_ConfigDefaults = wxID_HIGHEST + 1,
275                 ID_Firmware,
276                 ID_Timer,
277                 ID_Notebook,
278                 ID_BtnLoad,
279                 ID_BtnSave
280         };
281
282         void OnID_ConfigDefaults(wxCommandEvent& event)
283         {
284                 for (size_t i = 0; i < myTargets.size(); ++i)
285                 {
286                         myTargets[i]->setConfig(ConfigUtil::Default(i));
287                 }
288         }
289
290         void OnID_Firmware(wxCommandEvent& event)
291         {
292                 TimerLock lock(myTimer);
293                 doFirmwareUpdate();
294         }
295
296         void doFirmwareUpdate()
297         {
298                 wxFileDialog dlg(
299                         this,
300                         "Load firmware file",
301                         "",
302                         "",
303                         "SCSI2SD Firmware files (*.cyacd)|*.cyacd",
304                         wxFD_OPEN | wxFD_FILE_MUST_EXIST);
305                 if (dlg.ShowModal() == wxID_CANCEL) return;
306
307                 std::string filename(dlg.GetPath());
308
309                 try
310                 {
311                         if (!myHID) myHID.reset(HID::Open());
312                         if (myHID)
313                         {
314                                 std::clog << "Resetting SCSI2SD into bootloader" << std::endl;
315
316                                 myHID->enterBootloader();
317                                 myHID.reset();
318                         }
319
320                         if (myBootloader)
321                         {
322                                 // Verify the USB HID connection is valid
323                                 if (!myBootloader->ping())
324                                 {
325                                         myBootloader.reset();
326                                 }
327                         }
328
329                         for (int i = 0; !myBootloader && (i < 10); ++i)
330                         {
331                                 std::clog << "Searching for bootloader" << std::endl;
332                                 myBootloader.reset(Bootloader::Open());
333                                 wxMilliSleep(100);
334                         }
335                 }
336                 catch (std::exception& e)
337                 {
338                         std::clog << e.what() << std::endl;
339
340                         SetStatusText(e.what());
341                         myHID.reset();
342                         myBootloader.reset();
343                 }
344
345                 if (!myBootloader)
346                 {
347                         wxMessageBox(
348                                 "SCSI2SD not found",
349                                 "Device not ready",
350                                 wxOK | wxICON_ERROR);
351
352                         return;
353                 }
354
355                 if (!myBootloader->isCorrectFirmware(filename))
356                 {
357                         // TODO allow "force" option
358                         wxMessageBox(
359                                 "Wrong filename",
360                                 "Wrong filename",
361                                 wxOK | wxICON_ERROR);
362                         return;
363                 }
364
365                 int totalFlashRows = 0;
366                 try
367                 {
368                         Firmware firmware(filename);
369                         totalFlashRows = firmware.totalFlashRows();
370                 }
371                 catch (std::exception& e)
372                 {
373                         SetStatusText(e.what());
374                         std::stringstream msg;
375                         msg << "Could not open firmware file: " << e.what();
376                         wxMessageBox(
377                                 msg.str(),
378                                 "Bad file",
379                                 wxOK | wxICON_ERROR);
380                         return;
381                 }
382
383                 {
384                         wxWindowPtr<wxGenericProgressDialog> progress(
385                                 new wxGenericProgressDialog(
386                                         "Loading firmware",
387                                         "Loading firmware",
388                                         totalFlashRows,
389                                         this,
390                                         wxPD_APP_MODAL)
391                                         );
392                         TheProgressWrapper.setProgressDialog(progress, totalFlashRows);
393                 }
394
395                 std::clog << "Upgrading firmware from file: " << filename << std::endl;
396
397                 try
398                 {
399                         myBootloader->load(filename, &ProgressUpdate);
400                         TheProgressWrapper.clearProgressDialog();
401
402                         wxMessageBox(
403                                 "Firmware update successful",
404                                 "Firmware OK",
405                                 wxOK);
406                         SetStatusText("Firmware update successful");
407
408
409                         myHID.reset();
410                         myBootloader.reset();
411                 }
412                 catch (std::exception& e)
413                 {
414                         TheProgressWrapper.clearProgressDialog();
415                         SetStatusText(e.what());
416                         myHID.reset();
417                         myBootloader.reset();
418
419                         wxMessageBox(
420                                 "Firmware Update Failed",
421                                 e.what(),
422                                 wxOK | wxICON_ERROR);
423                 }
424         }
425
426         void OnID_Timer(wxTimerEvent& event)
427         {
428                 // Check if we are connected to the HID device.
429                 // AND/or bootloader device.
430                 try
431                 {
432                         if (myBootloader)
433                         {
434                                 // Verify the USB HID connection is valid
435                                 if (!myBootloader->ping())
436                                 {
437                                         myBootloader.reset();
438                                 }
439                         }
440
441                         if (!myBootloader)
442                         {
443                                 myBootloader.reset(Bootloader::Open());
444
445                                 if (myBootloader)
446                                 {
447                                         SetStatusText(wxT("SCSI2SD Bootloader Ready"));
448                                 }
449                         }
450
451                         if (myHID)
452                         {
453                                 // Verify the USB HID connection is valid
454                                 if (!myHID->ping())
455                                 {
456                                         myHID.reset();
457                                 }
458                         }
459
460                         if (!myHID)
461                         {
462                                 myHID.reset(HID::Open());
463                                 if (myHID)
464                                 {
465                                         uint16_t version = myHID->getFirmwareVersion();
466                                         if (version == 0)
467                                         {
468                                                 // Oh dear, old firmware
469                                                 SetStatusText(wxT("Firmware update required"));
470                                                 myHID.reset();
471                                         }
472                                         else
473                                         {
474                                                 SetStatusText(wxT("SCSI2SD Ready"));
475
476                                                 if (!myInitialConfig)
477                                                 {
478                                                         wxCommandEvent loadEvent(wxEVT_NULL, ID_BtnLoad);
479                                                         GetEventHandler()->AddPendingEvent(loadEvent);
480                                                 }
481                                         }
482                                 }
483                                 else
484                                 {
485                                         char ticks[] = {'/', '-', '\\', '|'};
486                                         std::stringstream ss;
487                                         ss << "Searching for SCSI2SD device " << ticks[myTickCounter % sizeof(ticks)];
488                                         myTickCounter++;
489                                         SetStatusText(ss.str());
490                                 }
491                         }
492                 }
493                 catch (std::runtime_error& e)
494                 {
495                         std::clog << e.what() << std::endl;
496                         SetStatusText(e.what());
497                 }
498
499                 evaluate();
500         }
501
502         void doLoad(wxCommandEvent& event)
503         {
504                 TimerLock lock(myTimer);
505                 if (!myHID) return;
506
507                 std::clog << "Loading configuration" << std::endl;
508
509                 wxWindowPtr<wxGenericProgressDialog> progress(
510                         new wxGenericProgressDialog(
511                                 "Load config settings",
512                                 "Loading config settings",
513                                 100,
514                                 this,
515                                 wxPD_APP_MODAL | wxPD_CAN_ABORT)
516                                 );
517
518                 int flashRow = SCSI_CONFIG_0_ROW;
519                 int currentProgress = 0;
520                 int totalProgress = myTargets.size() * SCSI_CONFIG_ROWS;
521                 for (size_t i = 0;
522                         i < myTargets.size();
523                         ++i, flashRow += SCSI_CONFIG_ROWS)
524                 {
525                         std::vector<uint8_t> raw(sizeof(TargetConfig));
526
527                         for (size_t j = 0; j < SCSI_CONFIG_ROWS; ++j)
528                         {
529                                 std::stringstream ss;
530                                 ss << "Reading flash array " << SCSI_CONFIG_ARRAY <<
531                                         " row " << (flashRow + j);
532                                 SetStatusText(ss.str());
533                                 std::clog << ss.str() << std::endl;
534                                 currentProgress += 1;
535                                 if (!progress->Update(
536                                                 (100 * currentProgress) / totalProgress,
537                                                 ss.str()
538                                                 )
539                                         )
540                                 {
541                                         goto abort;
542                                 }
543
544                                 std::vector<uint8_t> flashData;
545
546                                 try
547                                 {
548                                         myHID->readFlashRow(
549                                                 SCSI_CONFIG_ARRAY, flashRow + j, flashData);
550
551                                 }
552                                 catch (std::runtime_error& e)
553                                 {
554                                         SetStatusText(e.what());
555                                         goto err;
556                                 }
557
558                                 std::copy(
559                                         flashData.begin(),
560                                         flashData.end(),
561                                         &raw[j * SCSI_CONFIG_ROW_SIZE]);
562                         }
563                         myTargets[i]->setConfig(ConfigUtil::fromBytes(&raw[0]));
564                 }
565
566                 myInitialConfig = true;
567                 SetStatusText("Load Complete");
568                 std::clog << "Load Complete" << std::endl;
569                 while (progress->Update(100, "Load Complete"))
570                 {
571                         // Wait for the user to click "Close"
572                         wxMilliSleep(50);
573                 }
574                 goto out;
575
576         err:
577                 SetStatusText("Load failed");
578                 std::clog << "Load failed" << std::endl;
579                 while (progress->Update(100, "Load failed"))
580                 {
581                         // Wait for the user to click "Close"
582                         wxMilliSleep(50);
583                 }
584                 goto out;
585
586         abort:
587                 SetStatusText("Load Aborted");
588                 std::clog << "Load Aborted" << std::endl;
589
590         out:
591                 return;
592         }
593
594         void doSave(wxCommandEvent& event)
595         {
596                 TimerLock lock(myTimer);
597                 if (!myHID) return;
598
599                 std::clog << "Saving configuration" << std::endl;
600
601                 wxWindowPtr<wxGenericProgressDialog> progress(
602                         new wxGenericProgressDialog(
603                                 "Save config settings",
604                                 "Saving config settings",
605                                 100,
606                                 this,
607                                 wxPD_APP_MODAL | wxPD_CAN_ABORT)
608                                 );
609
610                 int flashRow = SCSI_CONFIG_0_ROW;
611                 int currentProgress = 0;
612                 int totalProgress = myTargets.size() * SCSI_CONFIG_ROWS;
613                 for (size_t i = 0;
614                         i < myTargets.size();
615                         ++i, flashRow += SCSI_CONFIG_ROWS)
616                 {
617                         TargetConfig config(myTargets[i]->getConfig());
618                         std::vector<uint8_t> raw(ConfigUtil::toBytes(config));
619
620                         for (size_t j = 0; j < SCSI_CONFIG_ROWS; ++j)
621                         {
622                                 std::stringstream ss;
623                                 ss << "Programming flash array " << SCSI_CONFIG_ARRAY <<
624                                         " row " << (flashRow + j);
625                                 SetStatusText(ss.str());
626                                 std::clog << ss.str() << std::endl;
627                                 currentProgress += 1;
628                                 if (!progress->Update(
629                                                 (100 * currentProgress) / totalProgress,
630                                                 ss.str()
631                                                 )
632                                         )
633                                 {
634                                         goto abort;
635                                 }
636
637                                 std::vector<uint8_t> flashData(SCSI_CONFIG_ROW_SIZE, 0);
638                                 std::copy(
639                                         &raw[j * SCSI_CONFIG_ROW_SIZE],
640                                         &raw[(1+j) * SCSI_CONFIG_ROW_SIZE],
641                                         flashData.begin());
642                                 try
643                                 {
644                                         myHID->writeFlashRow(
645                                                 SCSI_CONFIG_ARRAY, flashRow + j, flashData);
646                                 }
647                                 catch (std::runtime_error& e)
648                                 {
649                                         std::clog << e.what() << std::endl;
650                                         SetStatusText(e.what());
651                                         goto err;
652                                 }
653                         }
654                 }
655
656                 // Reboot so new settings take effect.
657                 myHID->enterBootloader();
658                 myHID.reset();
659
660                 SetStatusText("Save Complete");
661                 std::clog << "Save Complete" << std::endl;
662                 while (progress->Update(100, "Save Complete"))
663                 {
664                         // Wait for the user to click "Close"
665                         wxMilliSleep(50);
666                 }
667                 goto out;
668
669         err:
670                 SetStatusText("Save failed");
671                 std::clog << "Save failed" << std::endl;
672                 while (progress->Update(100, "Save failed"))
673                 {
674                         // Wait for the user to click "Close"
675                         wxMilliSleep(50);
676                 }
677                 goto out;
678
679         abort:
680                 SetStatusText("Save Aborted");
681                 std::clog << "Save Aborted" << std::endl;
682
683         out:
684                 (void) true; // empty statement.
685         }
686
687         void OnExit(wxCommandEvent& event)
688         {
689                 Close(true);
690         }
691         void OnAbout(wxCommandEvent& event)
692         {
693                 wxMessageBox(
694                         "SCSI2SD (scsi2sd-util)\n"
695                         "Copyright (C) 2014 Michael McMaster <michael@codesrc.com>\n"
696                         "\n"
697 "This program is free software: you can redistribute it and/or modify\n"
698 "it under the terms of the GNU General Public License as published by\n"
699 "the Free Software Foundation, either version 3 of the License, or\n"
700 "(at your option) any later version.\n"
701 "\n"
702 "This program is distributed in the hope that it will be useful,\n"
703 "but WITHOUT ANY WARRANTY; without even the implied warranty of\n"
704 "MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n"
705 "GNU General Public License for more details.\n"
706 "\n"
707 "You should have received a copy of the GNU General Public License\n"
708 "along with this program.  If not, see <http://www.gnu.org/licenses/>.\n",
709
710                         "About scsi2sd-util", wxOK | wxICON_INFORMATION );
711         }
712
713         wxDECLARE_EVENT_TABLE();
714 };
715
716 wxBEGIN_EVENT_TABLE(AppFrame, wxFrame)
717         EVT_MENU(AppFrame::ID_ConfigDefaults, AppFrame::OnID_ConfigDefaults)
718         EVT_MENU(AppFrame::ID_Firmware, AppFrame::OnID_Firmware)
719         EVT_MENU(wxID_EXIT, AppFrame::OnExit)
720         EVT_MENU(wxID_ABOUT, AppFrame::OnAbout)
721
722         EVT_TIMER(AppFrame::ID_Timer, AppFrame::OnID_Timer)
723
724         EVT_COMMAND(wxID_ANY, ConfigChangedEvent, AppFrame::onConfigChanged)
725
726         EVT_BUTTON(ID_BtnSave, AppFrame::doSave)
727         EVT_BUTTON(ID_BtnLoad, AppFrame::doLoad)
728
729
730 wxEND_EVENT_TABLE()
731
732
733
734 class App : public wxApp
735 {
736 public:
737         virtual bool OnInit()
738         {
739                 AppFrame* frame = new AppFrame();
740                 frame->Show(true);
741                 SetTopWindow(frame);
742                 return true;
743         }
744 };
745 } // namespace
746
747 // Main Method
748 wxIMPLEMENT_APP(App);
749
750