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"
48 #if __cplusplus >= 201103L
51 using std::shared_ptr;
55 using std::tr1::shared_ptr;
58 #define MIN_FIRMWARE_VERSION 0x0400
60 using namespace SCSI2SD;
65 void setProgressDialog(
66 const wxWindowPtr<wxGenericProgressDialog>& dlg,
69 myProgressDialog = dlg;
74 void clearProgressDialog()
76 myProgressDialog->Show(false);
77 myProgressDialog.reset();
80 void update(unsigned char arrayId, unsigned short rowNum)
82 if (!myProgressDialog) return;
87 ss << "Writing flash array " <<
88 static_cast<int>(arrayId) << " row " <<
89 static_cast<int>(rowNum);
90 wxLogMessage("%s", ss.str());
91 myProgressDialog->Update(myNumRows, ss.str());
95 wxWindowPtr<wxGenericProgressDialog> myProgressDialog;
99 static ProgressWrapper TheProgressWrapper;
102 void ProgressUpdate(unsigned char arrayId, unsigned short rowNum)
104 TheProgressWrapper.update(arrayId, rowNum);
113 TimerLock(wxTimer* timer) :
115 myInterval(myTimer->GetInterval())
122 if (myTimer && myInterval > 0)
124 myTimer->Start(myInterval);
132 class AppFrame : public wxFrame
136 wxFrame(NULL, wxID_ANY, "scsi2sd-util", wxPoint(50, 50), wxSize(600, 650)),
137 myInitialConfig(false),
140 wxMenu *menuFile = new wxMenu();
144 "Load default configuration options.");
147 "&Upgrade Firmware...",
148 "Upgrade or inspect device firmware version.");
149 menuFile->AppendSeparator();
150 menuFile->Append(wxID_EXIT);
152 wxMenu *menuWindow= new wxMenu();
156 "Show debug log window");
158 wxMenu *menuHelp = new wxMenu();
159 menuHelp->Append(wxID_ABOUT);
161 wxMenuBar *menuBar = new wxMenuBar();
162 menuBar->Append( menuFile, "&File" );
163 menuBar->Append( menuWindow, "&Window" );
164 menuBar->Append( menuHelp, "&Help" );
165 SetMenuBar( menuBar );
168 wxLogStatus(this, "Searching for SCSI2SD");
171 wxPanel* cfgPanel = new wxPanel(this);
172 wxFlexGridSizer *fgs = new wxFlexGridSizer(3, 1, 15, 15);
173 cfgPanel->SetSizer(fgs);
175 // Empty space below menu bar.
176 fgs->Add(5, 5, wxALL);
178 wxNotebook* tabs = new wxNotebook(cfgPanel, ID_Notebook);
180 for (int i = 0; i < MAX_SCSI_TARGETS; ++i)
182 TargetPanel* target =
183 new TargetPanel(tabs, ConfigUtil::Default(i));
184 myTargets.push_back(target);
185 std::stringstream ss;
186 ss << "Device " << (i + 1);
187 tabs->AddPage(target, ss.str());
194 wxPanel* btnPanel = new wxPanel(cfgPanel);
195 wxFlexGridSizer *btnFgs = new wxFlexGridSizer(1, 2, 5, 5);
196 btnPanel->SetSizer(btnFgs);
198 new wxButton(btnPanel, ID_BtnLoad, wxT("Load from device"));
199 btnFgs->Add(myLoadButton);
201 new wxButton(btnPanel, ID_BtnSave, wxT("Save to device"));
202 btnFgs->Add(mySaveButton);
208 //Fit(); // Needed to reduce window size on Windows
209 FitInside(); // Needed on Linux to prevent status bar overlap
211 myTimer = new wxTimer(this, ID_Timer);
212 myTimer->Start(1000); //ms
214 myLogWindow = new wxLogWindow(this, wxT("scsi2sd-util debug log"), true);
217 wxLogWindow* myLogWindow;
218 std::vector<TargetPanel*> myTargets;
219 wxButton* myLoadButton;
220 wxButton* mySaveButton;
222 shared_ptr<HID> myHID;
223 shared_ptr<Bootloader> myBootloader;
224 bool myInitialConfig;
226 uint8_t myTickCounter;
228 void onConfigChanged(wxCommandEvent& event)
237 // Check for duplicate SCSI IDs
238 std::set<uint8_t> enabledID;
240 // Check for overlapping SD sectors.
241 std::vector<std::pair<uint32_t, uint64_t> > sdSectors;
243 bool isTargetEnabled = false; // Need at least one enabled
244 uint32_t autoStartSector = 0;
245 for (size_t i = 0; i < myTargets.size(); ++i)
247 myTargets[i]->setAutoStartSector(autoStartSector);
248 valid = myTargets[i]->evaluate() && valid;
250 if (myTargets[i]->isEnabled())
252 isTargetEnabled = true;
253 uint8_t scsiID = myTargets[i]->getSCSIId();
254 if (enabledID.find(scsiID) != enabledID.end())
256 myTargets[i]->setDuplicateID(true);
261 enabledID.insert(scsiID);
262 myTargets[i]->setDuplicateID(false);
265 auto sdSectorRange = myTargets[i]->getSDSectorRange();
266 for (auto it(sdSectors.begin()); it != sdSectors.end(); ++it)
268 if (sdSectorRange.first < it->second &&
269 sdSectorRange.second > it->first)
272 myTargets[i]->setSDSectorOverlap(true);
276 myTargets[i]->setSDSectorOverlap(false);
279 sdSectors.push_back(sdSectorRange);
280 autoStartSector = sdSectorRange.second + 1;
284 myTargets[i]->setDuplicateID(false);
285 myTargets[i]->setSDSectorOverlap(false);
289 valid = valid && isTargetEnabled; // Need at least one.
291 mySaveButton->Enable(
294 (myHID->getFirmwareVersion() >= MIN_FIRMWARE_VERSION));
296 myLoadButton->Enable(
298 (myHID->getFirmwareVersion() >= MIN_FIRMWARE_VERSION));
304 ID_ConfigDefaults = wxID_HIGHEST + 1,
313 void OnID_ConfigDefaults(wxCommandEvent& event)
315 for (size_t i = 0; i < myTargets.size(); ++i)
317 myTargets[i]->setConfig(ConfigUtil::Default(i));
321 void OnID_Firmware(wxCommandEvent& event)
323 TimerLock lock(myTimer);
327 void OnID_LogWindow(wxCommandEvent& event)
332 void doFirmwareUpdate()
336 "Load firmware file",
339 "SCSI2SD Firmware files (*.scsi2sd;*.cyacd)|*.cyacd;*.scsi2sd",
340 wxFD_OPEN | wxFD_FILE_MUST_EXIST);
341 if (dlg.ShowModal() == wxID_CANCEL) return;
343 std::string filename(dlg.GetPath());
345 wxWindowPtr<wxGenericProgressDialog> progress(
346 new wxGenericProgressDialog(
347 "Searching for bootloader",
348 "Searching for bootloader",
351 wxPD_AUTO_HIDE | wxPD_CAN_ABORT)
353 wxLogStatus(this, "Searching for bootloader");
358 if (!myHID) myHID.reset(HID::Open());
361 wxLogStatus(this, "Resetting SCSI2SD into bootloader");
363 myHID->enterBootloader();
370 myBootloader.reset(Bootloader::Open());
373 wxLogStatus(this, "Bootloader found");
378 else if (myBootloader)
380 // Verify the USB HID connection is valid
381 if (!myBootloader->ping())
383 wxLogStatus(this, "Bootloader ping failed");
384 myBootloader.reset();
388 wxLogStatus(this, "Bootloader found");
393 catch (std::exception& e)
395 wxLogStatus(this, "%s", e.what());
397 myBootloader.reset();
400 if (!progress->Pulse())
402 return; // user cancelled.
406 int totalFlashRows = 0;
410 zipper::ReaderPtr reader(new zipper::FileReader(filename));
411 zipper::Decompressor decomp(reader);
412 std::vector<zipper::CompressedFilePtr> files(decomp.getEntries());
413 for (auto it(files.begin()); it != files.end(); it++)
415 if (myBootloader->isCorrectFirmware((*it)->getPath()))
418 "Found firmware entry %s within archive %s",
422 wxFileName::CreateTempFileName(
423 wxT("SCSI2SD_Firmware"), static_cast<wxFile*>(NULL)
425 zipper::FileWriter out(tmpFile);
426 (*it)->decompress(out);
428 "Firmware extracted to %s",
436 // TODO allow "force" option
440 wxOK | wxICON_ERROR);
444 Firmware firmware(tmpFile);
445 totalFlashRows = firmware.totalFlashRows();
447 catch (std::exception& e)
449 wxLogStatus(this, "%s", e.what());
450 std::stringstream msg;
451 msg << "Could not open firmware file: " << e.what();
455 wxOK | wxICON_ERROR);
456 wxRemoveFile(tmpFile);
461 wxWindowPtr<wxGenericProgressDialog> progress(
462 new wxGenericProgressDialog(
467 wxPD_AUTO_HIDE | wxPD_REMAINING_TIME)
469 TheProgressWrapper.setProgressDialog(progress, totalFlashRows);
472 wxLogStatus(this, "Upgrading firmware from file: %s", tmpFile);
476 myBootloader->load(tmpFile, &ProgressUpdate);
477 TheProgressWrapper.clearProgressDialog();
480 "Firmware update successful",
483 wxLogStatus(this, "Firmware update successful");
487 myBootloader.reset();
489 catch (std::exception& e)
491 TheProgressWrapper.clearProgressDialog();
492 wxLogStatus(this, "%s", e.what());
494 myBootloader.reset();
497 "Firmware Update Failed",
499 wxOK | wxICON_ERROR);
501 wxRemoveFile(tmpFile);
505 void OnID_Timer(wxTimerEvent& event)
507 // Check if we are connected to the HID device.
508 // AND/or bootloader device.
513 // Verify the USB HID connection is valid
514 if (!myBootloader->ping())
516 myBootloader.reset();
522 myBootloader.reset(Bootloader::Open());
526 wxLogStatus(this, "%s", "SCSI2SD Bootloader Ready");
531 if (myHID && myHID->getFirmwareVersion() < MIN_FIRMWARE_VERSION)
533 // No method to check connection is still valid.
534 // So assume it isn't.
538 else if (myHID && !myHID->ping())
540 // Verify the USB HID connection is valid
546 myHID.reset(HID::Open());
549 if (myHID->getFirmwareVersion() < MIN_FIRMWARE_VERSION)
553 // Oh dear, old firmware
556 "Firmware update required. Version %s",
557 myHID->getFirmwareVersionStr());
564 "SCSI2SD Ready, firmware version %s",
565 myHID->getFirmwareVersionStr());
567 if (!myInitialConfig)
569 wxCommandEvent loadEvent(wxEVT_NULL, ID_BtnLoad);
570 GetEventHandler()->AddPendingEvent(loadEvent);
576 char ticks[] = {'/', '-', '\\', '|'};
577 std::stringstream ss;
578 ss << "Searching for SCSI2SD device " << ticks[myTickCounter % sizeof(ticks)];
580 SetStatusText(ss.str());
584 catch (std::runtime_error& e)
586 wxLogStatus(this, "%s", e.what());
592 void doLoad(wxCommandEvent& event)
594 TimerLock lock(myTimer);
597 wxLogStatus(this, "Loading configuration");
599 wxWindowPtr<wxGenericProgressDialog> progress(
600 new wxGenericProgressDialog(
601 "Load config settings",
602 "Loading config settings",
605 wxPD_CAN_ABORT | wxPD_REMAINING_TIME)
608 int flashRow = SCSI_CONFIG_0_ROW;
609 int currentProgress = 0;
610 int totalProgress = myTargets.size() * SCSI_CONFIG_ROWS;
612 i < myTargets.size();
613 ++i, flashRow += SCSI_CONFIG_ROWS)
615 std::vector<uint8_t> raw(sizeof(TargetConfig));
617 for (size_t j = 0; j < SCSI_CONFIG_ROWS; ++j)
619 std::stringstream ss;
620 ss << "Reading flash array " << SCSI_CONFIG_ARRAY <<
621 " row " << (flashRow + j);
622 wxLogStatus(this, "%s", ss.str());
623 currentProgress += 1;
624 if (!progress->Update(
625 (100 * currentProgress) / totalProgress,
633 std::vector<uint8_t> flashData;
638 SCSI_CONFIG_ARRAY, flashRow + j, flashData);
641 catch (std::runtime_error& e)
643 wxLogStatus(this, "%s", e.what());
650 &raw[j * SCSI_CONFIG_ROW_SIZE]);
652 myTargets[i]->setConfig(ConfigUtil::fromBytes(&raw[0]));
655 myInitialConfig = true;
656 wxLogStatus(this, "%s", "Load Complete");
657 while (progress->Update(100, "Load Complete"))
659 // Wait for the user to click "Close"
665 wxLogStatus(this, "%s", "Load failed");
666 while (progress->Update(100, "Load failed"))
668 // Wait for the user to click "Close"
674 wxLogStatus(this, "Load Aborted");
680 void doSave(wxCommandEvent& event)
682 TimerLock lock(myTimer);
685 wxLogStatus(this, "Saving configuration");
687 wxWindowPtr<wxGenericProgressDialog> progress(
688 new wxGenericProgressDialog(
689 "Save config settings",
690 "Saving config settings",
693 wxPD_CAN_ABORT | wxPD_REMAINING_TIME)
696 int flashRow = SCSI_CONFIG_0_ROW;
697 int currentProgress = 0;
698 int totalProgress = myTargets.size() * SCSI_CONFIG_ROWS;
700 i < myTargets.size();
701 ++i, flashRow += SCSI_CONFIG_ROWS)
703 TargetConfig config(myTargets[i]->getConfig());
704 std::vector<uint8_t> raw(ConfigUtil::toBytes(config));
706 for (size_t j = 0; j < SCSI_CONFIG_ROWS; ++j)
708 std::stringstream ss;
709 ss << "Programming flash array " << SCSI_CONFIG_ARRAY <<
710 " row " << (flashRow + j);
711 wxLogStatus(this, "%s", ss.str());
712 currentProgress += 1;
713 if (!progress->Update(
714 (100 * currentProgress) / totalProgress,
722 std::vector<uint8_t> flashData(SCSI_CONFIG_ROW_SIZE, 0);
724 &raw[j * SCSI_CONFIG_ROW_SIZE],
725 &raw[(1+j) * SCSI_CONFIG_ROW_SIZE],
729 myHID->writeFlashRow(
730 SCSI_CONFIG_ARRAY, flashRow + j, flashData);
732 catch (std::runtime_error& e)
734 wxLogStatus(this, "%s", e.what());
740 // Reboot so new settings take effect.
741 myHID->enterBootloader();
744 wxLogStatus(this, "Save Complete");
745 while (progress->Update(100, "Save Complete"))
747 // Wait for the user to click "Close"
753 wxLogStatus(this, "Save failed");
754 while (progress->Update(100, "Save failed"))
756 // Wait for the user to click "Close"
762 wxLogStatus(this, "Save Aborted");
765 (void) true; // empty statement.
768 void OnExit(wxCommandEvent& event)
772 void OnAbout(wxCommandEvent& event)
775 "SCSI2SD (scsi2sd-util)\n"
776 "Copyright (C) 2014 Michael McMaster <michael@codesrc.com>\n"
778 "This program is free software: you can redistribute it and/or modify\n"
779 "it under the terms of the GNU General Public License as published by\n"
780 "the Free Software Foundation, either version 3 of the License, or\n"
781 "(at your option) any later version.\n"
783 "This program is distributed in the hope that it will be useful,\n"
784 "but WITHOUT ANY WARRANTY; without even the implied warranty of\n"
785 "MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n"
786 "GNU General Public License for more details.\n"
788 "You should have received a copy of the GNU General Public License\n"
789 "along with this program. If not, see <http://www.gnu.org/licenses/>.\n",
791 "About scsi2sd-util", wxOK | wxICON_INFORMATION );
794 wxDECLARE_EVENT_TABLE();
797 wxBEGIN_EVENT_TABLE(AppFrame, wxFrame)
798 EVT_MENU(AppFrame::ID_ConfigDefaults, AppFrame::OnID_ConfigDefaults)
799 EVT_MENU(AppFrame::ID_Firmware, AppFrame::OnID_Firmware)
800 EVT_MENU(AppFrame::ID_LogWindow, AppFrame::OnID_LogWindow)
801 EVT_MENU(wxID_EXIT, AppFrame::OnExit)
802 EVT_MENU(wxID_ABOUT, AppFrame::OnAbout)
804 EVT_TIMER(AppFrame::ID_Timer, AppFrame::OnID_Timer)
806 EVT_COMMAND(wxID_ANY, ConfigChangedEvent, AppFrame::onConfigChanged)
808 EVT_BUTTON(ID_BtnSave, AppFrame::doSave)
809 EVT_BUTTON(ID_BtnLoad, AppFrame::doLoad)
816 class App : public wxApp
819 virtual bool OnInit()
821 AppFrame* frame = new AppFrame();
830 wxIMPLEMENT_APP(App);