mac-rom-simm-programmer.sof.../programmer.cpp

1696 lines
54 KiB
C++

/*
* Copyright (C) 2011-2012 Doug Brown
*
* This program 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 2
* of the License, or (at your option) any later version.
*
* This program 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 this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*
*/
#include "programmer.h"
#include <QDebug>
#include <QWaitCondition>
#include <QMutex>
typedef enum ProgrammerCommandState
{
WaitingForNextCommand = 0,
WriteSIMMWaitingSetSizeReply,
WriteSIMMWaitingSetVerifyModeReply,
WriteSIMMWaitingSetChipMaskReply,
WriteSIMMWaitingSetChipMaskValueReply,
WriteSIMMWaitingEraseReply,
WriteSIMMWaitingWriteReply,
WriteSIMMWaitingFinishReply,
WriteSIMMWaitingWriteMoreReply,
ElectricalTestWaitingStartReply,
ElectricalTestWaitingNextStatus,
ElectricalTestWaitingFirstFail,
ElectricalTestWaitingSecondFail,
ReadSIMMWaitingStartReply,
ReadSIMMWaitingStartOffsetReply,
ReadSIMMWaitingLengthReply,
ReadSIMMWaitingData,
ReadSIMMWaitingStatusReply,
BootloaderStateAwaitingOKReply,
BootloaderStateAwaitingReply,
BootloaderStateAwaitingOKReplyToBootloader,
BootloaderStateAwaitingReplyToBootloader,
BootloaderStateAwaitingUnplug,
BootloaderStateAwaitingPlug,
BootloaderStateAwaitingUnplugToBootloader,
BootloaderStateAwaitingPlugToBootloader,
IdentificationWaitingSetSizeReply,
IdentificationAwaitingOKReply,
IdentificationWaitingData,
IdentificationAwaitingDoneReply,
BootloaderEraseProgramAwaitingStartOKReply,
BootloaderEraseProgramWaitingFinishReply,
BootloaderEraseProgramWaitingWriteMoreReply,
BootloaderEraseProgramWaitingWriteReply,
WritePortionWaitingSetSizeReply,
WritePortionWaitingSetVerifyModeReply,
WritePortionWaitingSetChipMaskReply,
WritePortionWaitingSetChipMaskValueReply,
WritePortionWaitingEraseReply,
WritePortionWaitingEraseConfirmation,
WritePortionWaitingEraseResult,
WritePortionWaitingWriteAtReply
} ProgrammerCommandState;
typedef enum ProgrammerBoardFoundState
{
ProgrammerBoardNotFound,
ProgrammerBoardFound
} ProgrammerBoardFoundState;
typedef enum ProgrammerCommand
{
EnterWaitingMode = 0,
DoElectricalTest,
IdentifyChips,
ReadByte,
ReadChips,
EraseChips,
WriteChips,
GetBootloaderState,
EnterBootloader,
EnterProgrammer,
BootloaderEraseAndWriteProgram,
SetSIMMLayout_AddressStraight,
SetSIMMLayout_AddressShifted,
SetVerifyWhileWriting,
SetNoVerifyWhileWriting,
ErasePortion,
WriteChipsAt,
ReadChipsAt,
SetChipsMask
} ProgrammerCommand;
typedef enum ProgrammerReply
{
CommandReplyOK,
CommandReplyError,
CommandReplyInvalid
} ProgrammerReply;
typedef enum ComputerReadReply
{
ComputerReadOK,
ComputerReadCancel
} ComputerReadReply;
typedef enum ProgrammerReadReply
{
ProgrammerReadOK,
ProgrammerReadError,
ProgrammerReadMoreData,
ProgrammerReadFinished,
ProgrammerReadConfirmCancel
} ProgrammerReadReply;
typedef enum ComputerWriteReply
{
ComputerWriteMore,
ComputerWriteFinish,
ComputerWriteCancel
} ComputerWriteReply;
typedef enum ProgrammerWriteReply
{
ProgrammerWriteOK,
ProgrammerWriteError,
ProgrammerWriteConfirmCancel,
ProgrammerWriteVerificationError = 0x80 /* high bit */
} ProgrammerWriteReply;
typedef enum ProgrammerIdentifyReply
{
ProgrammerIdentifyDone
} ProgrammerIdentifyReply;
typedef enum ProgrammerElectricalTestReply
{
ProgrammerElectricalTestFail,
ProgrammerElectricalTestDone
} ProgrammerElectricalTestReply;
typedef enum BootloaderStateReply
{
BootloaderStateInBootloader,
BootloaderStateInProgrammer
} BootloaderStateReply;
typedef enum ProgrammerBootloaderEraseWriteReply
{
BootloaderWriteOK,
BootloaderWriteError,
BootloaderWriteConfirmCancel
} ProgrammerBootloaderEraseWriteReply;
typedef enum ComputerBootloaderEraseWriteRequest
{
ComputerBootloaderWriteMore = 0,
ComputerBootloaderFinish,
ComputerBootloaderCancel
} ComputerBootloaderEraseWriteRequest;
typedef enum ProgrammerErasePortionOfChipReply
{
ProgrammerErasePortionOK = 0,
ProgrammerErasePortionError,
ProgrammerErasePortionFinished
} ProgrammerErasePortionOfChipReply;
#define PROGRAMMER_USB_VENDOR_ID 0x16D0
#define PROGRAMMER_USB_DEVICE_ID 0x06AA
#define WRITE_CHUNK_SIZE 1024
#define READ_CHUNK_SIZE 1024
#define FIRMWARE_CHUNK_SIZE 1024
#define BLOCK_ERASE_SIZE (256*1024UL)
static ProgrammerCommandState curState = WaitingForNextCommand;
// After identifying that we're in the main program, what will be the command
// we will send and the state we will be waiting in?
static ProgrammerCommandState nextState = WaitingForNextCommand;
static uint8_t nextSendByte = 0;
static ProgrammerBoardFoundState foundState = ProgrammerBoardNotFound;
static QString programmerBoardPortName;
Programmer::Programmer(QObject *parent) :
QObject(parent)
{
_verifyMode = VerifyAfterWrite;
_verifyBadChipMask = 0;
verifyArray = new QByteArray();
verifyBuffer = new QBuffer(verifyArray);
verifyBuffer->open(QBuffer::ReadWrite);
serialPort = new QextSerialPort(QextSerialPort::EventDriven);
connect(serialPort, SIGNAL(readyRead()), SLOT(dataReady()));
}
Programmer::~Programmer()
{
closePort();
delete serialPort;
verifyBuffer->close();
delete verifyBuffer;
delete verifyArray;
}
void Programmer::readSIMM(QIODevice *device, uint32_t len)
{
// We're not verifying in this case
isReadVerifying = false;
internalReadSIMM(device, len);
}
void Programmer::internalReadSIMM(QIODevice *device, uint32_t len, uint32_t offset)
{
readDevice = device;
lenRead = 0;
readOffset = offset;
// Len == 0 means read the entire SIMM
if (len == 0)
{
lenRemaining = _simmCapacity;
trueLenToRead = _simmCapacity;
}
else if (len % READ_CHUNK_SIZE)
{
// We have to read a full chunk of data, so we read a little bit
// past the actual length requested but only return the amount
// requested.
uint32_t lastExtraChunk = (len % READ_CHUNK_SIZE);
lenRemaining = len - lastExtraChunk + READ_CHUNK_SIZE;
trueLenToRead = len;
}
else // already a multiple of READ_CHUNK_SIZE, no correction needed
{
lenRemaining = len;
trueLenToRead = len;
}
if (offset > 0)
{
startProgrammerCommand(ReadChipsAt, ReadSIMMWaitingStartOffsetReply);
}
else
{
startProgrammerCommand(ReadChips, ReadSIMMWaitingStartReply);
}
}
void Programmer::writeToSIMM(QIODevice *device, uint8_t chipsMask)
{
writeDevice = device;
writeChipMask = chipsMask;
if (writeDevice->size() > SIMMCapacity())
{
curState = WaitingForNextCommand;
emit writeStatusChanged(WriteFileTooBig);
return;
}
else
{
lenWritten = 0;
writeLenRemaining = writeDevice->size();
writeOffset = 0;
// Based on the SIMM size, tell the programmer board.
uint8_t setLayoutCommand;
if (SIMMCapacity() > 4*1024*1024)
{
setLayoutCommand = SetSIMMLayout_AddressShifted;
}
else
{
setLayoutCommand = SetSIMMLayout_AddressStraight;
}
startProgrammerCommand(setLayoutCommand, WriteSIMMWaitingSetSizeReply);
}
}
void Programmer::writeToSIMM(QIODevice *device, uint32_t startOffset, uint32_t length, uint8_t chipsMask)
{
writeDevice = device;
writeChipMask = chipsMask;
if ((writeDevice->size() > SIMMCapacity()) ||
(startOffset + length > SIMMCapacity()))
{
curState = WaitingForNextCommand;
emit writeStatusChanged(WriteFileTooBig);
return;
}
else if ((startOffset % BLOCK_ERASE_SIZE) || (length % BLOCK_ERASE_SIZE))
{
curState = WaitingForNextCommand;
emit writeStatusChanged(WriteEraseBlockWrongSize);
return;
}
else
{
lenWritten = 0;
writeLenRemaining = writeDevice->size() - startOffset;
if (writeLenRemaining > length)
{
writeLenRemaining = length;
}
device->seek(startOffset);
writeOffset = startOffset;
writeLength = length;
// Based on the SIMM size, tell the programmer board.
uint8_t setLayoutCommand;
if (SIMMCapacity() > 4*1024*1024)
{
setLayoutCommand = SetSIMMLayout_AddressShifted;
}
else
{
setLayoutCommand = SetSIMMLayout_AddressStraight;
}
startProgrammerCommand(setLayoutCommand, WritePortionWaitingSetSizeReply);
}
}
void Programmer::sendByte(uint8_t b)
{
serialPort->write((const char *)&b, 1);
}
void Programmer::sendWord(uint32_t w)
{
sendByte((w >> 0) & 0xFF);
sendByte((w >> 8) & 0xFF);
sendByte((w >> 16) & 0xFF);
sendByte((w >> 24) & 0xFF);
}
uint8_t Programmer::readByte()
{
uint8_t returnVal;
serialPort->read((char *)&returnVal, 1);
// TODO: Error checking if read fails?
return returnVal;
}
void Programmer::dataReady()
{
while (!serialPort->atEnd())
{
handleChar(readByte());
}
}
void Programmer::handleChar(uint8_t c)
{
switch (curState)
{
case WaitingForNextCommand:
// Not expecting anything. Ignore it.
break;
// Expecting reply after we told the programmer the size of SIMM to expect
case WriteSIMMWaitingSetSizeReply:
case WritePortionWaitingSetSizeReply:
switch (c)
{
case CommandReplyOK:
// If we got an OK reply, we're good to go. Next, check for the
// "verify while writing" capability if needed...
uint8_t verifyCommand;
if (verifyMode() == VerifyWhileWriting)
{
verifyCommand = SetVerifyWhileWriting;
}
else
{
verifyCommand = SetNoVerifyWhileWriting;
}
if (curState == WriteSIMMWaitingSetSizeReply)
{
curState = WriteSIMMWaitingSetVerifyModeReply;
}
else if (curState == WritePortionWaitingSetSizeReply)
{
curState = WritePortionWaitingSetVerifyModeReply;
}
sendByte(verifyCommand);
break;
case CommandReplyInvalid:
case CommandReplyError:
// If we got an error reply, we MAY still be OK unless we were
// requesting the large SIMM type, in which case the firmware
// doesn't support the large SIMM type so the user needs to know.
if (SIMMCapacity() > 2*1024*1024)
{
// Uh oh -- this is an old firmware that doesn't support a big
// SIMM. Let the caller know that the programmer board needs a
// firmware update.
qDebug() << "Programmer board needs firmware update.";
curState = WaitingForNextCommand;
closePort();
emit writeStatusChanged(WriteNeedsFirmwareUpdateBiggerSIMM);
}
else
{
// Error reply, but we're writing a small SIMM, so the firmware
// doesn't need updating -- it just didn't know how to handle
// the "set size" command. But that's OK -- it only supports
// the size we requested, so nothing's wrong.
// So...check for the "verify while writing" capability if needed.
uint8_t verifyCommand;
if (verifyMode() == VerifyWhileWriting)
{
verifyCommand = SetVerifyWhileWriting;
}
else
{
verifyCommand = SetNoVerifyWhileWriting;
}
if (curState == WriteSIMMWaitingSetSizeReply)
{
curState = WriteSIMMWaitingSetVerifyModeReply;
}
else if (curState == WritePortionWaitingSetSizeReply)
{
curState = WritePortionWaitingSetVerifyModeReply;
}
sendByte(verifyCommand);
}
break;
}
break;
// Expecting reply from programmer after we told it to verify during write
// (or not to verify during write)
case WriteSIMMWaitingSetVerifyModeReply:
case WritePortionWaitingSetVerifyModeReply:
switch (c)
{
case CommandReplyOK:
// If we got an OK reply, we're good. Now try to set the chip mask.
if (curState == WriteSIMMWaitingSetVerifyModeReply)
{
sendByte(SetChipsMask);
curState = WriteSIMMWaitingSetChipMaskReply;
}
else if (curState == WritePortionWaitingSetVerifyModeReply)
{
sendByte(SetChipsMask);
curState = WritePortionWaitingSetChipMaskReply;
}
break;
case CommandReplyInvalid:
case CommandReplyError:
// If we got an error reply, we MAY still be OK unless we were
// asking to verify while writing, in which case the firmware
// doesn't support verify during write so the user needs to know.
if (verifyMode() == VerifyWhileWriting)
{
// Uh oh -- this is an old firmware that doesn't support verify
// while write. Let the caller know that the programmer board
// needs a firmware update.
qDebug() << "Programmer board needs firmware update.";
curState = WaitingForNextCommand;
closePort();
emit writeStatusChanged(WriteNeedsFirmwareUpdateVerifyWhileWrite);
}
else
{
// Error reply, but we're not trying to verify while writing, so
// the firmware doesn't need updating -- it just didn't know how to handle
// the "set verify mode" command. But that's OK -- we don't need
// that command if we're not verifying while writing.
// So move onto the next thing to try.
if (curState == WriteSIMMWaitingSetVerifyModeReply)
{
sendByte(SetChipsMask);
curState = WriteSIMMWaitingSetChipMaskReply;
}
else if (curState == WritePortionWaitingSetVerifyModeReply)
{
sendByte(SetChipsMask);
curState = WritePortionWaitingSetChipMaskReply;
}
}
break;
}
break;
case WriteSIMMWaitingSetChipMaskReply:
case WritePortionWaitingSetChipMaskReply:
switch (c)
{
case CommandReplyOK:
// OK, now we can send the chip mask and move onto the next state
sendByte(writeChipMask);
if (curState == WriteSIMMWaitingSetChipMaskReply)
{
curState = WriteSIMMWaitingSetChipMaskValueReply;
}
else if (curState == WritePortionWaitingSetChipMaskReply)
{
curState = WritePortionWaitingSetChipMaskValueReply;
}
break;
case CommandReplyInvalid:
case CommandReplyError:
// Error reply. If we're trying to set a mask of 0x0F, no error, it
// just means the firmware's out of date and doesn't support setting
// custom chip masks. Ignore and move on.
if (writeChipMask == 0x0F)
{
// OK, erase the SIMM and get the ball rolling.
// Special case: Send out notification we are starting an erase command.
// I don't have any hooks into the process between now and the erase reply.
emit writeStatusChanged(WriteErasing);
if (curState == WriteSIMMWaitingSetChipMaskReply)
{
sendByte(EraseChips);
curState = WriteSIMMWaitingEraseReply;
}
else if (curState == WritePortionWaitingSetChipMaskReply)
{
sendByte(ErasePortion);
curState = WritePortionWaitingEraseReply;
}
}
else
{
// Uh oh -- this is an old firmware that doesn't support custom
// chip masks. Let the caller know that the programmer board
// needs a firmware update.
qDebug() << "Programmer board needs firmware update.";
curState = WaitingForNextCommand;
closePort();
emit writeStatusChanged(WriteNeedsFirmwareUpdateIndividualChips);
}
break;
}
break;
case WriteSIMMWaitingSetChipMaskValueReply:
case WritePortionWaitingSetChipMaskValueReply:
switch (c)
{
case CommandReplyOK:
// OK, erase the SIMM and get the ball rolling.
// Special case: Send out notification we are starting an erase command.
// I don't have any hooks into the process between now and the erase reply.
emit writeStatusChanged(WriteErasing);
if (curState == WriteSIMMWaitingSetChipMaskValueReply)
{
sendByte(EraseChips);
curState = WriteSIMMWaitingEraseReply;
}
else if (curState == WritePortionWaitingSetChipMaskValueReply)
{
sendByte(ErasePortion);
curState = WritePortionWaitingEraseReply;
}
break;
case CommandReplyInvalid:
case CommandReplyError:
// Error after trying to set the value.
qDebug() << "Error reply setting chip mask.";
curState = WaitingForNextCommand;
closePort();
emit writeStatusChanged(WriteError);
break;
}
break;
// Expecting reply from programmer after we told it to erase the chip
case WriteSIMMWaitingEraseReply:
{
switch (c)
{
case CommandReplyOK:
sendByte(WriteChips);
curState = WriteSIMMWaitingWriteReply;
qDebug() << "Chips erased. Now asking to start writing...";
emit writeStatusChanged(WriteEraseComplete);
emit writeTotalLengthChanged(writeLenRemaining);
emit writeCompletionLengthChanged(lenWritten);
break;
case CommandReplyError:
qDebug() << "Error erasing chips.";
curState = WaitingForNextCommand;
closePort();
emit writeStatusChanged(WriteEraseFailed);
break;
}
break;
}
case WritePortionWaitingEraseReply:
{
switch (c)
{
case CommandReplyOK:
sendWord(writeOffset);
sendWord(writeLength);
qDebug("Sending %u, %u", writeOffset, writeLength);
curState = WritePortionWaitingEraseConfirmation;
qDebug() << "Sent erase positions, waiting for reply...";
break;
case CommandReplyError:
// Uh oh -- this is an old firmware that doesn't support verify
// while write. Let the caller know that the programmer board
// needs a firmware update.
qDebug() << "Programmer board needs firmware update.";
curState = WaitingForNextCommand;
closePort();
emit writeStatusChanged(WriteNeedsFirmwareUpdateErasePortion);
break;
}
break;
}
case WritePortionWaitingEraseConfirmation:
{
switch (c)
{
case ProgrammerErasePortionOK:
curState = WritePortionWaitingEraseResult;
break;
case ProgrammerErasePortionError:
// Programmer didn't like the position/length we gave it
qDebug() << "Programmer didn't like erase pos/length.";
curState = WaitingForNextCommand;
closePort();
emit writeStatusChanged(WriteEraseFailed);
break;
}
break;
}
case WritePortionWaitingEraseResult:
{
switch (c)
{
case ProgrammerErasePortionFinished:
// we're done erasing, now it's time to write the data
// starting at where we wanted to flash to
sendByte(WriteChipsAt);
curState = WritePortionWaitingWriteAtReply;
qDebug() << "Chips partially erased. Now asking to start writing...";
emit writeStatusChanged(WriteEraseComplete);
break;
case ProgrammerErasePortionError:
// Programmer failed to erase
qDebug() << "Programmer had error erasing.";
curState = WaitingForNextCommand;
closePort();
emit writeStatusChanged(WriteEraseFailed);
break;
}
break;
}
case WritePortionWaitingWriteAtReply:
{
switch (c)
{
case CommandReplyOK:
sendWord(writeOffset);
qDebug() << "Sending" << writeOffset;
curState = WriteSIMMWaitingWriteReply;
emit writeTotalLengthChanged(writeLenRemaining);
emit writeCompletionLengthChanged(lenWritten);
qDebug() << "Partial write command accepted, sending offset...";
break;
case CommandReplyError:
case CommandReplyInvalid:
default:
// Programmer failed to erase
qDebug() << "Programmer didn't accept 'write at' command.";
curState = WaitingForNextCommand;
closePort();
emit writeStatusChanged(WriteError);
break;
}
break;
}
// Expecting reply from programmer after we sent a chunk of data to write
// (or after we first told it we're going to start writing)
case WriteSIMMWaitingWriteReply:
// This is a special case in the protocol for efficiency.
if (c & ProgrammerWriteVerificationError)
{
_verifyBadChipMask = c & ~ProgrammerWriteVerificationError;
qDebug() << "Verification error during write.";
curState = WaitingForNextCommand;
closePort();
emit writeStatusChanged(WriteVerificationFailure);
break;
}
else
{
switch (c)
{
case CommandReplyOK:
// We're in write SIMM mode. Now ask to start writing
if (writeLenRemaining > 0)
{
sendByte(ComputerWriteMore);
curState = WriteSIMMWaitingWriteMoreReply;
qDebug() << "Write more..." << writeLenRemaining << "remaining.";
}
else
{
sendByte(ComputerWriteFinish);
curState = WriteSIMMWaitingFinishReply;
qDebug() << "Finished writing. Sending write finish command...";
}
break;
case CommandReplyError:
qDebug() << "Error entering write mode.";
curState = WaitingForNextCommand;
closePort();
emit writeStatusChanged(WriteError);
break;
}
}
break;
// Expecting reply from programmer after we requested to write another data chunk
case WriteSIMMWaitingWriteMoreReply:
{
qDebug() << "Write more reply:" << c;
switch (c)
{
case ProgrammerWriteOK:
{
qDebug() << "Programmer replied OK to send 1024 bytes of data! Sending...";
// Write the next chunk of data to the SIMM...
int chunkSize = WRITE_CHUNK_SIZE;
if (writeLenRemaining < WRITE_CHUNK_SIZE)
{
chunkSize = writeLenRemaining;
}
// Read the chunk from the file!
QByteArray thisChunk = writeDevice->read(chunkSize);
// If it isn't a WRITE_CHUNK_SIZE chunk, pad the rest of it with 0xFFs (unprogrammed bytes)
// so the total chunk size is WRITE_CHUNK_SIZE, since that's what the programmer board expects.
for (int x = writeLenRemaining; x < WRITE_CHUNK_SIZE; x++)
{
thisChunk.append(0xFF);
}
// Write the chunk out (it's asynchronous so will return immediately)
serialPort->write(thisChunk);
// OK, now we're waiting to hear back from the programmer on the result
qDebug() << "Waiting for status reply...";
curState = WriteSIMMWaitingWriteReply;
writeLenRemaining -= chunkSize;
lenWritten += chunkSize;
emit writeCompletionLengthChanged(lenWritten);
break;
}
case ProgrammerWriteError:
default:
qDebug() << "Error writing to chips.";
curState = WaitingForNextCommand;
closePort();
emit writeStatusChanged(WriteError);
break;
}
break;
}
// Expecting reply from programmer after we told it we're done writing
case WriteSIMMWaitingFinishReply:
switch (c)
{
case ProgrammerWriteOK:
if (verifyMode() == VerifyAfterWrite)
{
isReadVerifying = true;
// Ensure the verify buffer is empty
verifyArray->clear();
verifyBuffer->seek(0);
verifyLength = lenWritten;
// Start reading from the SIMM now!
emit writeStatusChanged(WriteVerifying);
internalReadSIMM(verifyBuffer, writeDevice->size());
}
else
{
curState = WaitingForNextCommand;
qDebug() << "Write success at end";
closePort();
// Emit the correct signal based on how we finished
if (verifyMode() == NoVerification)
{
emit writeStatusChanged(WriteCompleteNoVerify);
}
else
{
emit writeStatusChanged(WriteCompleteVerifyOK);
}
}
break;
case ProgrammerWriteError:
default:
qDebug() << "Write failure at end";
curState = WaitingForNextCommand;
closePort();
emit writeStatusChanged(WriteError);
break;
}
break;
// ELECTRICAL TEST STATE HANDLERS
// Expecting reply from programmer after we told it to run an electrical test
case ElectricalTestWaitingStartReply:
switch (c)
{
case CommandReplyOK:
curState = ElectricalTestWaitingNextStatus;
emit electricalTestStatusChanged(ElectricalTestStarted);
electricalTestErrorCounter = 0;
break;
case CommandReplyError:
case CommandReplyInvalid:
default:
curState = WaitingForNextCommand;
closePort();
emit electricalTestStatusChanged(ElectricalTestCouldntStart);
}
break;
// Expecting info from programmer about the electrical test in progress
// (Either that it's done or that it found a failure)
case ElectricalTestWaitingNextStatus:
switch (c)
{
case ProgrammerElectricalTestDone:
curState = WaitingForNextCommand;
closePort();
if (electricalTestErrorCounter > 0)
{
emit electricalTestStatusChanged(ElectricalTestFailed);
}
else
{
emit electricalTestStatusChanged(ElectricalTestPassed);
}
break;
case ProgrammerElectricalTestFail:
electricalTestErrorCounter++;
curState = ElectricalTestWaitingFirstFail;
break;
}
break;
// Expecting electrical test fail location #1
case ElectricalTestWaitingFirstFail:
electricalTestFirstErrorLoc = c;
curState = ElectricalTestWaitingSecondFail;
break;
// Expecting electrical test fail location #2
case ElectricalTestWaitingSecondFail:
emit electricalTestFailLocation(electricalTestFirstErrorLoc, c);
curState = ElectricalTestWaitingNextStatus;
break;
// READ SIMM STATE HANDLERS
// Expecting reply after we told the programmer to start reading
case ReadSIMMWaitingStartReply:
case ReadSIMMWaitingStartOffsetReply:
switch (c)
{
case CommandReplyOK:
if (!isReadVerifying)
{
emit readStatusChanged(ReadStarting);
}
else
{
emit writeStatusChanged(WriteVerifyStarting);
}
curState = ReadSIMMWaitingLengthReply;
// Send the length requesting to be read (and offset if needed)
if (c == ReadSIMMWaitingStartOffsetReply)
{
sendWord(readOffset);
}
sendWord(lenRemaining);
// Now wait for the go-ahead from the programmer's side
break;
case CommandReplyError:
case CommandReplyInvalid:
default:
curState = WaitingForNextCommand;
closePort();
if (!isReadVerifying)
{
emit readStatusChanged(ReadError);
}
else
{
// Ensure the verify buffer is empty if we were verifying
verifyArray->clear();
verifyBuffer->seek(0);
emit writeStatusChanged(WriteVerifyError);
}
break;
}
break;
// Expecting reply after we gave the programmer a length to read
case ReadSIMMWaitingLengthReply:
switch (c)
{
case ProgrammerReadOK:
curState = ReadSIMMWaitingData;
if (!isReadVerifying)
{
emit readTotalLengthChanged(lenRemaining);
emit readCompletionLengthChanged(0);
}
else
{
emit writeVerifyTotalLengthChanged(lenRemaining);
emit writeVerifyCompletionLengthChanged(0);
}
readChunkLenRemaining = READ_CHUNK_SIZE;
break;
case ProgrammerReadError:
default:
curState = WaitingForNextCommand;
closePort();
if (!isReadVerifying)
{
emit readStatusChanged(ReadError);
}
else
{
// Ensure the verify buffer is empty if we were verifying
verifyArray->clear();
verifyBuffer->seek(0);
emit writeStatusChanged(WriteVerifyError);
}
break;
}
break;
// Expecting a chunk of data back from the programmer
case ReadSIMMWaitingData:
// Only keep adding to the readback if we need to
if (lenRead < trueLenToRead)
{
readDevice->write((const char *)&c, 1);
}
lenRead++;
if (--readChunkLenRemaining == 0)
{
if (!isReadVerifying)
{
emit readCompletionLengthChanged(lenRead);
}
else
{
emit writeVerifyCompletionLengthChanged(lenRead);
}
qDebug() << "Received a chunk of data";
sendByte(ComputerReadOK);
curState = ReadSIMMWaitingStatusReply;
}
break;
// Expecting status reply from programmer after we confirmed reception of
// previous chunk of data
case ReadSIMMWaitingStatusReply:
switch (c)
{
case ProgrammerReadFinished:
curState = WaitingForNextCommand;
closePort();
if (!isReadVerifying)
{
emit readStatusChanged(ReadComplete);
}
else
{
doVerifyAfterWriteCompare();
}
break;
case ProgrammerReadConfirmCancel:
curState = WaitingForNextCommand;
closePort();
if (!isReadVerifying)
{
emit readStatusChanged(ReadCancelled);
}
else
{
// Ensure the verify buffer is empty if we were verifying
verifyArray->clear();
verifyBuffer->seek(0);
emit writeStatusChanged(WriteVerifyCancelled);
}
break;
case ProgrammerReadMoreData:
curState = ReadSIMMWaitingData;
readChunkLenRemaining = READ_CHUNK_SIZE;
break;
}
break;
// BOOTLOADER STATE HANDLERS
// Expecting reply after we asked for bootloader state (original request is
// to end up in programmer mode)
case BootloaderStateAwaitingOKReply:
if (c == CommandReplyOK)
{
// Good to go, now we're waiting for the "in programmer" or "in bootloader" reply.
curState = BootloaderStateAwaitingReply;
}
else
{
curState = WaitingForNextCommand;
qDebug() << "Unable to enter programmer mode";
// TODO: Error out somehow
}
break;
// Expecting bootloader state after request was confirmed (original request
// is to end up in programmer mode)
case BootloaderStateAwaitingReply:
switch (c)
{
case BootloaderStateInBootloader:
// Oops! We're in the bootloader. Better change over to the programmer.
qDebug() << "We're in the bootloader, so sending an \"enter programmer\" request.";
emit startStatusChanged(ProgrammerInitializing);
sendByte(EnterProgrammer);
closePort();
// Now wait for it to reconnect
curState = BootloaderStateAwaitingUnplug;
break;
case BootloaderStateInProgrammer:
// Good to go...
// So change to the next state and send out the next command
// to begin whatever sequence of events we expected.
qDebug() << "Already in programmer. Good! Do the command now...";
emit startStatusChanged(ProgrammerInitialized);
curState = nextState;
sendByte(nextSendByte);
break;
// TODO: Otherwise, raise an error?
}
break;
// Expecting reply after we asked for bootloader state (original request is
// to end up in bootloader mode)
case BootloaderStateAwaitingOKReplyToBootloader:
if (c == CommandReplyOK)
{
// Good to go, now we're waiting for the "in programmer" or "in bootloader" reply.
curState = BootloaderStateAwaitingReplyToBootloader;
}
else
{
curState = WaitingForNextCommand;
qDebug() << "Unable to enter bootloader mode";
// TODO: Error out somehow
}
break;
// Expecting bootloader state after request was confirmed (original request
// is to end up in bootloader mode)
case BootloaderStateAwaitingReplyToBootloader:
switch (c)
{
case BootloaderStateInProgrammer:
// Oops! We're in the programmer. Better change over to the bootloader.
qDebug() << "We're in the programmer, so sending an \"enter bootloader\" request.";
emit startStatusChanged(ProgrammerInitializing);
sendByte(EnterBootloader);
closePort();
// Now wait for it to reconnect
curState = BootloaderStateAwaitingUnplugToBootloader;
break;
case BootloaderStateInBootloader:
// Good to go...
// So change to the next state and send out the next command
// to begin whatever sequence of events we expected.
qDebug() << "Already in bootloader. Good! Do the command now...";
emit startStatusChanged(ProgrammerInitialized);
curState = nextState;
sendByte(nextSendByte);
break;
// TODO: Otherwise, raise an error?
}
break;
// IDENTIFICATION STATE HANDLERS
// // Expecting reply after we told the programmer what size of SIMM to use
case IdentificationWaitingSetSizeReply:
switch (c)
{
case CommandReplyOK:
// If we got an OK reply, we're ready to go, so start...
sendByte(IdentifyChips);
curState = IdentificationAwaitingOKReply;
break;
case CommandReplyInvalid:
case CommandReplyError:
// If we got an error reply, we MAY still be OK unless we were
// requesting the large SIMM type, in which case the firmware
// doesn't support the large SIMM type so the user needs to know.
if (SIMMCapacity() > 2*1024*1024)
{
// Uh oh -- this is an old firmware that doesn't support a big
// SIMM. Let the caller know that the programmer board needs a
// firmware update.
qDebug() << "Programmer board needs firmware update.";
curState = WaitingForNextCommand;
closePort();
emit identificationStatusChanged(IdentificationNeedsFirmwareUpdate);
}
else
{
// Error reply, but we're identifying a small SIMM, so the firmware
// doesn't need updating -- it just didn't know how to handle
// the "set size" command. But that's OK -- it only supports
// the size we requested, so nothing's wrong.
sendByte(IdentifyChips);
curState = IdentificationAwaitingOKReply;
}
break;
}
break;
// Expecting reply after we asked to identify chips
case IdentificationAwaitingOKReply:
if (c == CommandReplyOK)
{
// Good to go, now waiting for identification data
emit identificationStatusChanged(IdentificationStarting);
curState = IdentificationWaitingData;
identificationCounter = 0;
}
else
{
// Error -- close the port, we're done!
closePort();
emit identificationStatusChanged(IdentificationError);
curState = WaitingForNextCommand;
}
break;
// Expecting device/manufacturer info about the chips
case IdentificationWaitingData:
if (identificationCounter & 1) // device ID?
{
chipDeviceIDs[identificationCounter/2] = c;
}
else // manufacturer ID?
{
chipManufacturerIDs[identificationCounter/2] = c;
}
// All done?
if (++identificationCounter >= 8)
{
curState = IdentificationAwaitingDoneReply;
}
break;
// Expecting final done confirmation after receiving all device/manufacturer info
case IdentificationAwaitingDoneReply:
curState = WaitingForNextCommand;
closePort();
if (c == ProgrammerIdentifyDone)
{
emit identificationStatusChanged(IdentificationComplete);
}
else
{
emit identificationStatusChanged(IdentificationError);
}
break;
// WRITE BOOTLOADER PROGRAM STATE HANDLERS
// Expecting reply after we asked to flash the firmware
case BootloaderEraseProgramAwaitingStartOKReply:
if (c == CommandReplyOK)
{
emit firmwareFlashStatusChanged(FirmwareFlashStarting);
sendByte(ComputerBootloaderWriteMore);
curState = BootloaderEraseProgramWaitingWriteMoreReply;
}
else
{
curState = WaitingForNextCommand;
closePort();
firmwareFile->close();
emit firmwareFlashStatusChanged(FirmwareFlashError);
}
break;
// Expecting reply after we told bootloader we're done flashing firmware
case BootloaderEraseProgramWaitingFinishReply:
if (c == BootloaderWriteOK)
{
curState = WaitingForNextCommand;
closePort();
firmwareFile->close();
emit firmwareFlashStatusChanged(FirmwareFlashComplete);
}
else
{
curState = WaitingForNextCommand;
closePort();
firmwareFile->close();
emit firmwareFlashStatusChanged(FirmwareFlashError);
}
break;
// Expecting reply after we asked to write more firmware data
case BootloaderEraseProgramWaitingWriteMoreReply:
if (c == BootloaderWriteOK)
{
// Send the next chunk of data
qDebug() << "Bootloader replied OK to send 1024 bytes of data! Sending...";
int chunkSize = FIRMWARE_CHUNK_SIZE;
if (firmwareLenRemaining < FIRMWARE_CHUNK_SIZE)
{
chunkSize = firmwareLenRemaining;
}
// Read the chunk from the file!
QByteArray thisChunk = firmwareFile->read(chunkSize);
// If it isn't FIRMWARE_CHUNK_SIZE, pad the rest with 0xFF
// (unprogrammed bytes)
for (int x = firmwareLenRemaining; x < FIRMWARE_CHUNK_SIZE; x++)
{
thisChunk.append(0xFF);
}
// Write the chunk out (it's asynchronous so will return immediately)
serialPort->write(thisChunk);
// OK, now we're waiting to hear back from the programmer on the result
qDebug() << "Waiting for status reply...";
curState = BootloaderEraseProgramWaitingWriteReply;
firmwareLenRemaining -= chunkSize;
firmwareLenWritten += chunkSize;
emit firmwareFlashCompletionLengthChanged(firmwareLenWritten);
}
else
{
curState = WaitingForNextCommand;
closePort();
firmwareFile->close();
emit firmwareFlashStatusChanged(FirmwareFlashError);
}
break;
// Expecting reply after we sent a chunk of firmware data
case BootloaderEraseProgramWaitingWriteReply:
if (c == CommandReplyOK)
{
// Either ask to send the next chunk, or send a "finish" response
if (firmwareLenRemaining > 0)
{
sendByte(ComputerBootloaderWriteMore);
curState = BootloaderEraseProgramWaitingWriteMoreReply;
}
else
{
sendByte(ComputerBootloaderFinish);
curState = BootloaderEraseProgramWaitingFinishReply;
}
}
else
{
curState = WaitingForNextCommand;
closePort();
firmwareFile->close();
emit firmwareFlashStatusChanged(FirmwareFlashError);
}
break;
// UNUSED STATE HANDLERS (They are handled elsewhere)
case BootloaderStateAwaitingPlug:
case BootloaderStateAwaitingUnplug:
case BootloaderStateAwaitingPlugToBootloader:
case BootloaderStateAwaitingUnplugToBootloader:
break;
}
}
void Programmer::runElectricalTest()
{
startProgrammerCommand(DoElectricalTest, ElectricalTestWaitingStartReply);
}
QString Programmer::electricalTestPinName(uint8_t index)
{
if (index <= LAST_ADDRESS_LINE_FAIL_INDEX)
{
return QString().sprintf("A%d", index - FIRST_ADDRESS_LINE_FAIL_INDEX);
}
else if (index <= LAST_DATA_LINE_FAIL_INDEX)
{
// The byte ordering is backwards to the labeling, so I have to fix that.
// Reverse the byte ordering so we have the correct number in terms of how
// D0 to D31 are labeled...
index = index - FIRST_DATA_LINE_FAIL_INDEX;
if (index < 8)
{
index = index + 24;
}
else if (index < 16)
{
index = index + 8;
}
else if (index < 24)
{
index = index - 8;
}
else
{
index = index - 24;
}
return QString().sprintf("D%d", index);
}
else if (index == CS_FAIL_INDEX)
{
return "CS";
}
else if (index == OE_FAIL_INDEX)
{
return "OE";
}
else if (index == WE_FAIL_INDEX)
{
return "WE";
}
else if (index == GROUND_FAIL_INDEX)
{
return "GND";
}
else
{
return "?";
}
}
void Programmer::identifySIMMChips()
{
//startProgrammerCommand(IdentifyChips, IdentificationAwaitingOKReply);
// Based on the SIMM size, tell the programmer board.
uint8_t setLayoutCommand;
if (SIMMCapacity() > 4*1024*1024)
{
setLayoutCommand = SetSIMMLayout_AddressShifted;
}
else
{
setLayoutCommand = SetSIMMLayout_AddressStraight;
}
startProgrammerCommand(setLayoutCommand, IdentificationWaitingSetSizeReply);
}
void Programmer::getChipIdentity(int chipIndex, uint8_t *manufacturer, uint8_t *device)
{
if ((chipIndex >= 0) && (chipIndex < 4))
{
*manufacturer = chipManufacturerIDs[chipIndex];
*device = chipDeviceIDs[chipIndex];
}
else
{
*manufacturer = 0;
*device = 0;
}
}
void Programmer::flashFirmware(QString filename)
{
firmwareFile = new QFile(filename);
if (!firmwareFile->open(QFile::ReadOnly))
{
curState = WaitingForNextCommand;
emit firmwareFlashStatusChanged(FirmwareFlashError);
return;
}
firmwareLenWritten = 0;
firmwareLenRemaining = firmwareFile->size();
emit firmwareFlashTotalLengthChanged(firmwareLenRemaining);
emit firmwareFlashCompletionLengthChanged(firmwareLenWritten);
startBootloaderCommand(BootloaderEraseAndWriteProgram, BootloaderEraseProgramAwaitingStartOKReply);
}
// Begins a command by opening the serial port, making sure we're in the PROGRAMMER
// rather than the bootloader, then sending a command and setting a new command state.
// TODO: When it fails, this needs to carry errors over somehow.
// newState is really just a ProgrammerCommandState but in order to keep
// ProgrammerCommandState private, I did it this way.
void Programmer::startProgrammerCommand(uint8_t commandByte, uint32_t newState)
{
nextState = (ProgrammerCommandState)newState;
nextSendByte = commandByte;
curState = BootloaderStateAwaitingOKReply;
openPort();
sendByte(GetBootloaderState);
}
// Begins a command by opening the serial port, making sure we're in the BOOTLOADER
// rather than the programmer, then sending a command and setting a new command state.
// TODO: When it fails, this needs to carry errors over somehow.
// newState is really just a ProgrammerCommandState but in order to keep
// ProgrammerCommandState private, I did it this way.
void Programmer::startBootloaderCommand(uint8_t commandByte, uint32_t newState)
{
nextState = (ProgrammerCommandState)newState;
nextSendByte = commandByte;
curState = BootloaderStateAwaitingOKReplyToBootloader;
openPort();
sendByte(GetBootloaderState);
}
#include <QTimer>
void Programmer::portDiscovered(const QextPortInfo &info)
{
if ((foundState == ProgrammerBoardNotFound) &&
(info.vendorID == PROGRAMMER_USB_VENDOR_ID) &&
(info.productID == PROGRAMMER_USB_DEVICE_ID) &&
(info.portName != ""))
{
// Note: I check that portName != "" because QextSerialEnumerator seems to give me
// 2 notifications that match the vendor ID -- one is the real deal, and the other
// has a blank port name. If I match on the blank port name one, it breaks.
#ifdef Q_WS_WIN
programmerBoardPortName = "\\\\.\\" + info.portName;
#else
programmerBoardPortName = info.portName;
#endif
foundState = ProgrammerBoardFound;
// I create a temporary timer here because opening it immediately seems to crash
// Mac OS X in my limited testing. Don't worry about a memory leak -- the
// portDiscovered_internal() slot will delete the newly-allocated QTimer.
QTimer *t = new QTimer();
connect(t, SIGNAL(timeout()), SLOT(portDiscovered_internal()));
t->setInterval(50);
t->setSingleShot(true);
t->start();
}
}
void Programmer::portDiscovered_internal()
{
// Delete the QTimer that sent us this signal. Ugly, but it works...
sender()->deleteLater();
closePort();
serialPort->setPortName(programmerBoardPortName);
// Don't show the "control" screen if we intentionally
// reconnected the USB port because we are changing from bootloader
// to programmer mode or vice-versa.
if (curState == BootloaderStateAwaitingPlug)
{
openPort();
curState = nextState;
sendByte(nextSendByte);
}
else if (curState == BootloaderStateAwaitingPlugToBootloader)
{
openPort();
curState = nextState;
sendByte(nextSendByte);
}
else
{
emit programmerBoardConnected();
}
}
void Programmer::portRemoved(const QextPortInfo &info)
{
if ((info.vendorID == PROGRAMMER_USB_VENDOR_ID) &&
(info.productID == PROGRAMMER_USB_DEVICE_ID) &&
(foundState == ProgrammerBoardFound))
{
programmerBoardPortName = "";
foundState = ProgrammerBoardNotFound;
// Don't show the "no programmer connected" screen if we intentionally
// disconnected the USB port because we are changing from bootloader
// to programmer mode or vice-versa.
if (curState == BootloaderStateAwaitingUnplug)
{
curState = BootloaderStateAwaitingPlug;
}
else if (curState == BootloaderStateAwaitingUnplugToBootloader)
{
curState = BootloaderStateAwaitingPlugToBootloader;
}
else
{
closePort();
if (curState != WaitingForNextCommand)
{
// This means they unplugged while we were in the middle
// of an operation. Reset state, and let them know.
curState = WaitingForNextCommand;
emit programmerBoardDisconnectedDuringOperation();
}
else
{
emit programmerBoardDisconnected();
}
}
}
}
void Programmer::startCheckingPorts()
{
QextSerialEnumerator *p = new QextSerialEnumerator();
connect(p, SIGNAL(deviceDiscovered(QextPortInfo)), SLOT(portDiscovered(QextPortInfo)));
connect(p, SIGNAL(deviceRemoved(QextPortInfo)), SLOT(portRemoved(QextPortInfo)));
p->setUpNotifications();
}
void Programmer::openPort()
{
serialPort->open(QextSerialPort::ReadWrite);
}
void Programmer::closePort()
{
serialPort->close();
}
void Programmer::setSIMMCapacity(uint32_t bytes)
{
_simmCapacity = bytes;
}
uint32_t Programmer::SIMMCapacity() const
{
return _simmCapacity;
}
void Programmer::setVerifyMode(VerificationOption mode)
{
_verifyMode = mode;
}
VerificationOption Programmer::verifyMode() const
{
return _verifyMode;
}
void Programmer::doVerifyAfterWriteCompare()
{
// Do the comparison, emit the correct signal
// Read the entire file we just wrote into a QByteArray
writeDevice->seek(readOffset);
QByteArray originalFileContents = writeDevice->read(verifyLength);
qDebug() << "Read" << originalFileContents.length() << "bytes, asked for" << verifyLength;
WriteStatus emitStatus;
// Now, compare the readback (but only for the length of originalFileContents
// (because the readback might be longer since it has to be a multiple of
// READ_CHUNK_SIZE)
if (originalFileContents.size() <= verifyArray->size())
{
const char *fileBytesPtr = originalFileContents.constData();
const char *readBytesPtr = verifyArray->constData();
if (memcmp(fileBytesPtr, readBytesPtr, originalFileContents.size()) != 0)
{
// Now let's do some trickery and figure out which chip is acting up (or chips)
_verifyBadChipMask = 0;
// Keep a list of which chips are reading bad data back
for (int x = 0; (x < originalFileContents.size()) && (_verifyBadChipMask != 0xF); x++)
{
if (fileBytesPtr[x] != readBytesPtr[x])
{
// OK, we found a mismatched byte. Now look at
// which byte (0-3) it is in each 4-byte group.
// If it's byte 0, it's the MOST significant byte
// because the 68k is big endian. IC4 contains the
// MSB, so IC4 is the first chip, IC3 second, and
// so on. That's why I subtract it from 3 --
// 0 through 3 get mapped to 3 through 0.
_verifyBadChipMask |= (1 << (3 - (x % 4)));
}
}
// Now make sure we're not complaining about chips we didn't
// write to...this will zero out errors on chips we weren't
// flashing, but will leave errors intact for chips we did write.
// (the chip mask is backwards from the IC numbering...that's why
// I have to do this in a special way)
if ((writeChipMask & 0x01) == 0) _verifyBadChipMask &= ~0x08;
if ((writeChipMask & 0x02) == 0) _verifyBadChipMask &= ~0x04;
if ((writeChipMask & 0x04) == 0) _verifyBadChipMask &= ~0x02;
if ((writeChipMask & 0x08) == 0) _verifyBadChipMask &= ~0x01;
if (_verifyBadChipMask != 0)
{
emitStatus = WriteVerificationFailure;
}
else
{
emitStatus = WriteCompleteVerifyOK;
}
}
else
{
emitStatus = WriteCompleteVerifyOK;
}
}
else
{
// Wrong amount of data read back for some reason...shouldn't ever happen,
// but I'll call it a verification failure.
emitStatus = WriteVerificationFailure;
}
// Reset verification buffer to emptiness
verifyArray->clear();
verifyBuffer->seek(0);
// Finally, emit the final status signal
emit writeStatusChanged(emitStatus);
}