/* * The contents of this file are subject to the Mozilla Public License * Version 1.0 (the "License"); you may not use this file except in * compliance with the License. You may obtain a copy of the License at * http://www.mozilla.org/MPL/ * * Software distributed under the License is distributed on an "AS IS" * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the * License for the specific language governing rights and limitations * under the License. * * The Initial Developer of this code is David Baum. * Portions created by David Baum are Copyright (C) 1998 David Baum. * All Rights Reserved. * * Portions created by John Hansen are Copyright (C) 2005 John Hansen. * All Rights Reserved. * */ #include #include #include #include #include "RCX_Link.h" #include "RCX_Cmd.h" #include "rcxnub.h" #include "rcxnub_odd.h" #include "RCX_PipeTransport.h" #include "RCX_SerialPipe.h" #include "PDebug.h" #include "RCX_Image.h" #include "RCX_SpyboticsLinker.h" #ifdef GHOST #include "RCX_GhostTransport.h" #endif using std::printf; using std::getenv; using std::tolower; #define kSerialPortEnv "RCX_PORT" #define kFragmentChunk 20 #define kSpyboticsSmallChunk 2 #define kSpyboticsChunk 16 #define kFirmwareChunk 200 #define kDownloadWaitTime 300 #define kNubStart 0x8000 extern bool gQuiet; static int Checksum(const UByte *data, int length); static bool gUSB = false; RCX_Link::RCX_Link() { fTransport = 0; fOmitHeader = false; fRCXProgramChunkSize = kFragmentChunk; fRCXFirmwareChunkSize = kFirmwareChunk; fDownloadWaitTime = kDownloadWaitTime; fVerbose = false; } RCX_Link::~RCX_Link() { Close(); } RCX_Result RCX_Link::Open(RCX_TargetType target, const char *portName, ULong options) { fVerbose = (options & kVerboseMode); fTarget = target; // see if an environment variable is set, otherwise use default serial device if (!portName) portName = getenv(kSerialPortEnv); const char *devName; if (portName && ((devName=CheckPrefix(portName, "usb")) != 0)) { // USB Tower gUSB = true; #ifdef GHOST fTransport = new RCX_GhostTransport(); #else RCX_Pipe *pipe = RCX_NewUSBTowerPipe(); if (!pipe) return kRCX_USBUnsupportedError; fTransport = new RCX_PipeTransport(pipe); #endif } else { // Serial Tower gUSB = false; if (portName) { // strip off "serial:" prefix if present devName = CheckPrefix(portName, "serial"); if (!devName) devName = portName; } else { devName = PSerial::GetDefaultName(); } fTransport = new RCX_PipeTransport(new RCX_SerialPipe()); } fTransport->SetOmitHeader(fOmitHeader); RCX_Result result; result = fTransport->Open(target, devName, options); PREQUIRENOT(result, Fail_Open); if (fTarget == kRCX_SpyboticsTarget) { // turn off pinging RCX_Cmd cmd; result = Send(cmd.MakeSet(RCX_VALUE(kRCX_SpybotPingCtrlType, 1), RCX_VALUE(2, 0))); if (RCX_ERROR(result)) return result; } fSynced = false; fResult = kRCX_OK; return kRCX_OK; Fail_Open: delete fTransport; fTransport = 0; return result; } void RCX_Link::Close() { if (fTransport) { fTransport->Close(); delete fTransport; fTransport = 0; } } RCX_Result RCX_Link::Sync() { RCX_Cmd cmd; RCX_Result result; if (fSynced) return kRCX_OK; // always start with a ping result = Send(cmd.MakePing()); // if error, try a second time if (result == kRCX_ReplyError) result = Send(&cmd); if (RCX_ERROR(result)) return result; // cybermaster requires an unlock also if (fTarget == kRCX_CMTarget) { result = Send(cmd.MakeUnlockCM()); if (RCX_ERROR(result)) return result; } else if (fTarget == kRCX_ScoutTarget ) { result = Send(cmd.MakeBoot()); if (RCX_ERROR(result)) return result; result = Send(cmd.Set(0x47, 0x80)); if (RCX_ERROR(result)) return result; } fSynced = true; return kRCX_OK; } bool RCX_Link::WasErrorFromMissingFirmware() { // if not RCX, RCX2, or Swan then firmware isn't required if ((fTarget != kRCX_RCXTarget) && (fTarget != kRCX_RCX2Target) && (fTarget != kRCX_SwanTarget)) return false; // if not synced, then firmware wasn't a problem if (!fSynced) return false; // use Unlock command to check ROM/firmware versions RCX_Result result; RCX_Cmd cmd; result = Send(cmd.MakeUnlock()); // check the reply if (result != 8) return false; for(int i=4; i<8; ++i) { if (GetReplyByte(i) != 0) return false; } return true; } RCX_Result RCX_Link::Download(const RCX_Image &image, int programNumber) { RCX_Result result; RCX_Cmd cmd; // sync with RCX result = Sync(); if (RCX_ERROR(result)) return result; // stop any running tasks result = Send(cmd.Set(kRCX_StopAllOp)); if (RCX_ERROR(result)) return result; if (fTarget == kRCX_SpyboticsTarget) { result = DownloadSpybotics(image); } else { result = DownloadByChunk(image, programNumber); } if (RCX_ERROR(result)) return result; if (!gQuiet) // play sound when done Send(cmd.MakePlaySound(5)); return kRCX_OK; } RCX_Result RCX_Link::DownloadSpybotics(const RCX_Image &image) { RCX_SpyboticsLinker linker; vector output; RCX_Result result; RCX_Cmd cmd; linker.Generate(image, output); int length = output.size(); const UByte* data = &output[0]; int check = Checksum(data, length); int start = 0x100; result = Send(cmd.Set(kRCX_BeginFirmwareOp, (UByte)(start), (UByte)(start>>8), (UByte)check, (UByte)(check>>8), 0)); if (RCX_ERROR(result)) return result; BeginProgress(length); result = Download(data, length, gUSB ? kSpyboticsSmallChunk : kSpyboticsChunk); if (RCX_ERROR(result)) return result; return kRCX_OK; } bool gProgramMode = false; class ProgramMode { public: ProgramMode() { gProgramMode = true; } ~ProgramMode() { gProgramMode = false; } }; RCX_Result RCX_Link::DownloadByChunk(const RCX_Image &image, int programNumber) { ProgramMode x = ProgramMode(); RCX_Result result; RCX_Cmd cmd; int i; // select program if (programNumber) { result = Send(cmd.Set(kRCX_SelectProgramOp, (UByte)(programNumber-1))); if (result < 0) return result; } // clear existing tasks and/or subs result = Send(cmd.MakeDeleteTasks()); if (RCX_ERROR(result)) return result; result = Send(cmd.MakeDeleteSubs()); if (RCX_ERROR(result)) return result; int total = image.GetSize(); for(i=0; i 0) BeginProgress(total); result = Download(data, length, fRCXProgramChunkSize); return result; } RCX_Result RCX_Link::GetVersion(ULong &rom, ULong &ram) { RCX_Cmd cmd; RCX_Result result; UByte reply[8]; result = Sync(); if (RCX_ERROR(result)) return result; result = Send(cmd.MakeUnlock()); if (RCX_ERROR(result)) return result; if (result != 8) return kRCX_ReplyError; GetReply(reply,8); rom = ((ULong)reply[0] << 24) | ((ULong)reply[1] << 16) | ((ULong)reply[2] << 8) | ((ULong)reply[3]); ram = ((ULong)reply[4] << 24) | ((ULong)reply[5] << 16) | ((ULong)reply[6] << 8) | ((ULong)reply[7]); return kRCX_OK; } RCX_Result RCX_Link::GetValue(RCX_Value value) { RCX_Cmd cmd; RCX_Result result; result = Sync(); if (RCX_ERROR(result)) return result; result = Send(cmd.MakeRead(value)); if (RCX_ERROR(result)) return result; if (result != 2) return kRCX_ReplyError; result = (int)GetReplyByte(0) + ((int)GetReplyByte(1) << 8); return result; } RCX_Result RCX_Link::GetBatteryLevel() { RCX_Cmd cmd; RCX_Result result; result = Sync(); if (RCX_ERROR(result)) return result; if (fTarget == kRCX_ScoutTarget) { result = Send(cmd.Set(kRCX_PollMemoryOp, 0x3a, 0x01, 0x01)); if (result != 1) return kRCX_ReplyError; result = (int)GetReplyByte(0) * 109; } else { result = Send(cmd.Set(kRCX_BatteryLevelOp)); if (result != 2) return kRCX_ReplyError; result = (int)GetReplyByte(0) + ((int)GetReplyByte(1) << 8); } return result; } RCX_Result RCX_Link::DownloadFirmware(const UByte *data, int length, int start, bool fast) { RCX_Result result; if (fast) { // check for fast mode support if (!fTransport->FastModeSupported()) return kRCX_PipeModeError; // send the nub first if (fTransport->FastModeOddParity()) { result = TransferFirmware(rcxnub_odd, sizeof(rcxnub_odd), kNubStart, false); } else { result = TransferFirmware(rcxnub, sizeof(rcxnub), kNubStart, false); } if (RCX_ERROR(result)) return result; // switch to fast mode fTransport->SetFastMode(true); // download result = TransferFirmware(data, length, start, true); fTransport->SetFastMode(false); } else { result = TransferFirmware(data, length, start, true); } return result; } RCX_Result RCX_Link::TransferFirmware(const UByte *data, int length, int start, bool progress) { RCX_Cmd cmd; RCX_Result result; result = Sync(); if (RCX_ERROR(result)) return result; result = Send(cmd.MakeUnlock()); if (RCX_ERROR(result)) return result; result = Send(cmd.Set(kRCX_BootModeOp, 1, 3, 5, 7, 0xb)); if (RCX_ERROR(result)) return result; int check = Checksum(data, length < 0x4c00 ? length : 0x4c00); result = Send(cmd.Set(kRCX_BeginFirmwareOp, (UByte)(start), (UByte)(start>>8), (UByte)check, (UByte)(check>>8), 0)); if (RCX_ERROR(result)) return result; BeginProgress(progress ? length : 0); result = Download(data, length, fRCXFirmwareChunkSize); if (RCX_ERROR(result)) return result; // last packet is no-retry with an extra long delay // this gives the RCX time to respond and makes sure response doesn't get trampled result = Send(cmd.MakeBoot(), false); if (fTransport->GetFastMode()) return kRCX_OK; return result; } const int nOnesDensity[256] = { 0, 1, 1, 2, 1, 2, 2, 3, 1, 2, 2, 3, 2, 3, 3, 4, 1, 2, 2, 3, 2, 3, 3, 4, 2, 3, 3, 4, 3, 4, 4, 5, 1, 2, 2, 3, 2, 3, 3, 4, 2, 3, 3, 4, 3, 4, 4, 5, 2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6, 1, 2, 2, 3, 2, 3, 3, 4, 2, 3, 3, 4, 3, 4, 4, 5, 2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6, 2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6, 3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7, 1, 2, 2, 3, 2, 3, 3, 4, 2, 3, 3, 4, 3, 4, 4, 5, 2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6, 2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6, 3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7, 2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6, 3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7, 3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7, 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8 }; #define max(x, y) ((x) > (y)) ? (x) : (y) int RCX_Link::AdjustChunkSize(const int n, const int nMaxZeros, const int nMaxOnes, const UByte *data, bool bComplement) { int size = n; // // Avoid long strings of zeroes -- fast downloading doesn't like it and messaging // can loose sync. Especially with short distances and transmitter set to long // range [i.e. high power] // // Only need this check when complement byte transmission is disabled // if (!bComplement) { const int kOnesPlusMinusScore = 3; int i; for (i = 0; i < (size - nMaxZeros); ++i) { int j; UByte * p = (UByte*)data + i; if (*p != 0) continue; // // found a zero -- check to see how many // for (j = 0; j < nMaxZeros; ++j) { UByte * q = (UByte*)data + i + j; if (*q != 0) break; } if (j >= nMaxZeros) { // too many consecutive zeros. Shorten the message size // size = i + nMaxZeros; if (fVerbose) printf("too many consecutive zeros (%d)\n", j); break; } } for (i = 0; i < (size - nMaxOnes); ++i) { int j; ubyte aByte; int nLotsOfOnes; aByte = *(data + i); if (nOnesDensity[aByte] >= 3) continue; // // found a sparse byte -- check to see how many consecutive // nLotsOfOnes = 0; for (j = 0; j < nMaxOnes; ++j) { aByte = *(data + i + j); if (nOnesDensity[aByte] >= 3) { ++nLotsOfOnes; if (nLotsOfOnes > kOnesPlusMinusScore) break; } else { nLotsOfOnes -= 2; nLotsOfOnes = max(0, nLotsOfOnes); } } if (j >= nMaxOnes) { // too many consecutive sparse bytes. Shorten the message size // size = max(i, nMaxOnes); if (fVerbose) printf("too many consecutive sparse bytes (%d)\n", j); break; } } } return size; } RCX_Result RCX_Link::Download(const UByte *data, int length, int chunk) { RCX_Cmd cmd; RCX_Result result; UShort seq; int remain = length; int n; seq = 1; while(remain > 0) { if (remain <= chunk) { if (!gQuiet || gProgramMode) seq = 0; n = remain; } else n = chunk; int maxZeros = gUSB ? 23 : 30; int maxOnes = 90; n = AdjustChunkSize(n, maxZeros, maxOnes, data, fTransport->GetComplementData()); if (fVerbose) printf("sending %d bytes\n", n); result = Send(cmd.MakeDownload(seq++, data, (UShort)n), true, fDownloadWaitTime); if (result < 0) return result; remain -= n; data += n; if (!IncrementProgress(n)) return kRCX_AbortError; } return kRCX_OK; } RCX_Result RCX_Link::Send(const RCX_Cmd *cmd, bool retry, int timeout) { return Send(cmd->GetBody(), cmd->GetLength(), retry, timeout); } RCX_Result RCX_Link::Send(const UByte *data, int length, bool retry, int timeout) { int expected = ExpectedReplyLength(data, length); if (length > kMaxCmdLength || expected > kMaxReplyLength) return kRCX_RequestError; fResult = fTransport->Send(data, length, fReply, expected, kMaxReplyLength, retry, timeout); return fResult; } RCX_Result RCX_Link::GetReply(UByte *data, int maxLength) { if (fResult < 0) return fResult; int length = fResult; if (length > maxLength) length = maxLength; const UByte *src = fReply+1; for(int i=0; i