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/>.
18 // For compilers that support precompilation, includes "wx/wx.h".
19 #include <wx/wxprec.h>
24 #include <wx/wrapsizer.h>
26 #include "ConfigUtil.hh"
27 #include "TargetPanel.hh"
35 using namespace SCSI2SD;
37 wxDEFINE_EVENT(SCSI2SD::ConfigChangedEvent, wxCommandEvent);
41 template<typename IntType, class WXCTRL> std::pair<IntType, bool>
42 CtrlGetValue(WXCTRL* ctrl)
45 std::stringstream conv;
46 conv << ctrl->GetValue();
48 return std::make_pair(value, static_cast<bool>(conv));
51 void CtrlGetFixedString(wxTextEntry* ctrl, char* dest, size_t len)
53 memset(dest, ' ', len);
54 std::string str(ctrl->GetValue().ToAscii());
55 // Don't use strncpy - we need to avoid NULL's
56 memcpy(dest, str.c_str(), std::min(len, str.size()));
59 bool CtrlIsAscii(wxTextEntry* ctrl)
61 return ctrl->GetValue().IsAscii();
66 TargetPanel::TargetPanel(wxWindow* parent, const TargetConfig& initialConfig) :
70 myStartSDSectorValidator(new wxIntegerValidator<uint32_t>),
71 mySectorSizeValidator(new wxIntegerValidator<uint16_t>),
72 myNumSectorValidator(new wxIntegerValidator<uint32_t>),
73 mySizeValidator(new wxFloatingPointValidator<float>(2))
75 wxFlexGridSizer *fgs = new wxFlexGridSizer(13, 3, 9, 25);
77 fgs->Add(new wxStaticText(this, wxID_ANY, wxT("")));
82 wxT("Enable SCSI Target"));
83 fgs->Add(myEnableCtrl);
84 // Set a non-visible string to leave room in the column for future messages
85 fgs->Add(new wxStaticText(this, wxID_ANY, wxT(" ")));
86 Bind(wxEVT_CHECKBOX, &TargetPanel::onInput<wxCommandEvent>, this, ID_enableCtrl);
89 fgs->Add(new wxStaticText(this, wxID_ANY, wxT("SCSI ID")));
97 wxSP_WRAP | wxSP_ARROW_KEYS,
101 fgs->Add(myScsiIdCtrl);
102 myScsiIdMsg = new wxStaticText(this, wxID_ANY, wxT(""));
103 fgs->Add(myScsiIdMsg);
104 Bind(wxEVT_SPINCTRL, &TargetPanel::onInput<wxSpinEvent>, this, ID_scsiIdCtrl);
106 fgs->Add(new wxStaticText(this, wxID_ANY, wxT("Device Type")));
107 wxString deviceTypes[] =
113 wxT("Magneto optical")
121 sizeof(deviceTypes) / sizeof(wxString),
124 myDeviceTypeCtrl->SetSelection(0);
125 fgs->Add(myDeviceTypeCtrl);
126 fgs->Add(new wxStaticText(this, wxID_ANY, wxT("")));
127 Bind(wxEVT_CHOICE, &TargetPanel::onInput<wxCommandEvent>, this, ID_deviceTypeCtrl);
129 fgs->Add(new wxStaticText(this, wxID_ANY, wxT("")));
134 wxT("Enable Parity"));
135 myParityCtrl->SetToolTip(wxT("Enable to require valid SCSI parity bits when receiving data. Some hosts don't provide parity. SCSI2SD always outputs valid parity bits."));
136 fgs->Add(myParityCtrl);
137 Bind(wxEVT_CHECKBOX, &TargetPanel::onInput<wxCommandEvent>, this, ID_parityCtrl);
143 wxT("Enable Unit Attention"));
144 myUnitAttCtrl->SetToolTip(wxT("Enable this to inform the host of changes after hot-swapping SD cards. Causes problems with Mac Plus."));
145 fgs->Add(myUnitAttCtrl);
146 Bind(wxEVT_CHECKBOX, &TargetPanel::onInput<wxCommandEvent>, this, ID_unitAttCtrl);
148 fgs->Add(new wxStaticText(this, wxID_ANY, wxT("")));
153 wxT("Enable SCSI2 Mode"));
154 myScsi2Ctrl->SetToolTip(wxT("Enable high-performance mode. May cause problems with SASI/SCSI1 hosts."));
155 fgs->Add(myScsi2Ctrl);
156 Bind(wxEVT_CHECKBOX, &TargetPanel::onInput<wxCommandEvent>, this, ID_scsi2Ctrl);
162 wxT("Disable glitch filter"));
163 myGlitchCtrl->SetToolTip(wxT("Improve performance at the cost of noise immunity. Only use with short cables."));
164 fgs->Add(myGlitchCtrl);
165 Bind(wxEVT_CHECKBOX, &TargetPanel::onInput<wxCommandEvent>, this, ID_glitchCtrl);
167 fgs->Add(new wxStaticText(this, wxID_ANY, wxT("SD card start sector")));
168 wxWrapSizer* startContainer = new wxWrapSizer();
169 myStartSDSectorCtrl =
172 ID_startSDSectorCtrl,
177 *myStartSDSectorValidator);
178 myStartSDSectorCtrl->SetToolTip(wxT("Supports multiple SCSI targets "
179 "on a single memory card. In units of 512-byte sectors."));
180 startContainer->Add(myStartSDSectorCtrl);
181 myAutoStartSectorCtrl =
184 ID_autoStartSectorCtrl,
186 startContainer->Add(myAutoStartSectorCtrl);
187 Bind(wxEVT_CHECKBOX, &TargetPanel::onInput<wxCommandEvent>, this, ID_autoStartSectorCtrl);
188 fgs->Add(startContainer);
189 myStartSDSectorMsg = new wxStaticText(this, wxID_ANY, wxT(""));
190 fgs->Add(myStartSDSectorMsg);
191 Bind(wxEVT_TEXT, &TargetPanel::onInput<wxCommandEvent>, this, ID_startSDSectorCtrl);
194 fgs->Add(new wxStaticText(this, wxID_ANY, wxT("Sector size (bytes)")));
203 *mySectorSizeValidator);
204 mySectorSizeCtrl->SetToolTip(wxT("Between 64 and 8192. Default of 512 is suitable in most cases."));
205 fgs->Add(mySectorSizeCtrl);
206 mySectorSizeMsg = new wxStaticText(this, wxID_ANY, wxT(""));
207 fgs->Add(mySectorSizeMsg);
208 Bind(wxEVT_TEXT, &TargetPanel::onSizeInput, this, ID_sectorSizeCtrl);
211 fgs->Add(new wxStaticText(this, wxID_ANY, wxT("Sector count")));
220 *myNumSectorValidator);
221 myNumSectorCtrl->SetToolTip(wxT("Number of sectors (device size)"));
222 fgs->Add(myNumSectorCtrl);
223 myNumSectorMsg = new wxStaticText(this, wxID_ANY, wxT(""));
224 fgs->Add(myNumSectorMsg);
225 Bind(wxEVT_TEXT, &TargetPanel::onSizeInput, this, ID_numSectorCtrl);
228 fgs->Add(new wxStaticText(this, wxID_ANY, wxT("Device size")));
229 wxWrapSizer* sizeContainer = new wxWrapSizer();
239 mySizeCtrl->SetToolTip(wxT("Device size"));
240 sizeContainer->Add(mySizeCtrl);
241 wxString units[] = {wxT("KB"), wxT("MB"), wxT("GB")};
248 sizeof(units) / sizeof(wxString),
251 sizeContainer->Add(mySizeUnitCtrl);
252 fgs->Add(sizeContainer);
253 fgs->Add(new wxStaticText(this, wxID_ANY, wxT("")));
254 Bind(wxEVT_TEXT, &TargetPanel::onSizeInput, this, ID_sizeCtrl);
255 Bind(wxEVT_CHOICE, &TargetPanel::onSizeInput, this, ID_sizeUnitCtrl);
257 fgs->Add(new wxStaticText(this, wxID_ANY, wxT("Vendor")));
264 wxSize(GetCharWidth() * 10, -1));
265 myVendorCtrl->SetMaxLength(8);
266 myVendorCtrl->SetToolTip(wxT("SCSI Vendor string. eg. ' codesrc'"));
267 fgs->Add(myVendorCtrl);
268 myVendorMsg = new wxStaticText(this, wxID_ANY, wxT(""));
269 fgs->Add(myVendorMsg);
270 Bind(wxEVT_TEXT, &TargetPanel::onInput<wxCommandEvent>, this, ID_vendorCtrl);
272 fgs->Add(new wxStaticText(this, wxID_ANY, wxT("Product ID")));
279 wxSize(GetCharWidth() * 17, -1));
280 myProductCtrl->SetMaxLength(18);
281 myProductCtrl->SetToolTip(wxT("SCSI Product ID string. eg. 'SCSI2SD'"));
282 fgs->Add(myProductCtrl);
283 myProductMsg = new wxStaticText(this, wxID_ANY, wxT(""));
284 fgs->Add(myProductMsg);
285 Bind(wxEVT_TEXT, &TargetPanel::onInput<wxCommandEvent>, this, ID_productCtrl);
287 fgs->Add(new wxStaticText(this, wxID_ANY, wxT("Revision")));
294 wxSize(GetCharWidth() * 6, -1));
295 myRevisionCtrl->SetMaxLength(4);
296 myRevisionCtrl->SetToolTip(wxT("SCSI device revision string. eg. '3.5a'"));
297 fgs->Add(myRevisionCtrl);
298 myRevisionMsg = new wxStaticText(this, wxID_ANY, wxT(""));
299 fgs->Add(myRevisionMsg);
300 Bind(wxEVT_TEXT, &TargetPanel::onInput<wxCommandEvent>, this, ID_revisionCtrl);
302 fgs->Add(new wxStaticText(this, wxID_ANY, wxT("Serial number")));
309 wxSize(GetCharWidth() * 18, -1));
310 mySerialCtrl->SetMaxLength(16);
311 mySerialCtrl->SetToolTip(wxT("SCSI serial number. eg. '13eab5632a'"));
312 fgs->Add(mySerialCtrl);
313 mySerialMsg = new wxStaticText(this, wxID_ANY, wxT(""));
314 fgs->Add(mySerialMsg);
315 Bind(wxEVT_TEXT, &TargetPanel::onInput<wxCommandEvent>, this, ID_serialCtrl);
317 wxBoxSizer* hbox = new wxBoxSizer(wxHORIZONTAL);
318 hbox->Add(fgs, 1, wxALL | wxEXPAND, 15);
319 this->SetSizer(hbox);
323 setConfig(initialConfig);
328 TargetPanel::evaluate()
331 std::stringstream conv;
333 bool enabled = myEnableCtrl->IsChecked();
335 myScsiIdCtrl->Enable(enabled);
336 myDeviceTypeCtrl->Enable(enabled);
337 myParityCtrl->Enable(enabled);
338 myUnitAttCtrl->Enable(enabled);
339 myScsi2Ctrl->Enable(enabled);
340 myGlitchCtrl->Enable(enabled);
341 myStartSDSectorCtrl->Enable(enabled && !myAutoStartSectorCtrl->IsChecked());
342 myAutoStartSectorCtrl->Enable(enabled);
343 mySectorSizeCtrl->Enable(enabled);
344 myNumSectorCtrl->Enable(enabled);
345 mySizeCtrl->Enable(enabled);
346 mySizeUnitCtrl->Enable(enabled);
347 myVendorCtrl->Enable(enabled);
348 myProductCtrl->Enable(enabled);
349 myRevisionCtrl->Enable(enabled);
350 mySerialCtrl->Enable(enabled);
353 switch (myDeviceTypeCtrl->GetSelection())
356 mySectorSizeCtrl->ChangeValue("2048");
357 mySectorSizeCtrl->Enable(false);
359 case CONFIG_FLOPPY_14MB:
360 mySectorSizeCtrl->ChangeValue("512");
361 mySectorSizeCtrl->Enable(false);
362 myNumSectorCtrl->ChangeValue("2880");
363 myNumSectorCtrl->Enable(false);
364 mySizeUnitCtrl->Enable(false);
365 mySizeCtrl->Enable(false);
370 if (myAutoStartSectorCtrl->IsChecked())
372 std::stringstream ss; ss << myAutoStartSector;
373 myStartSDSectorCtrl->ChangeValue(ss.str());
376 uint32_t startSDsector;
378 conv << myStartSDSectorCtrl->GetValue();
379 conv >> startSDsector;
383 // TODO check if it is beyond the current SD card.
385 myStartSDSectorMsg->SetLabelMarkup(wxT("<span foreground='red' weight='bold'>Number >= 0</span>"));
390 myStartSDSectorMsg->SetLabelMarkup("");
392 conv.str(std::string()); conv.clear();
394 uint16_t sectorSize(CtrlGetValue<uint16_t>(mySectorSizeCtrl).first);
395 if (sectorSize < 64 || sectorSize > 8192)
397 mySectorSizeMsg->SetLabelMarkup(wxT("<span foreground='red' weight='bold'>Must be between 64 and 8192</span>"));
402 mySectorSizeMsg->SetLabelMarkup("");
404 conv.str(std::string()); conv.clear();
406 std::pair<uint32_t, bool> numSectors(CtrlGetValue<uint32_t>(myNumSectorCtrl));
407 if (!numSectors.second ||
408 numSectors.first == 0 ||
409 !convertUnitsToSectors().second)
411 myNumSectorMsg->SetLabelMarkup(wxT("<span foreground='red' weight='bold'>Invalid size</span>"));
416 myNumSectorMsg->SetLabelMarkup("");
419 if (!CtrlIsAscii(myVendorCtrl))
421 myVendorMsg->SetLabelMarkup(wxT("<span foreground='red' weight='bold'>Invalid characters</span>"));
426 myVendorMsg->SetLabelMarkup("");
429 if (!CtrlIsAscii(myProductCtrl))
431 myProductMsg->SetLabelMarkup(wxT("<span foreground='red' weight='bold'>Invalid characters</span>"));
436 myProductMsg->SetLabelMarkup("");
439 if (!CtrlIsAscii(myRevisionCtrl))
441 myRevisionMsg->SetLabelMarkup(wxT("<span foreground='red' weight='bold'>Invalid characters</span>"));
446 myRevisionMsg->SetLabelMarkup("");
449 if (!CtrlIsAscii(mySerialCtrl))
451 mySerialMsg->SetLabelMarkup(wxT("<span foreground='red' weight='bold'>Invalid characters</span>"));
456 mySerialMsg->SetLabelMarkup("");
459 return valid || !enabled;
462 template<typename EvtType> void
463 TargetPanel::onInput(EvtType& event)
465 wxCommandEvent changeEvent(ConfigChangedEvent);
466 wxPostEvent(myParent, changeEvent);
470 TargetPanel::onSizeInput(wxCommandEvent& event)
472 if (event.GetId() != ID_numSectorCtrl)
474 std::stringstream ss;
475 ss << convertUnitsToSectors().first;
476 myNumSectorCtrl->ChangeValue(ss.str());
479 onInput(event); // propagate
483 TargetPanel::evaluateSize()
486 std::stringstream conv;
487 conv << myNumSectorCtrl->GetValue();
490 conv.str(""); conv.clear();
495 uint64_t(numSectors) *
496 CtrlGetValue<uint16_t>(mySectorSizeCtrl).first;
498 if (bytes >= 1024 * 1024 * 1024)
500 conv << (bytes / (1024.0 * 1024 * 1024));
501 mySizeUnitCtrl->SetSelection(UNIT_GB);
503 else if (bytes >= 1024 * 1024)
505 conv << (bytes / (1024.0 * 1024));
506 mySizeUnitCtrl->SetSelection(UNIT_MB);
510 conv << (bytes / 1024.0);
511 mySizeUnitCtrl->SetSelection(UNIT_KB);
513 mySizeCtrl->ChangeValue(conv.str());
517 std::pair<uint32_t, bool>
518 TargetPanel::convertUnitsToSectors() const
522 uint64_t multiplier(0);
523 switch (mySizeUnitCtrl->GetSelection())
525 case UNIT_KB: multiplier = 1024; break;
526 case UNIT_MB: multiplier = 1024 * 1024; break;
527 case UNIT_GB: multiplier = 1024 * 1024 * 1024; break;
528 default: valid = false;
532 std::stringstream conv;
533 conv << mySizeCtrl->GetValue();
535 valid = valid && conv;
537 uint16_t sectorSize = CtrlGetValue<uint16_t>(mySectorSizeCtrl).first;
538 uint64_t sectors = ceil(multiplier * size / sectorSize);
540 if (sectors > std::numeric_limits<uint32_t>::max())
542 sectors = std::numeric_limits<uint32_t>::max();
546 return std::make_pair(static_cast<uint32_t>(sectors), valid);
551 TargetPanel::getConfig() const
555 // Try and keep unknown/unused fields as-is to support newer firmware
557 memcpy(&config, &myConfig, sizeof(config));
561 auto scsiId = CtrlGetValue<uint8_t>(myScsiIdCtrl);
562 config.scsiId = scsiId.first & CONFIG_TARGET_ID_BITS;
563 valid = valid && scsiId.second;
564 if (myEnableCtrl->IsChecked())
566 config.scsiId = config.scsiId | CONFIG_TARGET_ENABLED;
569 config.deviceType = myDeviceTypeCtrl->GetSelection();
572 (myParityCtrl->IsChecked() ? CONFIG_ENABLE_PARITY : 0) |
573 (myUnitAttCtrl->IsChecked() ? CONFIG_ENABLE_UNIT_ATTENTION : 0) |
574 (myScsi2Ctrl->IsChecked() ? CONFIG_ENABLE_SCSI2 : 0) |
575 (myGlitchCtrl->IsChecked() ? CONFIG_DISABLE_GLITCH : 0);
577 auto startSDSector = CtrlGetValue<uint32_t>(myStartSDSectorCtrl);
578 config.sdSectorStart = startSDSector.first;
579 valid = valid && startSDSector.second;
581 auto numSectors = CtrlGetValue<uint32_t>(myNumSectorCtrl);
582 config.scsiSectors = numSectors.first;
583 valid = valid && numSectors.second;
585 auto sectorSize = CtrlGetValue<uint16_t>(mySectorSizeCtrl);
586 config.bytesPerSector = sectorSize.first;
587 valid = valid && sectorSize.second;
589 CtrlGetFixedString(myVendorCtrl, config.vendor, sizeof(config.vendor));
590 CtrlGetFixedString(myProductCtrl, config.prodId, sizeof(config.prodId));
591 CtrlGetFixedString(myRevisionCtrl, config.revision, sizeof(config.revision));
592 CtrlGetFixedString(mySerialCtrl, config.serial, sizeof(config.serial));
598 TargetPanel::setConfig(const TargetConfig& config)
600 memcpy(&myConfig, &config, sizeof(config));
602 myScsiIdCtrl->SetValue(config.scsiId & CONFIG_TARGET_ID_BITS);
603 myEnableCtrl->SetValue(config.scsiId & CONFIG_TARGET_ENABLED);
605 myDeviceTypeCtrl->SetSelection(config.deviceType);
607 myParityCtrl->SetValue(config.flags & CONFIG_ENABLE_PARITY);
608 myUnitAttCtrl->SetValue(config.flags & CONFIG_ENABLE_UNIT_ATTENTION);
609 myScsi2Ctrl->SetValue(config.flags & CONFIG_ENABLE_SCSI2);
610 myGlitchCtrl->SetValue(config.flags & CONFIG_DISABLE_GLITCH);
613 std::stringstream ss; ss << config.sdSectorStart;
614 myStartSDSectorCtrl->ChangeValue(ss.str());
615 myAutoStartSectorCtrl->SetValue(0);
619 std::stringstream ss; ss << config.scsiSectors;
620 myNumSectorCtrl->ChangeValue(ss.str());
624 std::stringstream ss; ss << config.bytesPerSector;
625 mySectorSizeCtrl->ChangeValue(ss.str());
628 myVendorCtrl->ChangeValue(std::string(config.vendor, sizeof(config.vendor)));
629 myProductCtrl->ChangeValue(std::string(config.prodId, sizeof(config.prodId)));
630 myRevisionCtrl->ChangeValue(std::string(config.revision, sizeof(config.revision)));
631 mySerialCtrl->ChangeValue(std::string(config.serial, sizeof(config.serial)));
633 // Set the size fields based on sector size, and evaluate inputs.
634 wxCommandEvent fakeEvent(wxEVT_NULL, ID_numSectorCtrl);
635 onSizeInput(fakeEvent);
639 TargetPanel::isEnabled() const
641 return myEnableCtrl->IsChecked();
645 TargetPanel::getSCSIId() const
647 return CtrlGetValue<uint8_t>(myScsiIdCtrl).first & CONFIG_TARGET_ID_BITS;
650 std::pair<uint32_t, uint64_t>
651 TargetPanel::getSDSectorRange() const
653 std::pair<uint32_t, uint64_t> result;
654 result.first = CtrlGetValue<uint32_t>(myStartSDSectorCtrl).first;
656 uint32_t numSCSISectors = CtrlGetValue<uint32_t>(myNumSectorCtrl).first;
657 uint16_t scsiSectorSize = CtrlGetValue<uint16_t>(mySectorSizeCtrl).first;
659 const int sdSector = 512; // Always 512 for SDHC/SDXC
660 result.second = result.first +
662 ((uint64_t(numSCSISectors) * scsiSectorSize) + (sdSector - 1))
669 TargetPanel::setDuplicateID(bool duplicate)
673 myScsiIdMsg->SetLabelMarkup(wxT("<span foreground='red' weight='bold'>Duplicate ID</span>"));
677 myScsiIdMsg->SetLabelMarkup("");
682 TargetPanel::setSDSectorOverlap(bool overlap)
686 myStartSDSectorMsg->SetLabelMarkup(wxT("<span foreground='red' weight='bold'>Overlapping data</span>"));
690 myStartSDSectorMsg->SetLabelMarkup("");
695 TargetPanel::setAutoStartSector(uint32_t start)
697 myAutoStartSector = start;