mirror of
https://github.com/steve-chamberlin/mac-rom-simm-programmer.software.git
synced 2025-01-02 17:30:22 +00:00
04711c3851
Inserted GPL license header into source code.
1000 lines
30 KiB
C++
1000 lines
30 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,
|
|
|
|
WriteSIMMWaitingEraseReply,
|
|
WriteSIMMWaitingWriteReply,
|
|
WriteSIMMWaitingFinishReply,
|
|
WriteSIMMWaitingWriteMoreReply,
|
|
|
|
ElectricalTestWaitingStartReply,
|
|
ElectricalTestWaitingNextStatus,
|
|
ElectricalTestWaitingFirstFail,
|
|
ElectricalTestWaitingSecondFail,
|
|
|
|
ReadSIMMWaitingStartReply,
|
|
ReadSIMMWaitingLengthReply,
|
|
ReadSIMMWaitingData,
|
|
ReadSIMMWaitingStatusReply,
|
|
|
|
BootloaderStateAwaitingOKReply,
|
|
BootloaderStateAwaitingReply,
|
|
BootloaderStateAwaitingOKReplyToBootloader,
|
|
BootloaderStateAwaitingReplyToBootloader,
|
|
BootloaderStateAwaitingUnplug,
|
|
BootloaderStateAwaitingPlug,
|
|
BootloaderStateAwaitingUnplugToBootloader,
|
|
BootloaderStateAwaitingPlugToBootloader,
|
|
|
|
IdentificationAwaitingOKReply,
|
|
IdentificationWaitingData,
|
|
IdentificationAwaitingDoneReply,
|
|
|
|
BootloaderEraseProgramAwaitingStartOKReply,
|
|
BootloaderEraseProgramWaitingFinishReply,
|
|
BootloaderEraseProgramWaitingWriteMoreReply,
|
|
BootloaderEraseProgramWaitingWriteReply
|
|
} ProgrammerCommandState;
|
|
|
|
typedef enum ProgrammerBoardFoundState
|
|
{
|
|
ProgrammerBoardNotFound,
|
|
ProgrammerBoardFound
|
|
} ProgrammerBoardFoundState;
|
|
|
|
typedef enum ProgrammerCommand
|
|
{
|
|
EnterWaitingMode = 0,
|
|
DoElectricalTest,
|
|
IdentifyChips,
|
|
ReadByte,
|
|
ReadChips,
|
|
EraseChips,
|
|
WriteChips,
|
|
GetBootloaderState,
|
|
EnterBootloader,
|
|
EnterProgrammer,
|
|
BootloaderEraseAndWriteProgram
|
|
} 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
|
|
} 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;
|
|
|
|
#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
|
|
|
|
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)
|
|
{
|
|
serialPort = new QextSerialPort(QextSerialPort::EventDriven);
|
|
connect(serialPort, SIGNAL(readyRead()), SLOT(dataReady()));
|
|
}
|
|
|
|
Programmer::~Programmer()
|
|
{
|
|
closePort();
|
|
delete serialPort;
|
|
}
|
|
|
|
void Programmer::readSIMM(QIODevice *device)
|
|
{
|
|
//readFile = new QFile(filename);
|
|
//readFile->open(QFile::WriteOnly);
|
|
//lenRead = 0;
|
|
//lenRemaining = _simmCapacity;
|
|
|
|
readDevice = device;
|
|
lenRead = 0;
|
|
lenRemaining = _simmCapacity;
|
|
|
|
startProgrammerCommand(ReadChips, ReadSIMMWaitingStartReply);
|
|
}
|
|
|
|
void Programmer::writeToSIMM(QIODevice *device)
|
|
{
|
|
/*writeFile = new QFile(filename);
|
|
if (!writeFile->open(QFile::ReadOnly))
|
|
{
|
|
curState = WaitingForNextCommand;
|
|
emit writeStatusChanged(WriteError);
|
|
return;
|
|
}
|
|
if (writeFile->size() > _simmCapacity)
|
|
{
|
|
curState = WaitingForNextCommand;
|
|
emit writeStatusChanged(WriteFileTooBig);
|
|
return;
|
|
}
|
|
else
|
|
{
|
|
lenWritten = 0;
|
|
writeLenRemaining = writeFile->size();
|
|
|
|
startProgrammerCommand(EraseChips, WriteSIMMWaitingEraseReply);
|
|
}*/
|
|
|
|
writeDevice = device;
|
|
if (writeDevice->size() > _simmCapacity)
|
|
{
|
|
curState = WaitingForNextCommand;
|
|
emit writeStatusChanged(WriteFileTooBig);
|
|
return;
|
|
}
|
|
else
|
|
{
|
|
lenWritten = 0;
|
|
writeLenRemaining = writeDevice->size();
|
|
|
|
startProgrammerCommand(EraseChips, WriteSIMMWaitingEraseReply);
|
|
}
|
|
}
|
|
|
|
void Programmer::sendByte(uint8_t b)
|
|
{
|
|
serialPort->write((const char *)&b, 1);
|
|
}
|
|
|
|
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;
|
|
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 WriteSIMMWaitingWriteReply:
|
|
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;
|
|
case WriteSIMMWaitingWriteMoreReply:
|
|
{
|
|
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;
|
|
}
|
|
case WriteSIMMWaitingFinishReply:
|
|
switch (c)
|
|
{
|
|
case ProgrammerWriteOK:
|
|
curState = WaitingForNextCommand;
|
|
qDebug() << "Write success at end";
|
|
closePort();
|
|
emit writeStatusChanged(WriteComplete);
|
|
break;
|
|
case ProgrammerWriteError:
|
|
default:
|
|
qDebug() << "Write failure at end";
|
|
curState = WaitingForNextCommand;
|
|
closePort();
|
|
emit writeStatusChanged(WriteError);
|
|
break;
|
|
}
|
|
|
|
break;
|
|
|
|
// ELECTRICAL TEST STATE HANDLERS
|
|
|
|
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;
|
|
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;
|
|
case ElectricalTestWaitingFirstFail:
|
|
electricalTestFirstErrorLoc = c;
|
|
curState = ElectricalTestWaitingSecondFail;
|
|
break;
|
|
case ElectricalTestWaitingSecondFail:
|
|
emit electricalTestFailLocation(electricalTestFirstErrorLoc, c);
|
|
curState = ElectricalTestWaitingNextStatus;
|
|
break;
|
|
|
|
// READ SIMM STATE HANDLERS
|
|
case ReadSIMMWaitingStartReply:
|
|
switch (c)
|
|
{
|
|
case CommandReplyOK:
|
|
|
|
emit readStatusChanged(ReadStarting);
|
|
curState = ReadSIMMWaitingLengthReply;
|
|
|
|
// Send the length requesting to be read
|
|
sendByte((lenRemaining >> 0) & 0xFF);
|
|
sendByte((lenRemaining >> 8) & 0xFF);
|
|
sendByte((lenRemaining >> 16) & 0xFF);
|
|
sendByte((lenRemaining >> 24) & 0xFF);
|
|
|
|
// Now wait for the go-ahead from the programmer's side
|
|
break;
|
|
case CommandReplyError:
|
|
case CommandReplyInvalid:
|
|
default:
|
|
curState = WaitingForNextCommand;
|
|
emit readStatusChanged(ReadError);
|
|
break;
|
|
}
|
|
break;
|
|
|
|
case ReadSIMMWaitingLengthReply:
|
|
switch (c)
|
|
{
|
|
case ProgrammerReadOK:
|
|
curState = ReadSIMMWaitingData;
|
|
emit readTotalLengthChanged(_simmCapacity);
|
|
emit readCompletionLengthChanged(0);
|
|
readChunkLenRemaining = READ_CHUNK_SIZE;
|
|
break;
|
|
case ProgrammerReadError:
|
|
default:
|
|
curState = WaitingForNextCommand;
|
|
closePort();
|
|
emit readStatusChanged(ReadError);
|
|
break;
|
|
}
|
|
break;
|
|
case ReadSIMMWaitingData:
|
|
readDevice->write((const char *)&c, 1);
|
|
lenRead++;
|
|
if (--readChunkLenRemaining == 0)
|
|
{
|
|
emit readCompletionLengthChanged(lenRead);
|
|
qDebug() << "Received a chunk of data";
|
|
sendByte(ComputerReadOK);
|
|
curState = ReadSIMMWaitingStatusReply;
|
|
}
|
|
break;
|
|
case ReadSIMMWaitingStatusReply:
|
|
switch (c)
|
|
{
|
|
case ProgrammerReadFinished:
|
|
curState = WaitingForNextCommand;
|
|
closePort();
|
|
emit readStatusChanged(ReadComplete);
|
|
break;
|
|
case ProgrammerReadConfirmCancel:
|
|
curState = WaitingForNextCommand;
|
|
closePort();
|
|
emit readStatusChanged(ReadCancelled);
|
|
break;
|
|
case ProgrammerReadMoreData:
|
|
curState = ReadSIMMWaitingData;
|
|
readChunkLenRemaining = READ_CHUNK_SIZE;
|
|
break;
|
|
}
|
|
|
|
break;
|
|
|
|
// BOOTLOADER STATE HANDLERS
|
|
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;
|
|
case BootloaderStateAwaitingReply:
|
|
switch (c)
|
|
{
|
|
case BootloaderStateInBootloader:
|
|
// Oops! We're in the bootloader. Better change over to the programmer.
|
|
// TODO: Send "enter programmer" command.
|
|
// Close serial port.
|
|
// Wait for serial port to reappear (or just wait a fixed time?)
|
|
// Open serial port.
|
|
// Ensure we're in the programmer?
|
|
// Then do the command correctly
|
|
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);
|
|
// 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.
|
|
if (nextSendByte == EraseChips)
|
|
{
|
|
emit writeStatusChanged(WriteErasing);
|
|
}
|
|
break;
|
|
// TODO: Otherwise, raise an error?
|
|
}
|
|
break;
|
|
|
|
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;
|
|
case BootloaderStateAwaitingReplyToBootloader:
|
|
switch (c)
|
|
{
|
|
case BootloaderStateInProgrammer:
|
|
// Oops! We're in the programmer. Better change over to the bootloader.
|
|
// TODO: Send "enter bootloader" command.
|
|
// Close serial port.
|
|
// Wait for serial port to reappear (or just wait a fixed time?)
|
|
// Open serial port.
|
|
// Ensure we're in the bootloader?
|
|
// Then do the command correctly
|
|
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
|
|
case IdentificationAwaitingOKReply:
|
|
if (c == CommandReplyOK)
|
|
{
|
|
// Good to go, now waiting for identification data
|
|
emit identificationStatusChanged(IdentificationStarting);
|
|
curState = IdentificationWaitingData;
|
|
identificationCounter = 0;
|
|
}
|
|
else
|
|
{
|
|
emit identificationStatusChanged(IdentificationError);
|
|
curState = WaitingForNextCommand;
|
|
}
|
|
break;
|
|
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;
|
|
case IdentificationAwaitingDoneReply:
|
|
curState = WaitingForNextCommand;
|
|
closePort();
|
|
if (c == ProgrammerIdentifyDone)
|
|
{
|
|
emit identificationStatusChanged(IdentificationComplete);
|
|
}
|
|
else
|
|
{
|
|
emit identificationStatusChanged(IdentificationError);
|
|
}
|
|
break;
|
|
|
|
// WRITE BOOTLOADER PROGRAM STATE HANDLERS
|
|
case BootloaderEraseProgramAwaitingStartOKReply:
|
|
if (c == CommandReplyOK)
|
|
{
|
|
emit firmwareFlashStatusChanged(FirmwareFlashStarting);
|
|
sendByte(ComputerBootloaderWriteMore);
|
|
curState = BootloaderEraseProgramWaitingWriteMoreReply;
|
|
}
|
|
else
|
|
{
|
|
curState = WaitingForNextCommand;
|
|
closePort();
|
|
firmwareFile->close();
|
|
emit firmwareFlashStatusChanged(FirmwareFlashError);
|
|
}
|
|
break;
|
|
case BootloaderEraseProgramWaitingFinishReply:
|
|
if (c == BootloaderWriteOK)
|
|
{
|
|
curState = WaitingForNextCommand;
|
|
closePort();
|
|
firmwareFile->close();
|
|
emit firmwareFlashStatusChanged(FirmwareFlashComplete);
|
|
}
|
|
else
|
|
{
|
|
curState = WaitingForNextCommand;
|
|
closePort();
|
|
firmwareFile->close();
|
|
emit firmwareFlashStatusChanged(FirmwareFlashError);
|
|
}
|
|
break;
|
|
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;
|
|
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);
|
|
}
|
|
|
|
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);
|
|
// 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.
|
|
if (nextSendByte == EraseChips)
|
|
{
|
|
emit writeStatusChanged(WriteErasing);
|
|
}
|
|
}
|
|
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()
|
|
{
|
|
return _simmCapacity;
|
|
}
|