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>
26 #include <wx/filedlg.h>
27 #include <wx/filefn.h>
28 #include <wx/filename.h>
30 #include <wx/notebook.h>
31 #include <wx/progdlg.h>
33 #include <wx/wfstream.h>
34 #include <wx/windowptr.h>
35 #include <wx/thread.h>
36 #include <wx/txtstrm.h>
40 #include "ConfigUtil.hh"
41 #include "BoardPanel.hh"
42 #include "TargetPanel.hh"
43 #include "SCSI2SD_HID.hh"
51 #if __cplusplus >= 201103L
54 using std::shared_ptr;
58 using std::tr1::shared_ptr;
61 #include <libusb-1.0/libusb.h>
63 using namespace SCSI2SD;
68 void setProgressDialog(
69 const wxWindowPtr<wxGenericProgressDialog>& dlg,
72 myProgressDialog = dlg;
77 void clearProgressDialog()
79 myProgressDialog->Show(false);
80 myProgressDialog.reset();
83 void update(unsigned char arrayId, unsigned short rowNum)
85 if (!myProgressDialog) return;
90 ss << "Writing flash array " <<
91 static_cast<int>(arrayId) << " row " <<
92 static_cast<int>(rowNum);
93 wxLogMessage("%s", ss.str());
94 myProgressDialog->Update(myNumRows, ss.str());
98 wxWindowPtr<wxGenericProgressDialog> myProgressDialog;
102 static ProgressWrapper TheProgressWrapper;
105 void ProgressUpdate(unsigned char arrayId, unsigned short rowNum)
107 TheProgressWrapper.update(arrayId, rowNum);
112 bool hasDFUdevice() {
115 libusb_device **list;
116 ssize_t cnt = libusb_get_device_list(NULL, &list);
118 if (cnt < 0) return false;
120 for (i = 0; i < cnt; i++) {
121 libusb_device *device = list[i];
122 libusb_device_descriptor desc;
123 libusb_get_device_descriptor(device, &desc);
124 if (desc.idVendor == 0x0483 && desc.idProduct == 0xdf11) {
130 libusb_free_device_list(list, 1);
136 static uint8_t sdCrc7(uint8_t* chr, uint8_t cnt, uint8_t crc)
139 for(a = 0; a < cnt; a++)
141 uint8_t data = chr[a];
143 for(i = 0; i < 8; i++)
146 if ((data & 0x80) ^ (crc & 0x80))
159 TimerLock(wxTimer* timer) :
161 myInterval(myTimer->GetInterval())
168 if (myTimer && myInterval > 0)
170 myTimer->Start(myInterval);
178 class AppFrame : public wxFrame
182 wxFrame(NULL, wxID_ANY, "scsi2sd-util6", wxPoint(50, 50), wxSize(600, 700)),
183 myInitialConfig(false),
187 wxMenu *menuFile = new wxMenu();
190 _("&Save to file..."),
191 _("Save settings to local file."));
195 _("Load settings from local file."));
196 menuFile->AppendSeparator();
200 _("Load default configuration options."));
203 _("&Upgrade Firmware..."),
204 _("Upgrade or inspect device firmware version."));
205 menuFile->Append(wxID_EXIT);
207 wxMenu *menuWindow= new wxMenu();
211 _("Show debug log window"));
213 wxMenu *menuDebug = new wxMenu();
214 mySCSILogChk = menuDebug->AppendCheckItem(
217 _("Log SCSI commands"));
219 mySelfTestChk = menuDebug->AppendCheckItem(
221 _("SCSI Standalone Self-Test"),
222 _("SCSI Standalone Self-Test"));
224 wxMenu *menuHelp = new wxMenu();
225 menuHelp->Append(wxID_ABOUT);
227 wxMenuBar *menuBar = new wxMenuBar();
228 menuBar->Append( menuFile, _("&File") );
229 menuBar->Append( menuDebug, _("&Debug") );
230 menuBar->Append( menuWindow, _("&Window") );
231 menuBar->Append( menuHelp, _("&Help") );
232 SetMenuBar( menuBar );
237 wxPanel* cfgPanel = new wxPanel(this);
238 wxFlexGridSizer *fgs = new wxFlexGridSizer(3, 1, 15, 15);
239 cfgPanel->SetSizer(fgs);
241 // Empty space below menu bar.
242 fgs->Add(5, 5, wxALL);
244 wxNotebook* tabs = new wxNotebook(cfgPanel, ID_Notebook);
245 myBoardPanel = new BoardPanel(tabs, ConfigUtil::DefaultBoardConfig());
246 tabs->AddPage(myBoardPanel, _("General Settings"));
247 for (int i = 0; i < S2S_MAX_TARGETS; ++i)
249 TargetPanel* target =
250 new TargetPanel(tabs, ConfigUtil::Default(i));
251 myTargets.push_back(target);
252 std::stringstream ss;
253 ss << "Device " << (i + 1);
254 tabs->AddPage(target, ss.str());
261 wxPanel* btnPanel = new wxPanel(cfgPanel);
262 wxFlexGridSizer *btnFgs = new wxFlexGridSizer(1, 2, 5, 5);
263 btnPanel->SetSizer(btnFgs);
265 new wxButton(btnPanel, ID_BtnLoad, _("Load from device"));
266 btnFgs->Add(myLoadButton);
268 new wxButton(btnPanel, ID_BtnSave, _("Save to device"));
269 btnFgs->Add(mySaveButton);
275 //Fit(); // Needed to reduce window size on Windows
276 FitInside(); // Needed on Linux to prevent status bar overlap
278 myLogWindow = new wxLogWindow(this, _("scsi2sd-util6 debug log"), true);
279 myLogWindow->PassMessages(false); // Prevent messagebox popups
281 myTimer = new wxTimer(this, ID_Timer);
282 myTimer->Start(16); //ms, suitable for scsi debug logging
286 wxLogWindow* myLogWindow;
287 BoardPanel* myBoardPanel;
288 std::vector<TargetPanel*> myTargets;
289 wxButton* myLoadButton;
290 wxButton* mySaveButton;
291 wxMenuItem* mySCSILogChk;
292 wxMenuItem* mySelfTestChk;
294 shared_ptr<HID> myHID;
295 bool myInitialConfig;
297 uint8_t myTickCounter;
299 time_t myLastPollTime;
301 void mmLogStatus(const std::string& msg)
303 // We set PassMessages to false on our log window to prevent popups, but
304 // this also prevents wxLogStatus from updating the status bar.
306 wxLogMessage(this, "%s", msg.c_str());
309 void onConfigChanged(wxCommandEvent& event)
318 // Check for duplicate SCSI IDs
319 std::set<uint8_t> enabledID;
321 // Check for overlapping SD sectors.
322 std::vector<std::pair<uint32_t, uint64_t> > sdSectors;
324 bool isTargetEnabled = false; // Need at least one enabled
325 uint32_t autoStartSector = 0;
326 for (size_t i = 0; i < myTargets.size(); ++i)
328 myTargets[i]->setAutoStartSector(autoStartSector);
329 valid = myTargets[i]->evaluate() && valid;
331 if (myTargets[i]->isEnabled())
333 isTargetEnabled = true;
334 uint8_t scsiID = myTargets[i]->getSCSIId();
335 if (enabledID.find(scsiID) != enabledID.end())
337 myTargets[i]->setDuplicateID(true);
342 enabledID.insert(scsiID);
343 myTargets[i]->setDuplicateID(false);
346 auto sdSectorRange = myTargets[i]->getSDSectorRange();
347 for (auto it(sdSectors.begin()); it != sdSectors.end(); ++it)
349 if (sdSectorRange.first < it->second &&
350 sdSectorRange.second > it->first)
353 myTargets[i]->setSDSectorOverlap(true);
357 myTargets[i]->setSDSectorOverlap(false);
360 sdSectors.push_back(sdSectorRange);
361 autoStartSector = sdSectorRange.second;
365 myTargets[i]->setDuplicateID(false);
366 myTargets[i]->setSDSectorOverlap(false);
370 valid = valid && isTargetEnabled; // Need at least one.
372 mySaveButton->Enable(valid && myHID);
374 myLoadButton->Enable(static_cast<bool>(myHID));
380 ID_ConfigDefaults = wxID_HIGHEST + 1,
393 void OnID_ConfigDefaults(wxCommandEvent& event)
395 myBoardPanel->setConfig(ConfigUtil::DefaultBoardConfig());
396 for (size_t i = 0; i < myTargets.size(); ++i)
398 myTargets[i]->setConfig(ConfigUtil::Default(i));
402 void OnID_SaveFile(wxCommandEvent& event)
404 TimerLock lock(myTimer);
410 "Save config settings",
413 "XML files (*.xml)|*.xml",
414 wxFD_SAVE | wxFD_OVERWRITE_PROMPT);
415 if (dlg.ShowModal() == wxID_CANCEL) return;
417 wxFileOutputStream file(dlg.GetPath());
420 wxLogError("Cannot save settings to file '%s'.", dlg.GetPath());
424 wxTextOutputStream s(file);
428 s << ConfigUtil::toXML(myBoardPanel->getConfig());
429 for (size_t i = 0; i < myTargets.size(); ++i)
431 s << ConfigUtil::toXML(myTargets[i]->getConfig());
437 void OnID_OpenFile(wxCommandEvent& event)
439 TimerLock lock(myTimer);
443 "Load config settings",
446 "XML files (*.xml)|*.xml",
447 wxFD_OPEN | wxFD_FILE_MUST_EXIST);
448 if (dlg.ShowModal() == wxID_CANCEL) return;
452 std::pair<S2S_BoardCfg, std::vector<S2S_TargetCfg>> configs(
453 ConfigUtil::fromXML(std::string(dlg.GetPath())));
455 myBoardPanel->setConfig(configs.first);
458 for (i = 0; i < configs.second.size() && i < myTargets.size(); ++i)
460 myTargets[i]->setConfig(configs.second[i]);
463 for (; i < myTargets.size(); ++i)
465 myTargets[i]->setConfig(ConfigUtil::Default(i));
468 catch (std::exception& e)
471 "Cannot load settings from file '%s'.\n%s",
478 wxOK | wxICON_ERROR);
482 void OnID_Firmware(wxCommandEvent& event)
484 TimerLock lock(myTimer);
488 void OnID_LogWindow(wxCommandEvent& event)
493 void doFirmwareUpdate()
497 "Load firmware file",
500 "SCSI2SD Firmware files (*.dfu)|*.dfu",
501 wxFD_OPEN | wxFD_FILE_MUST_EXIST);
502 if (dlg.ShowModal() == wxID_CANCEL) return;
504 std::string filename(dlg.GetPath());
506 wxWindowPtr<wxGenericProgressDialog> progress(
507 new wxGenericProgressDialog(
508 "Searching for bootloader",
509 "Searching for bootloader",
512 wxPD_AUTO_HIDE | wxPD_CAN_ABORT)
514 mmLogStatus("Searching for bootloader");
519 if (!myHID) myHID.reset(HID::Open());
522 mmLogStatus("Resetting SCSI2SD into bootloader");
524 myHID->enterBootloader();
531 mmLogStatus("STM DFU Bootloader found");
533 doDFUUpdate(filename);
537 catch (std::exception& e)
539 mmLogStatus(e.what());
543 if (!progress->Pulse())
545 return; // user cancelled.
550 void doDFUUpdate(const std::string& filename)
552 if (filename.find(".dfu") == std::string::npos)
556 "SCSI2SD V6 requires a .dfu file",
557 wxOK | wxICON_ERROR);
561 std::stringstream ss;
562 ss << "dfu-util --download \""
563 << filename.c_str() << "\" --alt 0 --reset";
566 std::string cmd = ss.str();
567 int result = system(cmd.c_str());
568 if (WEXITSTATUS(result) != 0)
572 "Firmware update failed. Command = " + cmd,
573 wxOK | wxICON_ERROR);
578 void dumpSCSICommand(std::vector<uint8_t> buf)
580 std::stringstream msg;
582 for (size_t i = 0; i < 32 && i < buf.size(); ++i)
584 msg << std::setfill('0') << std::setw(2) <<
585 static_cast<int>(buf[i]) << ' ';
587 wxLogMessage(this, msg.str().c_str());
592 if (!mySCSILogChk->IsChecked() ||
599 std::vector<uint8_t> info(HID::HID_PACKET_SIZE);
600 if (myHID->readSCSIDebugInfo(info))
602 dumpSCSICommand(info);
605 catch (std::exception& e)
607 wxLogWarning(this, e.what());
612 void OnID_Timer(wxTimerEvent& event)
615 time_t now = time(NULL);
616 if (now == myLastPollTime) return;
617 myLastPollTime = now;
619 // Check if we are connected to the HID device.
622 if (myHID && !myHID->ping())
624 // Verify the USB HID connection is valid
630 myHID.reset(HID::Open());
633 std::stringstream msg;
634 msg << "SCSI2SD Ready, firmware version " <<
635 myHID->getFirmwareVersionStr();
636 mmLogStatus(msg.str());
638 std::vector<uint8_t> csd(myHID->getSD_CSD());
639 std::vector<uint8_t> cid(myHID->getSD_CID());
640 std::stringstream sdinfo;
641 sdinfo << "SD Capacity (512-byte sectors): " <<
642 myHID->getSDCapacity() << std::endl;
644 sdinfo << "SD CSD Register: ";
645 if (sdCrc7(&csd[0], 15, 0) != (csd[15] >> 1))
649 for (size_t i = 0; i < csd.size(); ++i)
652 std::hex << std::setfill('0') << std::setw(2) <<
653 static_cast<int>(csd[i]);
656 sdinfo << "SD CID Register: ";
657 if (sdCrc7(&cid[0], 15, 0) != (cid[15] >> 1))
661 for (size_t i = 0; i < cid.size(); ++i)
664 std::hex << std::setfill('0') << std::setw(2) <<
665 static_cast<int>(cid[i]);
668 wxLogMessage(this, "%s", sdinfo.str());
670 if (mySelfTestChk->IsChecked())
672 std::stringstream scsiInfo;
673 scsiInfo << "SCSI Self-Test: " <<
674 (myHID->scsiSelfTest() ? "Passed" : "FAIL");
675 wxLogMessage(this, "%s", scsiInfo.str());
678 if (!myInitialConfig)
680 /* This doesn't work properly, and causes crashes.
681 wxCommandEvent loadEvent(wxEVT_NULL, ID_BtnLoad);
682 GetEventHandler()->AddPendingEvent(loadEvent);
689 char ticks[] = {'/', '-', '\\', '|'};
690 std::stringstream ss;
691 ss << "Searching for SCSI2SD device " << ticks[myTickCounter % sizeof(ticks)];
693 SetStatusText(ss.str());
697 catch (std::runtime_error& e)
699 std::cerr << e.what() << std::endl;
700 mmLogStatus(e.what());
706 void doLoad(wxCommandEvent& event)
708 TimerLock lock(myTimer);
711 mmLogStatus("Loading configuration");
713 wxWindowPtr<wxGenericProgressDialog> progress(
714 new wxGenericProgressDialog(
715 "Load config settings",
716 "Loading config settings",
719 wxPD_CAN_ABORT | wxPD_REMAINING_TIME)
722 int currentProgress = 0;
723 int totalProgress = 2;
725 std::vector<uint8_t> cfgData(S2S_CFG_SIZE);
726 uint32_t sector = myHID->getSDCapacity() - 2;
727 for (size_t i = 0; i < 2; ++i)
729 std::stringstream ss;
730 ss << "Reading sector " << sector;
731 mmLogStatus(ss.str());
732 currentProgress += 1;
733 if (currentProgress == totalProgress)
735 ss.str("Load Complete.");
736 mmLogStatus("Load Complete.");
739 if (!progress->Update(
740 (100 * currentProgress) / totalProgress,
748 std::vector<uint8_t> sdData;
752 myHID->readSector(sector++, sdData);
754 catch (std::runtime_error& e)
756 mmLogStatus(e.what());
766 myBoardPanel->setConfig(ConfigUtil::boardConfigFromBytes(&cfgData[0]));
767 for (int i = 0; i < S2S_MAX_TARGETS; ++i)
769 myTargets[i]->setConfig(
770 ConfigUtil::fromBytes(
771 &cfgData[sizeof(S2S_BoardCfg) + i * sizeof(S2S_TargetCfg)]
776 myInitialConfig = true;
780 mmLogStatus("Load failed");
781 progress->Update(100, "Load failed");
785 mmLogStatus("Load Aborted");
792 void doSave(wxCommandEvent& event)
794 TimerLock lock(myTimer);
797 mmLogStatus("Saving configuration");
799 wxWindowPtr<wxGenericProgressDialog> progress(
800 new wxGenericProgressDialog(
801 "Save config settings",
802 "Saving config settings",
805 wxPD_CAN_ABORT | wxPD_REMAINING_TIME)
809 int currentProgress = 0;
810 int totalProgress = 2;
812 std::vector<uint8_t> cfgData(
813 ConfigUtil::boardConfigToBytes(myBoardPanel->getConfig())
815 for (int i = 0; i < S2S_MAX_TARGETS; ++i)
817 std::vector<uint8_t> raw(
818 ConfigUtil::toBytes(myTargets[i]->getConfig())
820 cfgData.insert(cfgData.end(), raw.begin(), raw.end());
823 uint32_t sector = myHID->getSDCapacity() - 2;
825 for (size_t i = 0; i < 2; ++i)
827 std::stringstream ss;
828 ss << "Programming sector flash array " << sector;
829 mmLogStatus(ss.str());
830 currentProgress += 1;
832 if (currentProgress == totalProgress)
834 ss.str("Save Complete.");
835 mmLogStatus("Save Complete.");
837 if (!progress->Update(
838 (100 * currentProgress) / totalProgress,
848 std::vector<uint8_t> buf;
849 buf.insert(buf.end(), &cfgData[i * 512], &cfgData[(i+1) * 512]);
850 myHID->writeSector(sector++, buf);
852 catch (std::runtime_error& e)
854 mmLogStatus(e.what());
864 mmLogStatus("Save failed");
865 progress->Update(100, "Save failed");
869 mmLogStatus("Save Aborted");
876 // Note: Don't confuse this with the wxApp::OnExit virtual method
877 void OnExitEvt(wxCommandEvent& event);
879 void OnCloseEvt(wxCloseEvent& event);
881 void OnAbout(wxCommandEvent& event)
884 "SCSI2SD (scsi2sd-util6)\n"
885 "Copyright (C) 2014-2016 Michael McMaster <michael@codesrc.com>\n"
887 "This program is free software: you can redistribute it and/or modify\n"
888 "it under the terms of the GNU General Public License as published by\n"
889 "the Free Software Foundation, either version 3 of the License, or\n"
890 "(at your option) any later version.\n"
892 "This program is distributed in the hope that it will be useful,\n"
893 "but WITHOUT ANY WARRANTY; without even the implied warranty of\n"
894 "MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n"
895 "GNU General Public License for more details.\n"
897 "You should have received a copy of the GNU General Public License\n"
898 "along with this program. If not, see <http://www.gnu.org/licenses/>.\n",
900 "About scsi2sd-util6", wxOK | wxICON_INFORMATION );
903 wxDECLARE_EVENT_TABLE();
906 wxBEGIN_EVENT_TABLE(AppFrame, wxFrame)
907 EVT_MENU(AppFrame::ID_ConfigDefaults, AppFrame::OnID_ConfigDefaults)
908 EVT_MENU(AppFrame::ID_Firmware, AppFrame::OnID_Firmware)
909 EVT_MENU(AppFrame::ID_LogWindow, AppFrame::OnID_LogWindow)
910 EVT_MENU(AppFrame::ID_SaveFile, AppFrame::OnID_SaveFile)
911 EVT_MENU(AppFrame::ID_OpenFile, AppFrame::OnID_OpenFile)
912 EVT_MENU(wxID_EXIT, AppFrame::OnExitEvt)
913 EVT_MENU(wxID_ABOUT, AppFrame::OnAbout)
915 EVT_TIMER(AppFrame::ID_Timer, AppFrame::OnID_Timer)
917 EVT_COMMAND(wxID_ANY, ConfigChangedEvent, AppFrame::onConfigChanged)
919 EVT_BUTTON(ID_BtnSave, AppFrame::doSave)
920 EVT_BUTTON(ID_BtnLoad, AppFrame::doLoad)
922 EVT_CLOSE(AppFrame::OnCloseEvt)
928 class App : public wxApp
931 virtual bool OnInit()
934 AppFrame* frame = new AppFrame();
943 wxIMPLEMENT_APP(App);
946 AppFrame::OnExitEvt(wxCommandEvent& event)
948 wxGetApp().ExitMainLoop();
952 AppFrame::OnCloseEvt(wxCloseEvent& event)
954 wxGetApp().ExitMainLoop();