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 void OnExit(wxCommandEvent& event)
856 void OnAbout(wxCommandEvent& event)
859 "SCSI2SD (scsi2sd-util)\n"
860 "Copyright (C) 2014 Michael McMaster <michael@codesrc.com>\n"
862 "This program is free software: you can redistribute it and/or modify\n"
863 "it under the terms of the GNU General Public License as published by\n"
864 "the Free Software Foundation, either version 3 of the License, or\n"
865 "(at your option) any later version.\n"
867 "This program is distributed in the hope that it will be useful,\n"
868 "but WITHOUT ANY WARRANTY; without even the implied warranty of\n"
869 "MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n"
870 "GNU General Public License for more details.\n"
872 "You should have received a copy of the GNU General Public License\n"
873 "along with this program. If not, see <http://www.gnu.org/licenses/>.\n",
875 "About scsi2sd-util", wxOK | wxICON_INFORMATION );
878 wxDECLARE_EVENT_TABLE();
881 wxBEGIN_EVENT_TABLE(AppFrame, wxFrame)
882 EVT_MENU(AppFrame::ID_ConfigDefaults, AppFrame::OnID_ConfigDefaults)
883 EVT_MENU(AppFrame::ID_Firmware, AppFrame::OnID_Firmware)
884 EVT_MENU(AppFrame::ID_LogWindow, AppFrame::OnID_LogWindow)
885 EVT_MENU(wxID_EXIT, AppFrame::OnExit)
886 EVT_MENU(wxID_ABOUT, AppFrame::OnAbout)
888 EVT_TIMER(AppFrame::ID_Timer, AppFrame::OnID_Timer)
890 EVT_COMMAND(wxID_ANY, ConfigChangedEvent, AppFrame::onConfigChanged)
892 EVT_BUTTON(ID_BtnSave, AppFrame::doSave)
893 EVT_BUTTON(ID_BtnLoad, AppFrame::doLoad)
900 class App : public wxApp
903 virtual bool OnInit()
905 AppFrame* frame = new AppFrame();
914 wxIMPLEMENT_APP(App);