aboutsummaryrefslogtreecommitdiff
path: root/SrcShared/EmROMTransfer.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'SrcShared/EmROMTransfer.cpp')
-rw-r--r--SrcShared/EmROMTransfer.cpp770
1 files changed, 770 insertions, 0 deletions
diff --git a/SrcShared/EmROMTransfer.cpp b/SrcShared/EmROMTransfer.cpp
new file mode 100644
index 0000000..feb0c5a
--- /dev/null
+++ b/SrcShared/EmROMTransfer.cpp
@@ -0,0 +1,770 @@
+/* -*- mode: C++; tab-width: 4 -*- */
+/* ===================================================================== *\
+ Copyright (c) 1999-2001 Palm, Inc. or its subsidiaries.
+ All rights reserved.
+
+ This file is part of the Palm OS Emulator.
+
+ 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.
+\* ===================================================================== */
+
+#include "EmCommon.h"
+#include "EmROMTransfer.h"
+
+#include "EmDlg.h" // DoTransferROM
+#include "EmStreamFile.h" // EmStreamFile
+#include "EmTransport.h" // EmTransport
+#include "EmTransportSerial.h" // EmTransportSerial
+#include "ErrorHandling.h" // Errors::ThrowIfError
+#include "Platform.h" // Platform::GetMilliseconds
+#include "Strings.r.h" // kStr_Waiting
+
+
+/*
+ Notes on the XModem/YModem implementation used in this file:
+
+ Basic XModem is dead simple:
+
+ 1. Sender waits for NAK from receiver.
+ 2. Sender sends SOH, block#, complement of block#
+ 3. Sender sends 128 bytes of data.
+ 3. Sender sends one-byte sum of those 128 bytes as checksum.
+ 4. Receiver sends ACK or NAK.
+ 5. If NAK, go back to step 2.
+ 6. If ACK, increment block# and go back to step 2.
+ 7. At end of file, sender sends EOT.
+ 8. Receiver acknowledges EOT with ACK.
+
+ There is an XModem variant called Ymodem that sends an additional block at
+ the start of the protocol - this block contains the file name and size,
+ among other things.
+*/
+
+
+const int kXModemBlockSize = 1024; // 1k-XModem variant
+const int kXModemBufferSize = 1029; // 1k-XModem variant
+const char kXModemSoh = 1; // start of block header
+const char kXModemEof = 4; // end of file signal
+const char kXModemAck = 6; // acknowledge
+const char kXModemNak = 21; // negative acknowledge (resend packet)
+const char kXModemCan = 24; // cancel
+const char kXModemNakCrc = 'C'; // used instead of NAK for initial block
+
+
+enum
+{
+ kWaitingForTransport, // Waiting for transprt to come online
+ kOpen // Transport ready, first NakCrc sent
+};
+
+
+/***********************************************************************
+ *
+ * FUNCTION: EmROMTransfer::ROMTransfer
+ *
+ * DESCRIPTION: Handle the entire process of downloading a ROM, from
+ * asking them for port/baud, to showing the progress
+ * dialog, to downloading the ROM, to saving it, and to
+ * handling the Cancel button.
+ *
+ * PARAMETERS: None
+ *
+ * RETURNED: Nothing
+ *
+ ***********************************************************************/
+
+void EmROMTransfer::ROMTransfer (void)
+{
+ // Run the dialog that sets up the download process.
+
+ EmTransport::Config* cfg;
+ if (EmDlg::DoROMTransferQuery (cfg) == kDlgItemCancel)
+ return;
+
+ EmAssert (cfg != NULL);
+
+ EmTransport* oldTransport = NULL;
+ EmTransport* transport = NULL;
+
+ try
+ {
+ // Close whatever might be on the selected port already.
+
+ oldTransport = cfg->GetTransport ();
+ if (oldTransport)
+ Errors::ThrowIfError (oldTransport->Close ());
+
+ // Open the port we want to use for downloading the ROM.
+
+ transport = cfg->NewTransport ();
+ Errors::ThrowIfError (transport->Open());
+
+ // Create the ROM transfer object with this transport.
+
+ EmROMTransfer transferer (transport);
+
+ // Run the progress window.
+
+ EmDlgItemID result = EmDlg::DoROMTransferProgress (transferer);
+
+ if (result != kDlgItemCancel && transferer.HaveEntireROM ())
+ {
+ // Ask what name to save the ROM image to.
+
+ EmFileRef ref;
+ EmFileTypeList typeList (1, kFileTypeROM);
+ if (EmDlg::DoPutFile (ref, "", "", typeList, "") == kDlgItemOK)
+ {
+ // Save the ROM image.
+
+ EmStreamFile stream (ref, kCreateOrEraseForUpdate,
+ kFileCreatorEmulator, kFileTypeROM);
+
+ stream.PutBytes (transferer.Data (), transferer.Size ());
+ }
+ }
+
+ ResetSerialPort (oldTransport, transport);
+ }
+ catch (ErrCode errCode)
+ {
+ ResetSerialPort (oldTransport, transport);
+ Errors::ReportIfError (kStr_CmdTransferROM, errCode, 0, false);
+ }
+}
+
+
+/***********************************************************************
+ *
+ * FUNCTION: EmROMTransfer c'tor
+ *
+ * DESCRIPTION: Create the object. Initialize all data members.
+ *
+ * PARAMETERS: transport - transport object for low-level communications.
+ * We do not own it; the client deletes it.
+ *
+ * RETURNED: Nothing
+ *
+ ***********************************************************************/
+
+EmROMTransfer::EmROMTransfer (EmTransport* transport) :
+ fState (kWaitingForTransport),
+ fTransport (transport),
+ fROMSize (0),
+ fROMRead (0),
+ fROMBuffer (),
+ fHaveFirstBlock (false),
+ fHaveLastBlock (false),
+ fLastValidBlock ((uint8) -1),
+ fProgressCaption (-1),
+ fProgressValue (-1),
+ fProgressMax (-1),
+ fProgressLastUpdate (0),
+// fTempBuffer (),
+ fTempBufferOffset (0)
+{
+}
+
+
+/***********************************************************************
+ *
+ * FUNCTION: EmROMTransfer d'tor
+ *
+ * DESCRIPTION: Destroy the object. Delete all data members.
+ *
+ * PARAMETERS: None
+ *
+ * RETURNED: Nothing
+ *
+ ***********************************************************************/
+
+EmROMTransfer::~EmROMTransfer (void)
+{
+}
+
+
+/***********************************************************************
+ *
+ * FUNCTION: EmROMTransfer::Continue
+ *
+ * DESCRIPTION: Continually called to incrementally download a ROM.
+ *
+ * PARAMETERS: dlg - reference to the progress dialog
+ *
+ * RETURNED: True to continue downloading, false if done.
+ *
+ ***********************************************************************/
+
+Bool EmROMTransfer::Continue (EmDlgRef dlg)
+{
+ switch (fState)
+ {
+ case kWaitingForTransport:
+ return this->WaitForTransport (dlg);
+
+ case kOpen:
+ return this->ReadSomeData (dlg);
+ }
+
+ return false; // Don't continue
+}
+
+
+/***********************************************************************
+ *
+ * FUNCTION: EmROMTransfer::Abort
+ *
+ * DESCRIPTION: Abort the download. Called when the user clicks on
+ * the Cancel button.
+ *
+ * PARAMETERS: dlg - reference to the progress dialog
+ *
+ * RETURNED: Nothing
+ *
+ ***********************************************************************/
+
+void EmROMTransfer::Abort (EmDlgRef)
+{
+ // Nothing to do. All memory is reclaimed in the destructor, and
+ // the transport is closed and called by EmROMTransfer::ROMTransfer.
+}
+
+
+/***********************************************************************
+ *
+ * FUNCTION: EmROMTransfer::Size
+ *
+ * DESCRIPTION: Return the size of the ROM. Valid only if the ROM has
+ * been successfully downloaded (that is, Continue had
+ * been called until it finally returned false).
+ *
+ * PARAMETERS: None
+ *
+ * RETURNED: Size of the ROM in bytes.
+ *
+ ***********************************************************************/
+
+long EmROMTransfer::Size (void)
+{
+ return fROMSize;
+}
+
+
+/***********************************************************************
+ *
+ * FUNCTION: EmROMTransfer::Data
+ *
+ * DESCRIPTION: Return a pointer to the ROM. Valid only if the ROM has
+ * been successfully downloaded (that is, Continue had
+ * been called until it finally returned false).
+ *
+ * PARAMETERS: None
+ *
+ * RETURNED: Pointer to the ROM image.
+ *
+ ***********************************************************************/
+
+void* EmROMTransfer::Data (void)
+{
+ return fROMBuffer.Get();
+}
+
+
+/***********************************************************************
+ *
+ * FUNCTION: EmROMTransfer::WaitForTransport
+ *
+ * DESCRIPTION: Called while waiting for the transport object to
+ * indicate that the transport is ready for sending
+ * data. Until it is, we idle the progress indicator.
+ * When the transport is ready, prepare our state
+ * variables for downloading the ROM.
+ *
+ * PARAMETERS: dlg - reference to the progress dialog
+ *
+ * RETURNED: True to indicate that the Continue function should
+ * still be called.
+ *
+ ***********************************************************************/
+
+Bool EmROMTransfer::WaitForTransport (EmDlgRef dlg)
+{
+ // Update the progress indicator.
+
+ this->UpdateProgress (dlg, kStr_Waiting, 0, 0);
+
+ if (fTransport->CanRead ())
+ {
+ // Start the transfer by sending kXModemNakCrc.
+
+ this->SendByte (kXModemNakCrc);
+
+ // Initialize our download state
+
+ fROMSize = 0;
+ fROMRead = 0;
+ fHaveFirstBlock = false;
+ fHaveLastBlock = false;
+ fLastValidBlock = (uint8) -1;
+ fTimeoutBase = Platform::GetMilliseconds ();
+ fTempBufferOffset = 0;
+
+ // Switch over to the engine state
+
+ fState = kOpen;
+ }
+
+ return true;
+}
+
+
+/***********************************************************************
+ *
+ * FUNCTION: EmROMTransfer::ReadSomeData
+ *
+ * DESCRIPTION: Called to incrementally download a ROM. Reads data
+ * until we have enough for an XModem packet. Examines
+ * the packet. If valid, adds it to our full ROM image and
+ * acks the packet. If invalid, requests a resend.
+ *
+ * PARAMETERS: dlg - reference to the progress dialog
+ *
+ * RETURNED: True to indicate that the Continue function should
+ * still be called. False if the entire ROM has now
+ * been downloaded.
+ *
+ ***********************************************************************/
+
+Bool EmROMTransfer::ReadSomeData (EmDlgRef dlg)
+{
+ this->BufferPendingData ();
+
+ uint8 ackChar;
+
+ // If we have an entire new valid block by now, process it.
+
+ if (this->HaveValidBlock ())
+ {
+ ackChar = this->HandleNewBlock ();
+ }
+
+ // EOF signal.
+
+ else if (fTempBufferOffset > 0 && fTempBuffer[0] == kXModemEof)
+ {
+ ackChar = kXModemAck;
+ fHaveLastBlock = true;
+ }
+
+ // Check for timeouts.
+
+ else
+ {
+ // Returns:
+ //
+ // kXModemNak If timeout in middle of transfer
+ // kXModemNakCrc If timeout and haven't started transfer, yet
+ // 0 If no timeout
+
+ ackChar = this->CheckForTimeouts ();
+ }
+
+ // Send the ack char and reset our timeout counter.
+
+ if (ackChar)
+ {
+ this->SendByte (ackChar);
+ }
+
+ // Update the progress indicator.
+
+ if (fHaveFirstBlock)
+ {
+ this->UpdateProgress (dlg, kStr_Transferring, fROMRead, fROMSize);
+ }
+
+ return !this->HaveEntireROM ();
+}
+
+
+/***********************************************************************
+ *
+ * FUNCTION: EmROMTransfer::HandleNewBlock
+ *
+ * DESCRIPTION: A new ROM block has been downloaded and verified as
+ * valid. Add it to our incrementally built ROM image
+ * and prepare for the next block.
+ *
+ * PARAMETERS: None
+ *
+ * RETURNED: The character with which to acknowledge the packet.
+ *
+ ***********************************************************************/
+
+uint8 EmROMTransfer::HandleNewBlock (void)
+{
+ // If this is the first block, skip past the "file name" and
+ // get the file (ROM) size. Allocate a buffer to hold the image.
+
+ uint8 receivedBlock = fTempBuffer[1];
+
+ EmAssert (
+ !fHaveFirstBlock ||
+ fLastValidBlock == receivedBlock ||
+ fLastValidBlock == (uint8) (receivedBlock - 1));
+
+ if (!fHaveFirstBlock)
+ {
+ char* p = (char*) &fTempBuffer[3];
+ p += strlen (p) + 1;
+ fROMSize = atoi (p);
+ fROMBuffer.Adopt ((char*) Platform::AllocateMemory (fROMSize));
+
+ fHaveFirstBlock = true;
+ }
+
+ // Got a good block of data. Copy it into the ROM image.
+
+ else if (fLastValidBlock != receivedBlock)
+ {
+ memcpy (fROMBuffer.Get () + fROMRead, &fTempBuffer[3], kXModemBlockSize);
+ fROMRead += kXModemBlockSize;
+
+#ifndef NDEBUG
+ int blocksRead = fROMRead / kXModemBlockSize;
+ EmAssert ((blocksRead % 256) == receivedBlock);
+ EmAssert (fROMRead <= fROMSize);
+#endif
+ }
+
+ fLastValidBlock = receivedBlock;
+
+ // Prepare the read buffer for the next block of data.
+
+ fTempBufferOffset = 0;
+
+ // Acknowledge this packet as good.
+
+ return kXModemAck;
+}
+
+
+/***********************************************************************
+ *
+ * FUNCTION: EmROMTransfer::CheckForTimeouts
+ *
+ * DESCRIPTION: Check to see if a certain amount of time has elapsed
+ * since we received a valid packet. If it has, indicate
+ * that the sender resend the previous packet.
+ *
+ * PARAMETERS: ackChar - the current ackChar the caller is considering
+ * sending back to the client.
+ *
+ * RETURNED: The ackChar to *really* send back to the client. If
+ * the timeout hasn't occurred, just send back what the
+ * caller sent us. If it has timed out, return an ackChar
+ * based on whether we're in the middle of a download or
+ * just starting up.
+ *
+ ***********************************************************************/
+
+uint8 EmROMTransfer::CheckForTimeouts (void)
+{
+ if (Platform::GetMilliseconds () - fTimeoutBase > kTimeout)
+ {
+ // We haven't received a good packet in some time. Probably a dropped
+ // character, or we're just starting the protocol. If just starting, nak
+ // with kXModemNakCrc 'C', else just use regular nak '\025'.
+
+ fTempBufferOffset = 0;
+ return fROMRead > 0 ? kXModemNak : kXModemNakCrc;
+ }
+
+ return 0; // No timeout
+}
+
+
+/***********************************************************************
+ *
+ * FUNCTION: EmROMTransfer::BufferPendingData
+ *
+ * DESCRIPTION: Transfer any data in the transport's buffer into our
+ * own private little buffer used to hold a single packet.
+ *
+ * PARAMETERS: None
+ *
+ * RETURNED: Nothing
+ *
+ ***********************************************************************/
+
+void EmROMTransfer::BufferPendingData (void)
+{
+ // Get some data. Read as much as we can, but don't overflow
+ // our local buffer.
+
+ long bytesInPort = fTransport->BytesInBuffer (kXModemBufferSize);
+
+ if (bytesInPort > 0)
+ {
+ long bytesToRead = min (bytesInPort, kBufferSize - fTempBufferOffset);
+ ErrCode err = fTransport->Read (bytesToRead, &fTempBuffer[fTempBufferOffset]);
+ Errors::ThrowIfError (err);
+ fTempBufferOffset += bytesToRead;
+ }
+}
+
+
+/***********************************************************************
+ *
+ * FUNCTION: EmROMTransfer::HaveValidBlock
+ *
+ * DESCRIPTION: Check to see if our little packet buffer now contains
+ * a full and valid packet
+ *
+ * PARAMETERS: None
+ *
+ * RETURNED: True if so.
+ *
+ ***********************************************************************/
+
+Bool EmROMTransfer::HaveValidBlock (void)
+{
+ // Check to see whether we have enough data for a full block, and
+ // that the block is valid, and it's the block number we're expecting
+
+ Bool haveEnoughData = fTempBufferOffset >= kXModemBufferSize;
+ Bool checksumValid = haveEnoughData && this->ValidXModemBlock (fTempBuffer);
+
+ return haveEnoughData && checksumValid;
+}
+
+
+/***********************************************************************
+ *
+ * FUNCTION: EmROMTransfer::HaveEntireROM
+ *
+ * DESCRIPTION: Return whether or now we have downloaded the entire ROM.
+ *
+ * PARAMETERS: None
+ *
+ * RETURNED: True if so.
+ *
+ ***********************************************************************/
+
+Bool EmROMTransfer::HaveEntireROM (void)
+{
+ return fHaveLastBlock;
+}
+
+
+/***********************************************************************
+ *
+ * FUNCTION: EmROMTransfer::ValidXModemBlock
+ *
+ * DESCRIPTION: Validate a packet by doing a checksum and comparing it
+ * to the checksum that came with the packet.
+ *
+ * PARAMETERS: block - the packet to checksum
+ *
+ * RETURNED: True if valid.
+ *
+ ***********************************************************************/
+
+Bool EmROMTransfer::ValidXModemBlock (const uint8* block)
+{
+ /*
+ * XModem-1k block layout is as follows:
+ *
+ * +--------------+
+ * | SOH = '\002' |
+ * +--------------+
+ * | Block number |
+ * +--------------+
+ * | Block compl. | Complement of block number
+ * +--------------+
+ * | 1024 bytes |
+ * | of data |
+ * . . .
+ * . . .
+ * +--------------+
+ * | CRC hi byte | CRC-16 of preceding 1024 bytes of data,
+ * +--------------+ plus two zero bytes
+ * | CRC lo byte |
+ * +--------------+
+ */
+
+ if (block[0] != kXModemSoh)
+ {
+ return false;
+ }
+
+ if ((block[1] ^ block[2]) != 0xFF)
+ {
+ return false;
+ }
+
+ uint16 calculatedCRC = Crc16CalcBlock ((void*) &block[3], kXModemBlockSize, 0);
+
+ uint8 zeros[2] = {0, 0};
+ calculatedCRC = Crc16CalcBlock (zeros, 2, calculatedCRC);
+
+ uint16 xmittedCRC = (((uint16) (block[3 + kXModemBlockSize])) << 8) |
+ block[3 + kXModemBlockSize + 1];
+
+ return xmittedCRC == calculatedCRC;
+}
+
+
+/***********************************************************************
+ *
+ * FUNCTION: EmROMTransfer::SendByte
+ *
+ * DESCRIPTION: Send a single byte to the client.
+ *
+ * PARAMETERS: byte - the byte to send.
+ *
+ * RETURNED: Nothing
+ *
+ ***********************************************************************/
+
+#define CORRUPT_SENDS 0
+
+#if CORRUPT_SENDS
+static UInt32 PrvRange (UInt32 maxValue)
+{
+ static int initialized;
+ if (!initialized)
+ {
+ initialized = true;
+ srand (1);
+ }
+
+ return (rand () * maxValue) / (1UL + RAND_MAX);
+}
+#endif
+
+
+void EmROMTransfer::SendByte (uint8 byte)
+{
+#if CORRUPT_SENDS
+ // Drop a character.
+ if (::PrvRange (100) < 5)
+ {
+ LogAppendMsg ("CORRUPTOR: dropping a character");
+ fTimeoutBase = Platform::GetMilliseconds ();
+ return;
+ }
+
+ // Corrupt a character.
+ if (::PrvRange (100) < 5)
+ {
+ LogAppendMsg ("CORRUPTOR: changing a character");
+ byte++;
+ }
+#endif
+
+ long len = 1;
+ /*ErrCode err =*/ fTransport->Write (len, &byte);
+
+ // Any errors will just cause the retry/timeout mechanisms to kick in.
+// Errors::ThrowIfError (err);
+
+ fTimeoutBase = Platform::GetMilliseconds ();
+}
+
+
+/***********************************************************************
+ *
+ * FUNCTION: EmROMTransfer::UpdateProgress
+ *
+ * DESCRIPTION: Update the progress bar according to whether or not we
+ * have started the download process and, if so, how far
+ * along we are. The progress information is updated only
+ * incrementally and only when it's been changed.
+ *
+ * PARAMETERS: dlg - reference to progress dialog.
+ * caption - StrID of string for caption.
+ * value - number indicating how much of the ROM has been
+ * downloaded.
+ * max - number indicating how large the ROM image is.
+ *
+ * RETURNED: Nothing
+ *
+ ***********************************************************************/
+
+void EmROMTransfer::UpdateProgress (EmDlgRef dlg, long caption, long value, long maxValue)
+{
+ if (fProgressCaption != caption)
+ {
+ fProgressCaption = caption;
+ EmDlg::SetItemText (dlg, kDlgItemDlpMessage, caption);
+ }
+
+ uint32 now = Platform::GetMilliseconds ();
+ uint32 delta = now - fProgressLastUpdate;
+ bool timeout = delta > kProgressTimeout;
+
+ if (timeout)
+ {
+ // Divide values by 1024, since the Windows control deals only
+ // with 16-bit values. (This is fixed in later versions of
+ // the progress control, but those aren't shipped with stock
+ // Windows installations, yet.)
+
+ if (fProgressMax != maxValue)
+ {
+ fProgressMax = maxValue;
+ EmDlg::SetItemMax (dlg, kDlgItemDlpProgress, max (1L, maxValue / 1024));
+ }
+
+ if (fProgressValue != value)
+ {
+ fProgressValue = value;
+ EmDlg::SetItemValue (dlg, kDlgItemDlpProgress, value / 1024);
+ }
+
+ fProgressLastUpdate = now;
+ }
+}
+
+
+/***********************************************************************
+ *
+ * FUNCTION: EmROMTransfer::ResetSerialPort
+ *
+ * DESCRIPTION: We're done downloading the ROM (either successfully, or
+ * after an error or after the user Cancels). Close up
+ * the transport object we were using and, if needed,
+ * restore the old transport object.
+ *
+ * PARAMETERS: oldTransport - the transport object that was using the
+ * connection medium before we came along and usurped
+ * it. Usually a serial port.
+ *
+ * curTransport - the transport object used to download
+ * the ROM and that now needs to be closed.
+ *
+ * RETURNED: Nothing
+ *
+ ***********************************************************************/
+
+void EmROMTransfer::ResetSerialPort (EmTransport* oldTransport, EmTransport* curTransport)
+{
+ // Close our stream.
+
+ if (curTransport)
+ {
+ curTransport->Close ();
+ delete curTransport;
+ }
+
+ // Reopen the stream used before we got in the way.
+
+ if (oldTransport)
+ {
+ oldTransport->Open ();
+ }
+}