1 // Copyright (C) 2014 Michael McMaster <michael@codesrc.com>
3 // This file is part of SCSI2SD.
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.
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.
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/>.
19 // For compilers that support precompilation, includes "wx/wx.h".
20 #include <wx/wxprec.h>
25 #include <wx/filedlg.h>
26 #include <wx/filefn.h>
27 #include <wx/filename.h>
29 #include <wx/notebook.h>
30 #include <wx/progdlg.h>
32 #include <wx/windowptr.h>
33 #include <wx/thread.h>
37 #include "ConfigUtil.hh"
38 #include "TargetPanel.hh"
39 #include "SCSI2SD_Bootloader.hh"
40 #include "SCSI2SD_HID.hh"
41 #include "Firmware.hh"
49 #if __cplusplus >= 201103L
52 using std::shared_ptr
;
56 using std::tr1::shared_ptr
;
59 #define MIN_FIRMWARE_VERSION 0x0400
61 using namespace SCSI2SD
;
66 void setProgressDialog(
67 const wxWindowPtr
<wxGenericProgressDialog
>& dlg
,
70 myProgressDialog
= dlg
;
75 void clearProgressDialog()
77 myProgressDialog
->Show(false);
78 myProgressDialog
.reset();
81 void update(unsigned char arrayId
, unsigned short rowNum
)
83 if (!myProgressDialog
) return;
88 ss
<< "Writing flash array " <<
89 static_cast<int>(arrayId
) << " row " <<
90 static_cast<int>(rowNum
);
91 wxLogMessage("%s", ss
.str());
92 myProgressDialog
->Update(myNumRows
, ss
.str());
96 wxWindowPtr
<wxGenericProgressDialog
> myProgressDialog
;
100 static ProgressWrapper TheProgressWrapper
;
103 void ProgressUpdate(unsigned char arrayId
, unsigned short rowNum
)
105 TheProgressWrapper
.update(arrayId
, rowNum
);
114 TimerLock(wxTimer
* timer
) :
116 myInterval(myTimer
->GetInterval())
123 if (myTimer
&& myInterval
> 0)
125 myTimer
->Start(myInterval
);
133 class AppFrame
: public wxFrame
137 wxFrame(NULL
, wxID_ANY
, "scsi2sd-util", wxPoint(50, 50), wxSize(600, 650)),
138 myInitialConfig(false),
142 wxMenu
*menuFile
= new wxMenu();
146 "Load default configuration options.");
149 "&Upgrade Firmware...",
150 "Upgrade or inspect device firmware version.");
151 menuFile
->AppendSeparator();
152 menuFile
->Append(wxID_EXIT
);
154 wxMenu
*menuWindow
= new wxMenu();
158 "Show debug log window");
160 wxMenu
*menuDebug
= new wxMenu();
161 mySCSILogChk
= menuDebug
->AppendCheckItem(
164 "Log SCSI commands");
166 wxMenu
*menuHelp
= new wxMenu();
167 menuHelp
->Append(wxID_ABOUT
);
169 wxMenuBar
*menuBar
= new wxMenuBar();
170 menuBar
->Append( menuFile
, "&File" );
171 menuBar
->Append( menuDebug
, "&Debug" );
172 menuBar
->Append( menuWindow
, "&Window" );
173 menuBar
->Append( menuHelp
, "&Help" );
174 SetMenuBar( menuBar
);
179 wxPanel
* cfgPanel
= new wxPanel(this);
180 wxFlexGridSizer
*fgs
= new wxFlexGridSizer(3, 1, 15, 15);
181 cfgPanel
->SetSizer(fgs
);
183 // Empty space below menu bar.
184 fgs
->Add(5, 5, wxALL
);
186 wxNotebook
* tabs
= new wxNotebook(cfgPanel
, ID_Notebook
);
188 for (int i
= 0; i
< MAX_SCSI_TARGETS
; ++i
)
190 TargetPanel
* target
=
191 new TargetPanel(tabs
, ConfigUtil::Default(i
));
192 myTargets
.push_back(target
);
193 std::stringstream ss
;
194 ss
<< "Device " << (i
+ 1);
195 tabs
->AddPage(target
, ss
.str());
202 wxPanel
* btnPanel
= new wxPanel(cfgPanel
);
203 wxFlexGridSizer
*btnFgs
= new wxFlexGridSizer(1, 2, 5, 5);
204 btnPanel
->SetSizer(btnFgs
);
206 new wxButton(btnPanel
, ID_BtnLoad
, wxT("Load from device"));
207 btnFgs
->Add(myLoadButton
);
209 new wxButton(btnPanel
, ID_BtnSave
, wxT("Save to device"));
210 btnFgs
->Add(mySaveButton
);
216 //Fit(); // Needed to reduce window size on Windows
217 FitInside(); // Needed on Linux to prevent status bar overlap
219 myLogWindow
= new wxLogWindow(this, wxT("scsi2sd-util debug log"), true);
220 myLogWindow
->PassMessages(false); // Prevent messagebox popups
222 myTimer
= new wxTimer(this, ID_Timer
);
223 myTimer
->Start(16); //ms, suitable for scsi debug logging
227 wxLogWindow
* myLogWindow
;
228 std::vector
<TargetPanel
*> myTargets
;
229 wxButton
* myLoadButton
;
230 wxButton
* mySaveButton
;
231 wxMenuItem
* mySCSILogChk
;
233 shared_ptr
<HID
> myHID
;
234 shared_ptr
<Bootloader
> myBootloader
;
235 bool myInitialConfig
;
237 uint8_t myTickCounter
;
239 time_t myLastPollTime
;
241 void mmLogStatus(const std::string
& msg
)
243 // We set PassMessages to false on our log window to prevent popups, but
244 // this also prevents wxLogStatus from updating the status bar.
246 wxLogMessage(this, "%s", msg
.c_str());
249 void onConfigChanged(wxCommandEvent
& event
)
258 // Check for duplicate SCSI IDs
259 std::set
<uint8_t> enabledID
;
261 // Check for overlapping SD sectors.
262 std::vector
<std::pair
<uint32_t, uint64_t> > sdSectors
;
264 bool isTargetEnabled
= false; // Need at least one enabled
265 uint32_t autoStartSector
= 0;
266 for (size_t i
= 0; i
< myTargets
.size(); ++i
)
268 myTargets
[i
]->setAutoStartSector(autoStartSector
);
269 valid
= myTargets
[i
]->evaluate() && valid
;
271 if (myTargets
[i
]->isEnabled())
273 isTargetEnabled
= true;
274 uint8_t scsiID
= myTargets
[i
]->getSCSIId();
275 if (enabledID
.find(scsiID
) != enabledID
.end())
277 myTargets
[i
]->setDuplicateID(true);
282 enabledID
.insert(scsiID
);
283 myTargets
[i
]->setDuplicateID(false);
286 auto sdSectorRange
= myTargets
[i
]->getSDSectorRange();
287 for (auto it(sdSectors
.begin()); it
!= sdSectors
.end(); ++it
)
289 if (sdSectorRange
.first
< it
->second
&&
290 sdSectorRange
.second
> it
->first
)
293 myTargets
[i
]->setSDSectorOverlap(true);
297 myTargets
[i
]->setSDSectorOverlap(false);
300 sdSectors
.push_back(sdSectorRange
);
301 autoStartSector
= sdSectorRange
.second
+ 1;
305 myTargets
[i
]->setDuplicateID(false);
306 myTargets
[i
]->setSDSectorOverlap(false);
310 valid
= valid
&& isTargetEnabled
; // Need at least one.
312 mySaveButton
->Enable(
315 (myHID
->getFirmwareVersion() >= MIN_FIRMWARE_VERSION
));
317 myLoadButton
->Enable(
319 (myHID
->getFirmwareVersion() >= MIN_FIRMWARE_VERSION
));
325 ID_ConfigDefaults
= wxID_HIGHEST
+ 1,
335 void OnID_ConfigDefaults(wxCommandEvent
& event
)
337 for (size_t i
= 0; i
< myTargets
.size(); ++i
)
339 myTargets
[i
]->setConfig(ConfigUtil::Default(i
));
343 void OnID_Firmware(wxCommandEvent
& event
)
345 TimerLock
lock(myTimer
);
349 void OnID_LogWindow(wxCommandEvent
& event
)
354 void doFirmwareUpdate()
358 "Load firmware file",
361 "SCSI2SD Firmware files (*.scsi2sd;*.cyacd)|*.cyacd;*.scsi2sd",
362 wxFD_OPEN
| wxFD_FILE_MUST_EXIST
);
363 if (dlg
.ShowModal() == wxID_CANCEL
) return;
365 std::string
filename(dlg
.GetPath());
367 wxWindowPtr
<wxGenericProgressDialog
> progress(
368 new wxGenericProgressDialog(
369 "Searching for bootloader",
370 "Searching for bootloader",
373 wxPD_AUTO_HIDE
| wxPD_CAN_ABORT
)
375 mmLogStatus("Searching for bootloader");
380 if (!myHID
) myHID
.reset(HID::Open());
383 mmLogStatus("Resetting SCSI2SD into bootloader");
385 myHID
->enterBootloader();
392 myBootloader
.reset(Bootloader::Open());
395 mmLogStatus("Bootloader found");
400 else if (myBootloader
)
402 // Verify the USB HID connection is valid
403 if (!myBootloader
->ping())
405 mmLogStatus("Bootloader ping failed");
406 myBootloader
.reset();
410 mmLogStatus("Bootloader found");
415 catch (std::exception
& e
)
417 mmLogStatus(e
.what());
419 myBootloader
.reset();
422 if (!progress
->Pulse())
424 return; // user cancelled.
428 int totalFlashRows
= 0;
432 zipper::ReaderPtr
reader(new zipper::FileReader(filename
));
433 zipper::Decompressor
decomp(reader
);
434 std::vector
<zipper::CompressedFilePtr
> files(decomp
.getEntries());
435 for (auto it(files
.begin()); it
!= files
.end(); it
++)
437 if (myBootloader
->isCorrectFirmware((*it
)->getPath()))
439 std::stringstream msg
;
440 msg
<< "Found firmware entry " << (*it
)->getPath() <<
441 " within archive " << filename
;
442 mmLogStatus(msg
.str());
444 wxFileName::CreateTempFileName(
445 wxT("SCSI2SD_Firmware"), static_cast<wxFile
*>(NULL
)
447 zipper::FileWriter
out(tmpFile
);
448 (*it
)->decompress(out
);
450 msg
<< "Firmware extracted to " << tmpFile
;
451 mmLogStatus(msg
.str());
458 // TODO allow "force" option
462 wxOK
| wxICON_ERROR
);
466 Firmware
firmware(tmpFile
);
467 totalFlashRows
= firmware
.totalFlashRows();
469 catch (std::exception
& e
)
471 mmLogStatus(e
.what());
472 std::stringstream msg
;
473 msg
<< "Could not open firmware file: " << e
.what();
477 wxOK
| wxICON_ERROR
);
478 wxRemoveFile(tmpFile
);
483 wxWindowPtr
<wxGenericProgressDialog
> progress(
484 new wxGenericProgressDialog(
489 wxPD_AUTO_HIDE
| wxPD_REMAINING_TIME
)
491 TheProgressWrapper
.setProgressDialog(progress
, totalFlashRows
);
494 std::stringstream msg
;
495 msg
<< "Upgrading firmware from file: " << tmpFile
;
496 mmLogStatus(msg
.str());
500 myBootloader
->load(tmpFile
, &ProgressUpdate
);
501 TheProgressWrapper
.clearProgressDialog();
504 "Firmware update successful",
507 mmLogStatus("Firmware update successful");
511 myBootloader
.reset();
513 catch (std::exception
& e
)
515 TheProgressWrapper
.clearProgressDialog();
516 mmLogStatus(e
.what());
518 myBootloader
.reset();
521 "Firmware Update Failed",
523 wxOK
| wxICON_ERROR
);
525 wxRemoveFile(tmpFile
);
531 if (!mySCSILogChk
->IsChecked() ||
538 std::vector
<uint8_t> info(HID::HID_PACKET_SIZE
);
539 if (myHID
->readSCSIDebugInfo(info
))
541 std::stringstream msg
;
543 for (size_t i
= 0; i
< 32 && i
< info
.size(); ++i
)
545 msg
<< std::setfill('0') << std::setw(2) <<
546 static_cast<int>(info
[i
]) << ' ';
548 wxLogMessage(this, msg
.str().c_str());
551 catch (std::exception
& e
)
553 wxLogWarning(this, e
.what());
558 void OnID_Timer(wxTimerEvent
& event
)
561 time_t now
= time(NULL
);
562 if (now
== myLastPollTime
) return;
563 myLastPollTime
= now
;
565 // Check if we are connected to the HID device.
566 // AND/or bootloader device.
571 // Verify the USB HID connection is valid
572 if (!myBootloader
->ping())
574 myBootloader
.reset();
580 myBootloader
.reset(Bootloader::Open());
584 mmLogStatus("SCSI2SD Bootloader Ready");
589 if (myHID
&& myHID
->getFirmwareVersion() < MIN_FIRMWARE_VERSION
)
591 // No method to check connection is still valid.
592 // So assume it isn't.
596 else if (myHID
&& !myHID
->ping())
598 // Verify the USB HID connection is valid
604 myHID
.reset(HID::Open());
607 if (myHID
->getFirmwareVersion() < MIN_FIRMWARE_VERSION
)
611 // Oh dear, old firmware
612 std::stringstream msg
;
613 msg
<< "Firmware update required. Version " <<
614 myHID
->getFirmwareVersionStr();
615 mmLogStatus(msg
.str());
620 std::stringstream msg
;
621 msg
<< "SCSI2SD Ready, firmware version " <<
622 myHID
->getFirmwareVersionStr();
623 mmLogStatus(msg
.str());
625 std::vector
<uint8_t> csd(myHID
->getSD_CSD());
626 std::vector
<uint8_t> cid(myHID
->getSD_CID());
627 std::stringstream sdinfo
;
628 sdinfo
<< "SD Capacity (512-byte sectors): " <<
629 myHID
->getSDCapacity() << std::endl
;
631 sdinfo
<< "SD CSD Register: ";
632 for (size_t i
= 0; i
< csd
.size(); ++i
)
635 std::hex
<< std::setfill('0') << std::setw(2) <<
636 static_cast<int>(csd
[i
]);
639 sdinfo
<< "SD CID Register: ";
640 for (size_t i
= 0; i
< cid
.size(); ++i
)
643 std::hex
<< std::setfill('0') << std::setw(2) <<
644 static_cast<int>(cid
[i
]);
647 wxLogMessage(this, "%s", sdinfo
.str());
649 if (!myInitialConfig
)
651 wxCommandEvent
loadEvent(wxEVT_NULL
, ID_BtnLoad
);
652 GetEventHandler()->AddPendingEvent(loadEvent
);
659 char ticks
[] = {'/', '-', '\\', '|'};
660 std::stringstream ss
;
661 ss
<< "Searching for SCSI2SD device " << ticks
[myTickCounter
% sizeof(ticks
)];
663 SetStatusText(ss
.str());
667 catch (std::runtime_error
& e
)
669 std::cerr
<< e
.what() << std::endl
;
670 mmLogStatus(e
.what());
676 void doLoad(wxCommandEvent
& event
)
678 TimerLock
lock(myTimer
);
681 mmLogStatus("Loading configuration");
683 wxWindowPtr
<wxGenericProgressDialog
> progress(
684 new wxGenericProgressDialog(
685 "Load config settings",
686 "Loading config settings",
689 wxPD_CAN_ABORT
| wxPD_REMAINING_TIME
)
692 int flashRow
= SCSI_CONFIG_0_ROW
;
693 int currentProgress
= 0;
694 int totalProgress
= myTargets
.size() * SCSI_CONFIG_ROWS
;
696 i
< myTargets
.size();
697 ++i
, flashRow
+= SCSI_CONFIG_ROWS
)
699 std::vector
<uint8_t> raw(sizeof(TargetConfig
));
701 for (size_t j
= 0; j
< SCSI_CONFIG_ROWS
; ++j
)
703 std::stringstream ss
;
704 ss
<< "Reading flash array " << SCSI_CONFIG_ARRAY
<<
705 " row " << (flashRow
+ j
);
706 mmLogStatus(ss
.str());
707 currentProgress
+= 1;
708 if (!progress
->Update(
709 (100 * currentProgress
) / totalProgress
,
717 std::vector
<uint8_t> flashData
;
722 SCSI_CONFIG_ARRAY
, flashRow
+ j
, flashData
);
725 catch (std::runtime_error
& e
)
727 mmLogStatus(e
.what());
734 &raw
[j
* SCSI_CONFIG_ROW_SIZE
]);
736 myTargets
[i
]->setConfig(ConfigUtil::fromBytes(&raw
[0]));
739 myInitialConfig
= true;
740 mmLogStatus("Load Complete");
741 while (progress
->Update(100, "Load Complete"))
743 // Wait for the user to click "Close"
749 mmLogStatus("Load failed");
750 while (progress
->Update(100, "Load failed"))
752 // Wait for the user to click "Close"
758 mmLogStatus("Load Aborted");
764 void doSave(wxCommandEvent
& event
)
766 TimerLock
lock(myTimer
);
769 mmLogStatus("Saving configuration");
771 wxWindowPtr
<wxGenericProgressDialog
> progress(
772 new wxGenericProgressDialog(
773 "Save config settings",
774 "Saving config settings",
777 wxPD_CAN_ABORT
| wxPD_REMAINING_TIME
)
780 int flashRow
= SCSI_CONFIG_0_ROW
;
781 int currentProgress
= 0;
782 int totalProgress
= myTargets
.size() * SCSI_CONFIG_ROWS
;
784 i
< myTargets
.size();
785 ++i
, flashRow
+= SCSI_CONFIG_ROWS
)
787 TargetConfig
config(myTargets
[i
]->getConfig());
788 std::vector
<uint8_t> raw(ConfigUtil::toBytes(config
));
790 for (size_t j
= 0; j
< SCSI_CONFIG_ROWS
; ++j
)
792 std::stringstream ss
;
793 ss
<< "Programming flash array " << SCSI_CONFIG_ARRAY
<<
794 " row " << (flashRow
+ j
);
795 mmLogStatus(ss
.str());
796 currentProgress
+= 1;
797 if (!progress
->Update(
798 (100 * currentProgress
) / totalProgress
,
806 std::vector
<uint8_t> flashData(SCSI_CONFIG_ROW_SIZE
, 0);
808 &raw
[j
* SCSI_CONFIG_ROW_SIZE
],
809 &raw
[(1+j
) * SCSI_CONFIG_ROW_SIZE
],
813 myHID
->writeFlashRow(
814 SCSI_CONFIG_ARRAY
, flashRow
+ j
, flashData
);
816 catch (std::runtime_error
& e
)
818 mmLogStatus(e
.what());
824 // Reboot so new settings take effect.
825 myHID
->enterBootloader();
828 mmLogStatus("Save Complete");
829 while (progress
->Update(100, "Save Complete"))
831 // Wait for the user to click "Close"
837 mmLogStatus("Save failed");
838 while (progress
->Update(100, "Save failed"))
840 // Wait for the user to click "Close"
846 mmLogStatus("Save Aborted");
852 // Note: Don't confuse this with the wxApp::OnExit virtual method
853 void OnExitEvt(wxCommandEvent
& event
)
858 void OnAbout(wxCommandEvent
& event
)
861 "SCSI2SD (scsi2sd-util)\n"
862 "Copyright (C) 2014 Michael McMaster <michael@codesrc.com>\n"
864 "This program is free software: you can redistribute it and/or modify\n"
865 "it under the terms of the GNU General Public License as published by\n"
866 "the Free Software Foundation, either version 3 of the License, or\n"
867 "(at your option) any later version.\n"
869 "This program is distributed in the hope that it will be useful,\n"
870 "but WITHOUT ANY WARRANTY; without even the implied warranty of\n"
871 "MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n"
872 "GNU General Public License for more details.\n"
874 "You should have received a copy of the GNU General Public License\n"
875 "along with this program. If not, see <http://www.gnu.org/licenses/>.\n",
877 "About scsi2sd-util", wxOK
| wxICON_INFORMATION
);
880 wxDECLARE_EVENT_TABLE();
883 wxBEGIN_EVENT_TABLE(AppFrame
, wxFrame
)
884 EVT_MENU(AppFrame::ID_ConfigDefaults
, AppFrame::OnID_ConfigDefaults
)
885 EVT_MENU(AppFrame::ID_Firmware
, AppFrame::OnID_Firmware
)
886 EVT_MENU(AppFrame::ID_LogWindow
, AppFrame::OnID_LogWindow
)
887 EVT_MENU(wxID_EXIT
, AppFrame::OnExitEvt
)
888 EVT_MENU(wxID_ABOUT
, AppFrame::OnAbout
)
890 EVT_TIMER(AppFrame::ID_Timer
, AppFrame::OnID_Timer
)
892 EVT_COMMAND(wxID_ANY
, ConfigChangedEvent
, AppFrame::onConfigChanged
)
894 EVT_BUTTON(ID_BtnSave
, AppFrame::doSave
)
895 EVT_BUTTON(ID_BtnLoad
, AppFrame::doLoad
)
902 class App
: public wxApp
905 virtual bool OnInit()
907 AppFrame
* frame
= new AppFrame();
916 wxIMPLEMENT_APP(App
);