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;
62 #include <libusb-1.0/libusb.h>
65 using namespace SCSI2SD;
70 void setProgressDialog(
71 const wxWindowPtr<wxGenericProgressDialog>& dlg,
74 myProgressDialog = dlg;
79 void clearProgressDialog()
81 myProgressDialog->Show(false);
82 myProgressDialog.reset();
85 void update(unsigned char arrayId, unsigned short rowNum)
87 if (!myProgressDialog) return;
92 ss << "Writing flash array " <<
93 static_cast<int>(arrayId) << " row " <<
94 static_cast<int>(rowNum);
95 wxLogMessage("%s", ss.str());
96 myProgressDialog->Update(myNumRows, ss.str());
100 wxWindowPtr<wxGenericProgressDialog> myProgressDialog;
104 static ProgressWrapper TheProgressWrapper;
107 void ProgressUpdate(unsigned char arrayId, unsigned short rowNum)
109 TheProgressWrapper.update(arrayId, rowNum);
114 bool hasDFUdevice() {
118 libusb_device **list;
119 ssize_t cnt = libusb_get_device_list(NULL, &list);
121 if (cnt < 0) return false;
123 for (i = 0; i < cnt; i++) {
124 libusb_device *device = list[i];
125 libusb_device_descriptor desc;
126 libusb_get_device_descriptor(device, &desc);
127 if (desc.idVendor == 0x0483 && desc.idProduct == 0xdf11) {
133 libusb_free_device_list(list, 1);
142 static uint8_t sdCrc7(uint8_t* chr, uint8_t cnt, uint8_t crc)
145 for(a = 0; a < cnt; a++)
147 uint8_t data = chr[a];
149 for(i = 0; i < 8; i++)
152 if ((data & 0x80) ^ (crc & 0x80))
165 TimerLock(wxTimer* timer) :
167 myInterval(myTimer->GetInterval())
174 if (myTimer && myInterval > 0)
176 myTimer->Start(myInterval);
184 class AppFrame : public wxFrame
188 wxFrame(NULL, wxID_ANY, "scsi2sd-util6", wxPoint(50, 50), wxSize(600, 700)),
189 myInitialConfig(false),
193 wxMenu *menuFile = new wxMenu();
196 _("&Save to file..."),
197 _("Save settings to local file."));
201 _("Load settings from local file."));
202 menuFile->AppendSeparator();
206 _("Load default configuration options."));
209 _("&Upgrade Firmware..."),
210 _("Upgrade or inspect device firmware version."));
211 menuFile->Append(wxID_EXIT);
213 wxMenu *menuWindow= new wxMenu();
217 _("Show debug log window"));
219 wxMenu *menuDebug = new wxMenu();
220 mySCSILogChk = menuDebug->AppendCheckItem(
223 _("Log SCSI commands"));
225 mySelfTestChk = menuDebug->AppendCheckItem(
227 _("SCSI Standalone Self-Test"),
228 _("SCSI Standalone Self-Test"));
230 wxMenu *menuHelp = new wxMenu();
231 menuHelp->Append(wxID_ABOUT);
233 wxMenuBar *menuBar = new wxMenuBar();
234 menuBar->Append( menuFile, _("&File") );
235 menuBar->Append( menuDebug, _("&Debug") );
236 menuBar->Append( menuWindow, _("&Window") );
237 menuBar->Append( menuHelp, _("&Help") );
238 SetMenuBar( menuBar );
243 wxPanel* cfgPanel = new wxPanel(this);
244 wxFlexGridSizer *fgs = new wxFlexGridSizer(3, 1, 15, 15);
245 cfgPanel->SetSizer(fgs);
247 // Empty space below menu bar.
248 fgs->Add(5, 5, wxALL);
250 wxNotebook* tabs = new wxNotebook(cfgPanel, ID_Notebook);
251 myBoardPanel = new BoardPanel(tabs, ConfigUtil::DefaultBoardConfig());
252 tabs->AddPage(myBoardPanel, _("General Settings"));
253 for (int i = 0; i < S2S_MAX_TARGETS; ++i)
255 TargetPanel* target =
256 new TargetPanel(tabs, ConfigUtil::Default(i));
257 myTargets.push_back(target);
258 std::stringstream ss;
259 ss << "Device " << (i + 1);
260 tabs->AddPage(target, ss.str());
267 wxPanel* btnPanel = new wxPanel(cfgPanel);
268 wxFlexGridSizer *btnFgs = new wxFlexGridSizer(1, 2, 5, 5);
269 btnPanel->SetSizer(btnFgs);
271 new wxButton(btnPanel, ID_BtnLoad, _("Load from device"));
272 btnFgs->Add(myLoadButton);
274 new wxButton(btnPanel, ID_BtnSave, _("Save to device"));
275 btnFgs->Add(mySaveButton);
281 //Fit(); // Needed to reduce window size on Windows
282 FitInside(); // Needed on Linux to prevent status bar overlap
284 myLogWindow = new wxLogWindow(this, _("scsi2sd-util6 debug log"), true);
285 myLogWindow->PassMessages(false); // Prevent messagebox popups
287 myTimer = new wxTimer(this, ID_Timer);
288 myTimer->Start(16); //ms, suitable for scsi debug logging
292 wxLogWindow* myLogWindow;
293 BoardPanel* myBoardPanel;
294 std::vector<TargetPanel*> myTargets;
295 wxButton* myLoadButton;
296 wxButton* mySaveButton;
297 wxMenuItem* mySCSILogChk;
298 wxMenuItem* mySelfTestChk;
300 shared_ptr<HID> myHID;
301 bool myInitialConfig;
303 uint8_t myTickCounter;
305 time_t myLastPollTime;
307 void mmLogStatus(const std::string& msg)
309 // We set PassMessages to false on our log window to prevent popups, but
310 // this also prevents wxLogStatus from updating the status bar.
312 wxLogMessage(this, "%s", msg.c_str());
315 void onConfigChanged(wxCommandEvent& event)
324 // Check for duplicate SCSI IDs
325 std::set<uint8_t> enabledID;
327 // Check for overlapping SD sectors.
328 std::vector<std::pair<uint32_t, uint64_t> > sdSectors;
330 bool isTargetEnabled = false; // Need at least one enabled
331 uint32_t autoStartSector = 0;
332 for (size_t i = 0; i < myTargets.size(); ++i)
334 myTargets[i]->setAutoStartSector(autoStartSector);
335 valid = myTargets[i]->evaluate() && valid;
337 if (myTargets[i]->isEnabled())
339 isTargetEnabled = true;
340 uint8_t scsiID = myTargets[i]->getSCSIId();
341 if (enabledID.find(scsiID) != enabledID.end())
343 myTargets[i]->setDuplicateID(true);
348 enabledID.insert(scsiID);
349 myTargets[i]->setDuplicateID(false);
352 auto sdSectorRange = myTargets[i]->getSDSectorRange();
353 for (auto it(sdSectors.begin()); it != sdSectors.end(); ++it)
355 if (sdSectorRange.first < it->second &&
356 sdSectorRange.second > it->first)
359 myTargets[i]->setSDSectorOverlap(true);
363 myTargets[i]->setSDSectorOverlap(false);
366 sdSectors.push_back(sdSectorRange);
367 autoStartSector = sdSectorRange.second;
371 myTargets[i]->setDuplicateID(false);
372 myTargets[i]->setSDSectorOverlap(false);
376 valid = valid && isTargetEnabled; // Need at least one.
378 mySaveButton->Enable(valid && myHID);
380 myLoadButton->Enable(static_cast<bool>(myHID));
386 ID_ConfigDefaults = wxID_HIGHEST + 1,
399 void OnID_ConfigDefaults(wxCommandEvent& event)
401 myBoardPanel->setConfig(ConfigUtil::DefaultBoardConfig());
402 for (size_t i = 0; i < myTargets.size(); ++i)
404 myTargets[i]->setConfig(ConfigUtil::Default(i));
408 void OnID_SaveFile(wxCommandEvent& event)
410 TimerLock lock(myTimer);
416 "Save config settings",
419 "XML files (*.xml)|*.xml",
420 wxFD_SAVE | wxFD_OVERWRITE_PROMPT);
421 if (dlg.ShowModal() == wxID_CANCEL) return;
423 wxFileOutputStream file(dlg.GetPath());
426 wxLogError("Cannot save settings to file '%s'.", dlg.GetPath());
430 wxTextOutputStream s(file);
434 s << ConfigUtil::toXML(myBoardPanel->getConfig());
435 for (size_t i = 0; i < myTargets.size(); ++i)
437 s << ConfigUtil::toXML(myTargets[i]->getConfig());
443 void OnID_OpenFile(wxCommandEvent& event)
445 TimerLock lock(myTimer);
449 "Load config settings",
452 "XML files (*.xml)|*.xml",
453 wxFD_OPEN | wxFD_FILE_MUST_EXIST);
454 if (dlg.ShowModal() == wxID_CANCEL) return;
458 std::pair<S2S_BoardCfg, std::vector<S2S_TargetCfg>> configs(
459 ConfigUtil::fromXML(std::string(dlg.GetPath())));
461 myBoardPanel->setConfig(configs.first);
464 for (i = 0; i < configs.second.size() && i < myTargets.size(); ++i)
466 myTargets[i]->setConfig(configs.second[i]);
469 for (; i < myTargets.size(); ++i)
471 myTargets[i]->setConfig(ConfigUtil::Default(i));
474 catch (std::exception& e)
477 "Cannot load settings from file '%s'.\n%s",
484 wxOK | wxICON_ERROR);
488 void OnID_Firmware(wxCommandEvent& event)
490 TimerLock lock(myTimer);
494 void OnID_LogWindow(wxCommandEvent& event)
499 void doFirmwareUpdate()
503 "Load firmware file",
506 "SCSI2SD Firmware files (*.dfu)|*.dfu",
507 wxFD_OPEN | wxFD_FILE_MUST_EXIST);
508 if (dlg.ShowModal() == wxID_CANCEL) return;
510 std::string filename(dlg.GetPath());
512 wxWindowPtr<wxGenericProgressDialog> progress(
513 new wxGenericProgressDialog(
514 "Searching for bootloader",
515 "Searching for bootloader",
518 wxPD_AUTO_HIDE | wxPD_CAN_ABORT)
520 mmLogStatus("Searching for bootloader");
525 if (!myHID) myHID.reset(HID::Open());
528 mmLogStatus("Resetting SCSI2SD into bootloader");
530 myHID->enterBootloader();
537 mmLogStatus("STM DFU Bootloader found");
539 doDFUUpdate(filename);
543 catch (std::exception& e)
545 mmLogStatus(e.what());
549 if (!progress->Pulse())
551 return; // user cancelled.
556 void doDFUUpdate(const std::string& filename)
558 if (filename.find(".dfu") == std::string::npos)
562 "SCSI2SD V6 requires a .dfu file",
563 wxOK | wxICON_ERROR);
567 std::stringstream ss;
568 ss << "dfu-util --download \""
569 << filename.c_str() << "\" --alt 0 --reset";
572 std::string cmd = ss.str();
573 int result = system(cmd.c_str());
577 if (WEXITSTATUS(result) != 0)
582 "Firmware update failed. Command = " + cmd,
583 wxOK | wxICON_ERROR);
588 void dumpSCSICommand(std::vector<uint8_t> buf)
590 std::stringstream msg;
592 for (size_t i = 0; i < 32 && i < buf.size(); ++i)
594 msg << std::setfill('0') << std::setw(2) <<
595 static_cast<int>(buf[i]) << ' ';
597 wxLogMessage(this, msg.str().c_str());
602 if (!mySCSILogChk->IsChecked() ||
609 std::vector<uint8_t> info(HID::HID_PACKET_SIZE);
610 if (myHID->readSCSIDebugInfo(info))
612 dumpSCSICommand(info);
615 catch (std::exception& e)
617 wxLogWarning(this, e.what());
622 void OnID_Timer(wxTimerEvent& event)
625 time_t now = time(NULL);
626 if (now == myLastPollTime) return;
627 myLastPollTime = now;
629 // Check if we are connected to the HID device.
632 if (myHID && !myHID->ping())
634 // Verify the USB HID connection is valid
640 myHID.reset(HID::Open());
643 std::stringstream msg;
644 msg << "SCSI2SD Ready, firmware version " <<
645 myHID->getFirmwareVersionStr();
646 mmLogStatus(msg.str());
648 std::vector<uint8_t> csd(myHID->getSD_CSD());
649 std::vector<uint8_t> cid(myHID->getSD_CID());
650 std::stringstream sdinfo;
651 sdinfo << "SD Capacity (512-byte sectors): " <<
652 myHID->getSDCapacity() << std::endl;
654 sdinfo << "SD CSD Register: ";
655 if (sdCrc7(&csd[0], 15, 0) != (csd[15] >> 1))
659 for (size_t i = 0; i < csd.size(); ++i)
662 std::hex << std::setfill('0') << std::setw(2) <<
663 static_cast<int>(csd[i]);
666 sdinfo << "SD CID Register: ";
667 if (sdCrc7(&cid[0], 15, 0) != (cid[15] >> 1))
671 for (size_t i = 0; i < cid.size(); ++i)
674 std::hex << std::setfill('0') << std::setw(2) <<
675 static_cast<int>(cid[i]);
678 wxLogMessage(this, "%s", sdinfo.str());
680 if (mySelfTestChk->IsChecked())
682 std::stringstream scsiInfo;
683 scsiInfo << "SCSI Self-Test: " <<
684 (myHID->scsiSelfTest() ? "Passed" : "FAIL");
685 wxLogMessage(this, "%s", scsiInfo.str());
688 if (!myInitialConfig)
690 /* This doesn't work properly, and causes crashes.
691 wxCommandEvent loadEvent(wxEVT_NULL, ID_BtnLoad);
692 GetEventHandler()->AddPendingEvent(loadEvent);
699 char ticks[] = {'/', '-', '\\', '|'};
700 std::stringstream ss;
701 ss << "Searching for SCSI2SD device " << ticks[myTickCounter % sizeof(ticks)];
703 SetStatusText(ss.str());
707 catch (std::runtime_error& e)
709 std::cerr << e.what() << std::endl;
710 mmLogStatus(e.what());
716 void doLoad(wxCommandEvent& event)
718 TimerLock lock(myTimer);
721 mmLogStatus("Loading configuration");
723 wxWindowPtr<wxGenericProgressDialog> progress(
724 new wxGenericProgressDialog(
725 "Load config settings",
726 "Loading config settings",
729 wxPD_CAN_ABORT | wxPD_REMAINING_TIME)
732 int currentProgress = 0;
733 int totalProgress = 2;
735 std::vector<uint8_t> cfgData(S2S_CFG_SIZE);
736 uint32_t sector = myHID->getSDCapacity() - 2;
737 for (size_t i = 0; i < 2; ++i)
739 std::stringstream ss;
740 ss << "Reading sector " << sector;
741 mmLogStatus(ss.str());
742 currentProgress += 1;
743 if (currentProgress == totalProgress)
745 ss.str("Load Complete.");
746 mmLogStatus("Load Complete.");
749 if (!progress->Update(
750 (100 * currentProgress) / totalProgress,
758 std::vector<uint8_t> sdData;
762 myHID->readSector(sector++, sdData);
764 catch (std::runtime_error& e)
766 mmLogStatus(e.what());
776 myBoardPanel->setConfig(ConfigUtil::boardConfigFromBytes(&cfgData[0]));
777 for (int i = 0; i < S2S_MAX_TARGETS; ++i)
779 myTargets[i]->setConfig(
780 ConfigUtil::fromBytes(
781 &cfgData[sizeof(S2S_BoardCfg) + i * sizeof(S2S_TargetCfg)]
786 myInitialConfig = true;
790 mmLogStatus("Load failed");
791 progress->Update(100, "Load failed");
795 mmLogStatus("Load Aborted");
802 void doSave(wxCommandEvent& event)
804 TimerLock lock(myTimer);
807 mmLogStatus("Saving configuration");
809 wxWindowPtr<wxGenericProgressDialog> progress(
810 new wxGenericProgressDialog(
811 "Save config settings",
812 "Saving config settings",
815 wxPD_CAN_ABORT | wxPD_REMAINING_TIME)
819 int currentProgress = 0;
820 int totalProgress = 2;
822 std::vector<uint8_t> cfgData(
823 ConfigUtil::boardConfigToBytes(myBoardPanel->getConfig())
825 for (int i = 0; i < S2S_MAX_TARGETS; ++i)
827 std::vector<uint8_t> raw(
828 ConfigUtil::toBytes(myTargets[i]->getConfig())
830 cfgData.insert(cfgData.end(), raw.begin(), raw.end());
833 uint32_t sector = myHID->getSDCapacity() - 2;
835 for (size_t i = 0; i < 2; ++i)
837 std::stringstream ss;
838 ss << "Programming sector flash array " << sector;
839 mmLogStatus(ss.str());
840 currentProgress += 1;
842 if (currentProgress == totalProgress)
844 ss.str("Save Complete.");
845 mmLogStatus("Save Complete.");
847 if (!progress->Update(
848 (100 * currentProgress) / totalProgress,
858 std::vector<uint8_t> buf;
859 buf.insert(buf.end(), &cfgData[i * 512], &cfgData[(i+1) * 512]);
860 myHID->writeSector(sector++, buf);
862 catch (std::runtime_error& e)
864 mmLogStatus(e.what());
874 mmLogStatus("Save failed");
875 progress->Update(100, "Save failed");
879 mmLogStatus("Save Aborted");
886 // Note: Don't confuse this with the wxApp::OnExit virtual method
887 void OnExitEvt(wxCommandEvent& event);
889 void OnCloseEvt(wxCloseEvent& event);
891 void OnAbout(wxCommandEvent& event)
894 "SCSI2SD (scsi2sd-util6)\n"
895 "Copyright (C) 2014-2016 Michael McMaster <michael@codesrc.com>\n"
897 "This program is free software: you can redistribute it and/or modify\n"
898 "it under the terms of the GNU General Public License as published by\n"
899 "the Free Software Foundation, either version 3 of the License, or\n"
900 "(at your option) any later version.\n"
902 "This program is distributed in the hope that it will be useful,\n"
903 "but WITHOUT ANY WARRANTY; without even the implied warranty of\n"
904 "MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n"
905 "GNU General Public License for more details.\n"
907 "You should have received a copy of the GNU General Public License\n"
908 "along with this program. If not, see <http://www.gnu.org/licenses/>.\n",
910 "About scsi2sd-util6", wxOK | wxICON_INFORMATION );
913 wxDECLARE_EVENT_TABLE();
916 wxBEGIN_EVENT_TABLE(AppFrame, wxFrame)
917 EVT_MENU(AppFrame::ID_ConfigDefaults, AppFrame::OnID_ConfigDefaults)
918 EVT_MENU(AppFrame::ID_Firmware, AppFrame::OnID_Firmware)
919 EVT_MENU(AppFrame::ID_LogWindow, AppFrame::OnID_LogWindow)
920 EVT_MENU(AppFrame::ID_SaveFile, AppFrame::OnID_SaveFile)
921 EVT_MENU(AppFrame::ID_OpenFile, AppFrame::OnID_OpenFile)
922 EVT_MENU(wxID_EXIT, AppFrame::OnExitEvt)
923 EVT_MENU(wxID_ABOUT, AppFrame::OnAbout)
925 EVT_TIMER(AppFrame::ID_Timer, AppFrame::OnID_Timer)
927 EVT_COMMAND(wxID_ANY, ConfigChangedEvent, AppFrame::onConfigChanged)
929 EVT_BUTTON(ID_BtnSave, AppFrame::doSave)
930 EVT_BUTTON(ID_BtnLoad, AppFrame::doLoad)
932 EVT_CLOSE(AppFrame::OnCloseEvt)
938 class App : public wxApp
941 virtual bool OnInit()
946 AppFrame* frame = new AppFrame();
955 wxIMPLEMENT_APP(App);
958 AppFrame::OnExitEvt(wxCommandEvent& event)
960 wxGetApp().ExitMainLoop();
964 AppFrame::OnCloseEvt(wxCloseEvent& event)
966 wxGetApp().ExitMainLoop();