mac-rom-simm-programmer/usb_serial/usb_serial.c

335 lines
10 KiB
C
Raw Normal View History

/*
* usb_serial.c
*
* Created on: Dec 9, 2011
* Author: Doug
*/
#include "usb_serial.h"
#include "../LUFA/Drivers/USB/USB.h"
#include "../cdc_device_definition.h"
#include "../external_mem.h"
2011-12-11 16:35:53 +00:00
#include "../tests/simm_electrical_test.h"
#include "../programmer_protocol.h"
2011-12-11 16:35:53 +00:00
2011-12-17 04:13:34 +00:00
#define CHIP_SIZE (512UL * 1024UL)
2011-12-11 16:35:53 +00:00
#define READ_CHUNK_SIZE_BYTES 1024UL
2011-12-17 04:13:34 +00:00
#define WRITE_CHUNK_SIZE_BYTES 1024UL
2011-12-11 16:35:53 +00:00
#if ((READ_CHUNK_SIZE_BYTES % 4) != 0)
#error Read chunk size should be a multiple of 4 bytes
#endif
2011-12-17 04:13:34 +00:00
#if ((WRITE_CHUNK_SIZE_BYTES % 4) != 0)
#error Write chunk size should be a multiple of 4 bytes
#endif
void USBSerial_Init(void)
{
USB_Init();
}
// Internal state so we know how to interpret the next-received byte
2011-12-11 16:35:53 +00:00
typedef enum ProgrammerCommandState
{
WaitingForCommand = 0,
ReadingByteWaitingForAddress,
ReadingChips,
ReadingChipsUnableSendError,
WritingChips
2011-12-11 16:35:53 +00:00
} ProgrammerCommandState;
static ProgrammerCommandState curCommandState = WaitingForCommand;
// State info for reading/writing
2011-12-11 16:35:53 +00:00
static uint8_t byteAddressReceiveCount = 0;
static uint16_t curReadIndex;
2011-12-17 04:13:34 +00:00
static int16_t writePosInChunk = -1;
static uint16_t curWriteIndex = 0;
2011-12-11 16:35:53 +00:00
// Private functions
2011-12-11 16:35:53 +00:00
void USBSerial_HandleWaitingForCommandByte(uint8_t byte);
2011-12-17 04:13:34 +00:00
void USBSerial_HandleReadingChipsByte(uint8_t byte);
2011-12-11 16:35:53 +00:00
void USBSerial_SendReadDataChunk(void);
2011-12-17 04:13:34 +00:00
void USBSerial_HandleWritingChipsByte(uint8_t byte);
void USBSerial_ElectricalTest_Fail_Handler(uint8_t index1, uint8_t index2);
2011-12-17 04:13:34 +00:00
// Read/write to USB serial macros -- easier than retyping
// CDC_Device_XXX(&VirtualSerial_CDC_Interface...) every time
2011-12-17 04:13:34 +00:00
#define SendByte(b) CDC_Device_SendByte(&VirtualSerial_CDC_Interface, b)
#define ReadByte() CDC_Device_ReceiveByte(&VirtualSerial_CDC_Interface)
#define SendData(d, l) CDC_Device_SendData(&VirtualSerial_CDC_Interface, d, l)
2011-12-11 16:35:53 +00:00
// Should be called periodically in the main loop
void USBSerial_Check(void)
{
// If we're configured, read a byte (if one is available) and process it
2011-12-17 04:13:34 +00:00
if (USB_DeviceState == DEVICE_STATE_Configured)
2011-12-11 16:35:53 +00:00
{
2011-12-17 04:13:34 +00:00
int16_t recvByte = ReadByte();
2011-12-11 16:35:53 +00:00
// Did we get a byte? If so, hand it off to the correct handler
// function based on the current state
2011-12-11 16:35:53 +00:00
if (recvByte >= 0)
{
switch (curCommandState)
{
case WaitingForCommand:
USBSerial_HandleWaitingForCommandByte((uint8_t)recvByte);
break;
2011-12-17 04:13:34 +00:00
case ReadingChips:
USBSerial_HandleReadingChipsByte((uint8_t)recvByte);
break;
case WritingChips:
USBSerial_HandleWritingChipsByte((uint8_t)recvByte);
break;
2011-12-11 16:35:53 +00:00
}
}
2011-12-17 04:13:34 +00:00
}
2011-12-11 16:35:53 +00:00
// And do the periodic CDC and USB tasks...
CDC_Device_USBTask(&VirtualSerial_CDC_Interface);
USB_USBTask();
}
// If we're in the "waiting for command" state, handle the command...
2011-12-11 16:35:53 +00:00
void USBSerial_HandleWaitingForCommandByte(uint8_t byte)
{
switch (byte)
{
// Asked to enter waiting mode -- we're already there, so say OK.
2011-12-11 16:35:53 +00:00
case EnterWaitingMode:
SendByte(CommandReplyOK);
2011-12-11 16:35:53 +00:00
curCommandState = WaitingForCommand;
break;
// Asked to do the electrical test. Reply OK, and then do the test,
// sending whatever replies necessary
2011-12-11 16:35:53 +00:00
case DoElectricalTest:
2011-12-17 04:13:34 +00:00
SendByte(CommandReplyOK);
SIMMElectricalTest_Run(USBSerial_ElectricalTest_Fail_Handler);
SendByte(ProgrammerElectricalTestDone);
2011-12-11 16:35:53 +00:00
curCommandState = WaitingForCommand;
break;
// Asked to identify the chips in the SIMM. Identify them and send reply.
2011-12-11 16:35:53 +00:00
case IdentifyChips:
{
struct ChipID chips[4];
2011-12-17 04:13:34 +00:00
SendByte(CommandReplyOK);
2011-12-11 16:35:53 +00:00
ExternalMem_IdentifyChips(chips);
int x;
for (x = 0; x < 4; x++)
{
SendByte(chips[x].manufacturerID);
SendByte(chips[x].deviceID);
}
SendByte(ProgrammerIdentifyDone);
2011-12-11 16:35:53 +00:00
break;
}
// Asked to read a single byte from each SIMM. Change the state and reply.
2011-12-11 16:35:53 +00:00
case ReadByte:
curCommandState = ReadingByteWaitingForAddress;
byteAddressReceiveCount = 0;
2011-12-17 04:13:34 +00:00
SendByte(CommandReplyOK);
2011-12-11 16:35:53 +00:00
break;
// Asked to read all four chips. Set the state, reply with the first chunk
2011-12-11 16:35:53 +00:00
case ReadChips:
curCommandState = ReadingChips;
curReadIndex = 0;
2011-12-17 04:13:34 +00:00
SendByte(CommandReplyOK);
2011-12-11 16:35:53 +00:00
USBSerial_SendReadDataChunk();
break;
// Erase the chips and reply OK. (TODO: Sometimes erase might fail)
2011-12-11 16:35:53 +00:00
case EraseChips:
2011-12-17 04:13:34 +00:00
ExternalMem_EraseChips(ALL_CHIPS);
SendByte(CommandReplyOK);
break;
// Begin writing the chips. Change the state, reply, wait for chunk of data
2011-12-11 16:35:53 +00:00
case WriteChips:
2011-12-17 04:13:34 +00:00
curCommandState = WritingChips;
curWriteIndex = 0;
writePosInChunk = -1;
SendByte(CommandReplyOK);
break;
// Asked for the current bootloader state. We are in the program right now,
// so reply accordingly.
case GetBootloaderState:
SendByte(CommandReplyOK);
SendByte(BootloaderStateInProgrammer);
break;
// Enter the bootloader. Wait a bit, then jump to the bootloader location.
case EnterBootloader:
SendByte(CommandReplyOK);
// Done with the USB interface -- the bootloader will re-initialize it.
USB_Disable();
// Disable interrupts so nothing weird happens...
cli();
// Wait a little bit to let everything settle and let the program
// close the port after the USB disconnect
_delay_ms(2000);
// And, of course, go into the bootloader.
__asm__ __volatile__ ( "jmp 0xE000" );
break;
// Enter the programmer. We're already there, so reply OK.
case EnterProgrammer:
// Already in the programmer
SendByte(CommandReplyOK);
break;
// We don't know what this command is, so reply that it was invalid.
2011-12-17 04:13:34 +00:00
default:
SendByte(CommandReplyInvalid);
break;
}
}
// If we're in the "reading chips" state, handle the incoming byte...
2011-12-17 04:13:34 +00:00
void USBSerial_HandleReadingChipsByte(uint8_t byte)
{
// The byte should be a reply from the computer. It should be either:
// 1) ComputerReadOK -- meaning it got the chunk we just sent
// or
// 2) ComputerReadCancel -- meaning the user canceled the read
2011-12-17 04:13:34 +00:00
switch (byte)
{
case ComputerReadOK:
// If they have confirmed the final data chunk, let them know
// that they have finished, and enter command state.
if (curReadIndex >= (CHIP_SIZE / (READ_CHUNK_SIZE_BYTES/4)))
2011-12-17 04:13:34 +00:00
{
SendByte(ProgrammerReadFinished);
curCommandState = WaitingForCommand;
}
else // There's more data left to read, so read it and send it to them!
2011-12-17 04:13:34 +00:00
{
SendByte(ProgrammerReadMoreData);
USBSerial_SendReadDataChunk();
}
break;
case ComputerReadCancel:
// If they've canceled, let them know we got their request and go back
// to "waiting for command" state
2011-12-17 04:13:34 +00:00
SendByte(ProgrammerReadConfirmCancel);
curCommandState = WaitingForCommand;
2011-12-11 16:35:53 +00:00
break;
}
}
// Read the next chunk of data from the SIMM and send it off over the serial.
2011-12-11 16:35:53 +00:00
void USBSerial_SendReadDataChunk(void)
{
// Here's a buffer we will use to read the next chunk of data.
// It's static because the stack is NOT big enough for it. If I start
// running low on RAM, I could pull this out of the function and share it
// with other functions, but I'm not bothering with that for now.
2011-12-11 16:35:53 +00:00
static union
{
uint32_t readChunks[READ_CHUNK_SIZE_BYTES / 4];
uint8_t readChunkBytes[READ_CHUNK_SIZE_BYTES];
} chunks;
// Read the next chunk of data, send it over USB, and make sure
// we sent it correctly.
ExternalMem_Read(curReadIndex * (READ_CHUNK_SIZE_BYTES/4),
chunks.readChunks, READ_CHUNK_SIZE_BYTES/4);
uint8_t retVal = SendData((const char *)chunks.readChunkBytes,
READ_CHUNK_SIZE_BYTES);
// If for some reason there was an error, mark it as such. Otherwise,
// increment our pointer so we know the next chunk of data to send.
2011-12-11 16:35:53 +00:00
if (retVal != ENDPOINT_RWSTREAM_NoError)
{
curCommandState = ReadingChipsUnableSendError;
}
else
{
curReadIndex++;
}
}
// Handles a received byte from the computer while we're in the "writing chips"
// mode.
2011-12-17 04:13:34 +00:00
void USBSerial_HandleWritingChipsByte(uint8_t byte)
{
// A buffer we use to store the incoming data. This, too, could be shared
// with other functions if I end up running out of RAM. Again, I'm not
// bothering with that yet, but this could easily be shared with the
// read function.
2011-12-17 04:13:34 +00:00
static union
{
uint32_t writeChunks[WRITE_CHUNK_SIZE_BYTES / 4];
uint8_t writeChunkBytes[WRITE_CHUNK_SIZE_BYTES];
} chunks;
// This means we have just started the entire process or just finished
// a chunk, so see what the computer has decided for us to do.
2011-12-17 04:13:34 +00:00
if (writePosInChunk == -1)
{
switch (byte)
{
// The computer asked to write more data to the SIMM.
2011-12-17 04:13:34 +00:00
case ComputerWriteMore:
writePosInChunk = 0;
// Make sure we don't write past the capacity of the chips.
if (curWriteIndex < CHIP_SIZE / (WRITE_CHUNK_SIZE_BYTES/4))
2011-12-17 04:13:34 +00:00
{
SendByte(ProgrammerWriteOK);
}
else
{
SendByte(ProgrammerWriteError);
// TODO: Enter waiting for command mode?
2011-12-17 04:13:34 +00:00
}
break;
// The computer said that it's done writing.
2011-12-17 04:13:34 +00:00
case ComputerWriteFinish:
SendByte(ProgrammerWriteOK);
curCommandState = WaitingForCommand;
break;
// The computer asked to cancel.
2011-12-17 04:13:34 +00:00
case ComputerWriteCancel:
SendByte(ProgrammerWriteConfirmCancel);
curCommandState = WaitingForCommand;
break;
}
}
else // Interpret the incoming byte as data to write to the SIMM.
2011-12-17 04:13:34 +00:00
{
// Save the byte, and check if we've filled up an entire chunk
2011-12-17 04:13:34 +00:00
chunks.writeChunkBytes[writePosInChunk++] = byte;
if (writePosInChunk >= WRITE_CHUNK_SIZE_BYTES)
{
// We filled up the chunk, write it out and confirm it, then wait
// for the next command from the computer!
ExternalMem_Write(curWriteIndex * (WRITE_CHUNK_SIZE_BYTES/4),
chunks.writeChunks, WRITE_CHUNK_SIZE_BYTES/4, ALL_CHIPS);
2011-12-17 04:13:34 +00:00
SendByte(ProgrammerWriteOK);
curWriteIndex++;
writePosInChunk = -1;
}
}
}
// Whenever an electrical test failure occurs, this handler will be called
// by it. It sends out a failure notice followed by indexes of the two
// shorted pins.
void USBSerial_ElectricalTest_Fail_Handler(uint8_t index1, uint8_t index2)
{
SendByte(ProgrammerElectricalTestFail);
SendByte(index1);
SendByte(index2);
}
// LUFA event handler for when the USB configuration changes.
void EVENT_USB_Device_ConfigurationChanged(void)
{
bool ConfigSuccess = true;
ConfigSuccess &= CDC_Device_ConfigureEndpoints(&VirtualSerial_CDC_Interface);
}
// LUFA event handler for when a USB control request is received
void EVENT_USB_Device_ControlRequest(void)
{
CDC_Device_ProcessControlRequest(&VirtualSerial_CDC_Interface);
}