// Copyright (C) 2014 Michael McMaster // // This file is part of SCSI2SD. // // SCSI2SD is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // // SCSI2SD is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License // along with SCSI2SD. If not, see . // For compilers that support precompilation, includes "wx/wx.h". #include #ifndef WX_PRECOMP #include #endif #include #include "ConfigUtil.hh" #include "TargetPanel.hh" #include #include #include #include using namespace SCSI2SD; wxDEFINE_EVENT(SCSI2SD::ConfigChangedEvent, wxCommandEvent); namespace { template std::pair CtrlGetValue(WXCTRL* ctrl) { IntType value; std::stringstream conv; conv << ctrl->GetValue(); conv >> value; return std::make_pair(value, static_cast(conv)); } void CtrlGetFixedString(wxTextEntry* ctrl, char* dest, size_t len) { memset(dest, ' ', len); strncpy(dest, ctrl->GetValue().ToAscii(), len); } bool CtrlIsAscii(wxTextEntry* ctrl) { return ctrl->GetValue().IsAscii(); } } TargetPanel::TargetPanel(wxWindow* parent, const TargetConfig& initialConfig) : wxPanel(parent), myParent(parent), myAutoStartSector(0), myStartSDSectorValidator(new wxIntegerValidator), mySectorSizeValidator(new wxIntegerValidator), myNumSectorValidator(new wxIntegerValidator), mySizeValidator(new wxFloatingPointValidator(2)) { wxFlexGridSizer *fgs = new wxFlexGridSizer(13, 3, 9, 25); fgs->Add(new wxStaticText(this, wxID_ANY, wxT(""))); myEnableCtrl = new wxCheckBox( this, ID_enableCtrl, wxT("Enable SCSI Target")); fgs->Add(myEnableCtrl); // Set a non-visible string to leave room in the column for future messages fgs->Add(new wxStaticText(this, wxID_ANY, wxT(" "))); Bind(wxEVT_CHECKBOX, &TargetPanel::onInput, this, ID_enableCtrl); fgs->Add(new wxStaticText(this, wxID_ANY, wxT("SCSI ID"))); myScsiIdCtrl = new wxSpinCtrl (this, ID_scsiIdCtrl, wxEmptyString, wxDefaultPosition, wxDefaultSize, wxSP_WRAP | wxSP_ARROW_KEYS, 0, 7, 0); fgs->Add(myScsiIdCtrl); myScsiIdMsg = new wxStaticText(this, wxID_ANY, wxT("")); fgs->Add(myScsiIdMsg); Bind(wxEVT_SPINCTRL, &TargetPanel::onInput, this, ID_scsiIdCtrl); fgs->Add(new wxStaticText(this, wxID_ANY, wxT("Device Type"))); wxString deviceTypes[] = { wxT("Hard Drive"), wxT("Removable"), wxT("CDROM"), wxT("3.5\" Floppy") }; myDeviceTypeCtrl = new wxChoice( this, ID_deviceTypeCtrl, wxDefaultPosition, wxDefaultSize, sizeof(deviceTypes) / sizeof(wxString), deviceTypes ); myDeviceTypeCtrl->SetSelection(0); fgs->Add(myDeviceTypeCtrl); fgs->Add(new wxStaticText(this, wxID_ANY, wxT(""))); Bind(wxEVT_CHOICE, &TargetPanel::onInput, this, ID_deviceTypeCtrl); fgs->Add(new wxStaticText(this, wxID_ANY, wxT(""))); myParityCtrl = new wxCheckBox( this, ID_parityCtrl, wxT("Enable Parity")); fgs->Add(myParityCtrl); fgs->Add(new wxStaticText(this, wxID_ANY, wxT(""))); Bind(wxEVT_CHECKBOX, &TargetPanel::onInput, this, ID_parityCtrl); fgs->Add(new wxStaticText(this, wxID_ANY, wxT(""))); myUnitAttCtrl = new wxCheckBox( this, ID_unitAttCtrl, wxT("Enable Unit Attention")); fgs->Add(myUnitAttCtrl); fgs->Add(new wxStaticText(this, wxID_ANY, wxT(""))); Bind(wxEVT_CHECKBOX, &TargetPanel::onInput, this, ID_unitAttCtrl); fgs->Add(new wxStaticText(this, wxID_ANY, wxT("SD card start sector"))); wxWrapSizer* startContainer = new wxWrapSizer(); myStartSDSectorCtrl = new wxTextCtrl( this, ID_startSDSectorCtrl, "0", wxDefaultPosition, wxDefaultSize, 0, *myStartSDSectorValidator); myStartSDSectorCtrl->SetToolTip(wxT("Supports multiple SCSI targets " "on a single memory card. In units of 512-byte sectors.")); startContainer->Add(myStartSDSectorCtrl); myAutoStartSectorCtrl = new wxCheckBox( this, ID_autoStartSectorCtrl, wxT("Auto")); startContainer->Add(myAutoStartSectorCtrl); Bind(wxEVT_CHECKBOX, &TargetPanel::onInput, this, ID_autoStartSectorCtrl); fgs->Add(startContainer); myStartSDSectorMsg = new wxStaticText(this, wxID_ANY, wxT("")); fgs->Add(myStartSDSectorMsg); Bind(wxEVT_TEXT, &TargetPanel::onInput, this, ID_startSDSectorCtrl); fgs->Add(new wxStaticText(this, wxID_ANY, wxT("Sector size (bytes)"))); mySectorSizeCtrl = new wxTextCtrl( this, ID_sectorSizeCtrl, "512", wxDefaultPosition, wxDefaultSize, 0, *mySectorSizeValidator); mySectorSizeCtrl->SetToolTip(wxT("Between 64 and 8192. Default of 512 is suitable in most cases.")); fgs->Add(mySectorSizeCtrl); mySectorSizeMsg = new wxStaticText(this, wxID_ANY, wxT("")); fgs->Add(mySectorSizeMsg); Bind(wxEVT_TEXT, &TargetPanel::onSizeInput, this, ID_sectorSizeCtrl); fgs->Add(new wxStaticText(this, wxID_ANY, wxT("Sector count"))); myNumSectorCtrl = new wxTextCtrl( this, ID_numSectorCtrl, "", wxDefaultPosition, wxDefaultSize, 0, *myNumSectorValidator); myNumSectorCtrl->SetToolTip(wxT("Number of sectors (device size)")); fgs->Add(myNumSectorCtrl); myNumSectorMsg = new wxStaticText(this, wxID_ANY, wxT("")); fgs->Add(myNumSectorMsg); Bind(wxEVT_TEXT, &TargetPanel::onSizeInput, this, ID_numSectorCtrl); fgs->Add(new wxStaticText(this, wxID_ANY, wxT("Device size"))); wxWrapSizer* sizeContainer = new wxWrapSizer(); mySizeCtrl = new wxTextCtrl( this, ID_sizeCtrl, "", wxDefaultPosition, wxDefaultSize, 0, *mySizeValidator); mySizeCtrl->SetToolTip(wxT("Device size")); sizeContainer->Add(mySizeCtrl); wxString units[] = {wxT("KB"), wxT("MB"), wxT("GB")}; mySizeUnitCtrl = new wxChoice( this, ID_sizeUnitCtrl, wxDefaultPosition, wxDefaultSize, sizeof(units) / sizeof(wxString), units ); sizeContainer->Add(mySizeUnitCtrl); fgs->Add(sizeContainer); fgs->Add(new wxStaticText(this, wxID_ANY, wxT(""))); Bind(wxEVT_TEXT, &TargetPanel::onSizeInput, this, ID_sizeCtrl); Bind(wxEVT_CHOICE, &TargetPanel::onSizeInput, this, ID_sizeUnitCtrl); fgs->Add(new wxStaticText(this, wxID_ANY, wxT("Vendor"))); myVendorCtrl = new wxTextCtrl( this, ID_vendorCtrl, wxEmptyString, wxDefaultPosition, wxSize(GetCharWidth() * 10, -1)); myVendorCtrl->SetMaxLength(8); myVendorCtrl->SetToolTip(wxT("SCSI Vendor string. eg. ' codesrc'")); fgs->Add(myVendorCtrl); myVendorMsg = new wxStaticText(this, wxID_ANY, wxT("")); fgs->Add(myVendorMsg); Bind(wxEVT_TEXT, &TargetPanel::onInput, this, ID_vendorCtrl); fgs->Add(new wxStaticText(this, wxID_ANY, wxT("Product ID"))); myProductCtrl = new wxTextCtrl( this, ID_productCtrl, wxEmptyString, wxDefaultPosition, wxSize(GetCharWidth() * 17, -1)); myProductCtrl->SetMaxLength(18); myProductCtrl->SetToolTip(wxT("SCSI Product ID string. eg. 'SCSI2SD'")); fgs->Add(myProductCtrl); myProductMsg = new wxStaticText(this, wxID_ANY, wxT("")); fgs->Add(myProductMsg); Bind(wxEVT_TEXT, &TargetPanel::onInput, this, ID_productCtrl); fgs->Add(new wxStaticText(this, wxID_ANY, wxT("Revision"))); myRevisionCtrl = new wxTextCtrl( this, ID_revisionCtrl, wxEmptyString, wxDefaultPosition, wxSize(GetCharWidth() * 6, -1)); myRevisionCtrl->SetMaxLength(4); myRevisionCtrl->SetToolTip(wxT("SCSI device revision string. eg. '3.5a'")); fgs->Add(myRevisionCtrl); myRevisionMsg = new wxStaticText(this, wxID_ANY, wxT("")); fgs->Add(myRevisionMsg); Bind(wxEVT_TEXT, &TargetPanel::onInput, this, ID_revisionCtrl); fgs->Add(new wxStaticText(this, wxID_ANY, wxT("Serial number"))); mySerialCtrl = new wxTextCtrl( this, ID_serialCtrl, wxEmptyString, wxDefaultPosition, wxSize(GetCharWidth() * 18, -1)); mySerialCtrl->SetMaxLength(16); mySerialCtrl->SetToolTip(wxT("SCSI serial number. eg. '13eab5632a'")); fgs->Add(mySerialCtrl); mySerialMsg = new wxStaticText(this, wxID_ANY, wxT("")); fgs->Add(mySerialMsg); Bind(wxEVT_TEXT, &TargetPanel::onInput, this, ID_serialCtrl); wxBoxSizer* hbox = new wxBoxSizer(wxHORIZONTAL); hbox->Add(fgs, 1, wxALL | wxEXPAND, 15); this->SetSizer(hbox); Centre(); setConfig(initialConfig); evaluate(); } bool TargetPanel::evaluate() { bool valid = true; std::stringstream conv; bool enabled = myEnableCtrl->IsChecked(); { myScsiIdCtrl->Enable(enabled); myDeviceTypeCtrl->Enable(enabled); myParityCtrl->Enable(enabled); myUnitAttCtrl->Enable(enabled); myStartSDSectorCtrl->Enable(enabled && !myAutoStartSectorCtrl->IsChecked()); myAutoStartSectorCtrl->Enable(enabled); mySectorSizeCtrl->Enable(enabled); myNumSectorCtrl->Enable(enabled); mySizeCtrl->Enable(enabled); mySizeUnitCtrl->Enable(enabled); myVendorCtrl->Enable(enabled); myProductCtrl->Enable(enabled); myRevisionCtrl->Enable(enabled); mySerialCtrl->Enable(enabled); } switch (myDeviceTypeCtrl->GetSelection()) { case CONFIG_OPTICAL: mySectorSizeCtrl->ChangeValue("2048"); mySectorSizeCtrl->Enable(false); break; case CONFIG_FLOPPY_14MB: mySectorSizeCtrl->ChangeValue("512"); mySectorSizeCtrl->Enable(false); myNumSectorCtrl->ChangeValue("2880"); myNumSectorCtrl->Enable(false); mySizeUnitCtrl->Enable(false); mySizeCtrl->Enable(false); break; }; evaluateSize(); if (myAutoStartSectorCtrl->IsChecked()) { std::stringstream ss; ss << myAutoStartSector; myStartSDSectorCtrl->ChangeValue(ss.str()); } uint32_t startSDsector; { conv << myStartSDSectorCtrl->GetValue(); conv >> startSDsector; } if (!conv) // TODO check if it is beyond the current SD card. { myStartSDSectorMsg->SetLabelMarkup(wxT("Number >= 0")); valid = false; } else { myStartSDSectorMsg->SetLabelMarkup(""); } conv.str(std::string()); conv.clear(); uint16_t sectorSize(CtrlGetValue(mySectorSizeCtrl).first); if (sectorSize < 64 || sectorSize > 8192) { mySectorSizeMsg->SetLabelMarkup(wxT("Must be between 64 and 8192")); valid = false; } else { mySectorSizeMsg->SetLabelMarkup(""); } conv.str(std::string()); conv.clear(); std::pair numSectors(CtrlGetValue(myNumSectorCtrl)); if (!numSectors.second || numSectors.first == 0 || !convertUnitsToSectors().second) { myNumSectorMsg->SetLabelMarkup(wxT("Invalid size")); valid = false; } else { myNumSectorMsg->SetLabelMarkup(""); } if (!CtrlIsAscii(myVendorCtrl)) { myVendorMsg->SetLabelMarkup(wxT("Invalid characters")); valid = false; } else { myVendorMsg->SetLabelMarkup(""); } if (!CtrlIsAscii(myProductCtrl)) { myProductMsg->SetLabelMarkup(wxT("Invalid characters")); valid = false; } else { myProductMsg->SetLabelMarkup(""); } if (!CtrlIsAscii(myRevisionCtrl)) { myRevisionMsg->SetLabelMarkup(wxT("Invalid characters")); valid = false; } else { myRevisionMsg->SetLabelMarkup(""); } if (!CtrlIsAscii(mySerialCtrl)) { mySerialMsg->SetLabelMarkup(wxT("Invalid characters")); valid = false; } else { mySerialMsg->SetLabelMarkup(""); } return valid || !enabled; } template void TargetPanel::onInput(EvtType& event) { wxCommandEvent changeEvent(ConfigChangedEvent); wxPostEvent(myParent, changeEvent); } void TargetPanel::onSizeInput(wxCommandEvent& event) { if (event.GetId() != ID_numSectorCtrl) { std::stringstream ss; ss << convertUnitsToSectors().first; myNumSectorCtrl->ChangeValue(ss.str()); } evaluateSize(); onInput(event); // propagate } void TargetPanel::evaluateSize() { uint32_t numSectors; std::stringstream conv; conv << myNumSectorCtrl->GetValue(); conv >> numSectors; conv.str(""); conv.clear(); if (conv) { uint64_t bytes = uint64_t(numSectors) * CtrlGetValue(mySectorSizeCtrl).first; if (bytes >= 1024 * 1024 * 1024) { conv << (bytes / (1024.0 * 1024 * 1024)); mySizeUnitCtrl->SetSelection(UNIT_GB); } else if (bytes >= 1024 * 1024) { conv << (bytes / (1024.0 * 1024)); mySizeUnitCtrl->SetSelection(UNIT_MB); } else { conv << (bytes / 1024.0); mySizeUnitCtrl->SetSelection(UNIT_KB); } mySizeCtrl->ChangeValue(conv.str()); } } std::pair TargetPanel::convertUnitsToSectors() const { bool valid = true; uint64_t multiplier(0); switch (mySizeUnitCtrl->GetSelection()) { case UNIT_KB: multiplier = 1024; break; case UNIT_MB: multiplier = 1024 * 1024; break; case UNIT_GB: multiplier = 1024 * 1024 * 1024; break; default: valid = false; } double size; std::stringstream conv; conv << mySizeCtrl->GetValue(); conv >> size; valid = valid && conv; uint16_t sectorSize = CtrlGetValue(mySectorSizeCtrl).first; uint64_t sectors = ceil(multiplier * size / sectorSize); if (sectors > std::numeric_limits::max()) { sectors = std::numeric_limits::max(); valid = false; } return std::make_pair(static_cast(sectors), valid); } TargetConfig TargetPanel::getConfig() const { TargetConfig config; // Try and keep unknown/unused fields as-is to support newer firmware // versions. memcpy(&config, &myConfig, sizeof(config)); bool valid = true; auto scsiId = CtrlGetValue(myScsiIdCtrl); config.scsiId = scsiId.first & CONFIG_TARGET_ID_BITS; valid = valid && scsiId.second; if (myEnableCtrl->IsChecked()) { config.scsiId = config.scsiId | CONFIG_TARGET_ENABLED; } config.deviceType = myDeviceTypeCtrl->GetSelection(); config.flags = (myParityCtrl->IsChecked() ? CONFIG_ENABLE_PARITY : 0) | (myUnitAttCtrl->IsChecked() ? CONFIG_ENABLE_UNIT_ATTENTION : 0); auto startSDSector = CtrlGetValue(myStartSDSectorCtrl); config.sdSectorStart = startSDSector.first; valid = valid && startSDSector.second; auto numSectors = CtrlGetValue(myNumSectorCtrl); config.scsiSectors = numSectors.first; valid = valid && numSectors.second; auto sectorSize = CtrlGetValue(mySectorSizeCtrl); config.bytesPerSector = sectorSize.first; valid = valid && sectorSize.second; CtrlGetFixedString(myVendorCtrl, config.vendor, sizeof(config.vendor)); CtrlGetFixedString(myProductCtrl, config.prodId, sizeof(config.prodId)); CtrlGetFixedString(myRevisionCtrl, config.revision, sizeof(config.revision)); CtrlGetFixedString(mySerialCtrl, config.serial, sizeof(config.serial)); return config; } void TargetPanel::setConfig(const TargetConfig& config) { memcpy(&myConfig, &config, sizeof(config)); myScsiIdCtrl->SetValue(config.scsiId & CONFIG_TARGET_ID_BITS); myEnableCtrl->SetValue(config.scsiId & CONFIG_TARGET_ENABLED); myDeviceTypeCtrl->SetSelection(config.deviceType); myParityCtrl->SetValue(config.flags & CONFIG_ENABLE_PARITY); myUnitAttCtrl->SetValue(config.flags & CONFIG_ENABLE_UNIT_ATTENTION); { std::stringstream ss; ss << config.sdSectorStart; myStartSDSectorCtrl->ChangeValue(ss.str()); myAutoStartSectorCtrl->SetValue(0); } { std::stringstream ss; ss << config.scsiSectors; myNumSectorCtrl->ChangeValue(ss.str()); } { std::stringstream ss; ss << config.bytesPerSector; mySectorSizeCtrl->ChangeValue(ss.str()); } myVendorCtrl->ChangeValue(std::string(config.vendor, sizeof(config.vendor))); myProductCtrl->ChangeValue(std::string(config.prodId, sizeof(config.prodId))); myRevisionCtrl->ChangeValue(std::string(config.revision, sizeof(config.revision))); mySerialCtrl->ChangeValue(std::string(config.serial, sizeof(config.serial))); // Set the size fields based on sector size, and evaluate inputs. wxCommandEvent fakeEvent(wxEVT_NULL, ID_numSectorCtrl); onSizeInput(fakeEvent); } bool TargetPanel::isEnabled() const { return myEnableCtrl->IsChecked(); } uint8_t TargetPanel::getSCSIId() const { return CtrlGetValue(myScsiIdCtrl).first & CONFIG_TARGET_ID_BITS; } std::pair TargetPanel::getSDSectorRange() const { std::pair result; result.first = CtrlGetValue(myStartSDSectorCtrl).first; uint32_t numSCSISectors = CtrlGetValue(myNumSectorCtrl).first; uint16_t scsiSectorSize = CtrlGetValue(mySectorSizeCtrl).first; const int sdSector = 512; // Always 512 for SDHC/SDXC result.second = result.first + ( ((uint64_t(numSCSISectors) * scsiSectorSize) + (sdSector - 1)) / sdSector ); return result; } void TargetPanel::setDuplicateID(bool duplicate) { if (duplicate) { myScsiIdMsg->SetLabelMarkup(wxT("Duplicate ID")); } else { myScsiIdMsg->SetLabelMarkup(""); } } void TargetPanel::setSDSectorOverlap(bool overlap) { if (overlap) { myStartSDSectorMsg->SetLabelMarkup(wxT("Overlapping data")); } else { myStartSDSectorMsg->SetLabelMarkup(""); } } void TargetPanel::setAutoStartSector(uint32_t start) { myAutoStartSector = start; }