/// <summary> /// Sends the provided message retries times, with a small delay on fail. /// </summary> /// <remarks> /// Returns a succsefull Response on the first successful attempt, or the failed Response if we run out of tries. /// </remarks> private async Task <Response <bool> > WritePayload(Message message, ToolPresentNotifier notifier, CancellationToken cancellationToken) { for (int i = MaxSendAttempts; i > 0; i--) { await notifier.Notify(); if (cancellationToken.IsCancellationRequested) { return(Response.Create(ResponseStatus.Cancelled, false)); } if (!await device.SendMessage(message)) { this.logger.AddDebugMessage("WritePayload: Unable to send message."); continue; } if (await this.WaitForSuccess(this.protocol.ParseUploadResponse, cancellationToken)) { return(Response.Create(ResponseStatus.Success, true)); } this.logger.AddDebugMessage("WritePayload: Upload request failed."); } this.logger.AddDebugMessage("WritePayload: Giving up."); return(Response.Create(ResponseStatus.Error, false)); // this should be response from the loop but the compiler thinks the response variable isnt in scope here???? }
/// <summary> /// Constructor. /// </summary> public Query(Device device, Func <Message> generator, Func <Message, Response <T> > filter, ILogger logger, CancellationToken cancellationToken, ToolPresentNotifier notifier = null) { this.device = device; this.generator = generator; this.filter = filter; this.logger = logger; this.notifier = notifier; this.cancellationToken = cancellationToken; this.MaxTimeouts = 5; }
/// <summary> /// Constructor. /// </summary> public Vehicle( Device device, Protocol protocol, ILogger logger, ToolPresentNotifier notifier) { this.device = device; this.protocol = protocol; this.logger = logger; this.notifier = notifier; }
/// <summary> /// Ask all of the devices on the VPW bus for permission to switch to 4X speed. /// </summary> private async Task <List <byte> > RequestHighSpeedPermission(ToolPresentNotifier notifier) { Message permissionCheck = this.protocol.CreateHighSpeedPermissionRequest(DeviceId.Broadcast); await this.device.SendMessage(permissionCheck); // Note that as of right now, the AllPro only receives 6 of the 11 responses. // So until that gets fixed, we could miss a 'refuse' response and try to switch // to 4X anyhow. That just results in an aborted read attempt, with no harm done. List <byte> result = new List <byte>(); Message response = null; bool anyRefused = false; while ((response = await this.device.ReceiveMessage()) != null) { this.logger.AddDebugMessage("Parsing " + response.GetBytes().ToHex()); Protocol.HighSpeedPermissionResult parsed = this.protocol.ParseHighSpeedPermissionResponse(response); if (!parsed.IsValid) { await Task.Delay(100); continue; } result.Add(parsed.DeviceId); if (parsed.PermissionGranted) { this.logger.AddUserMessage(string.Format("Module 0x{0:X2} ({1}) has agreed to enter high-speed mode.", parsed.DeviceId, DeviceId.DeviceCategory(parsed.DeviceId))); // Forcing a notification message should help ELM devices receive responses. await notifier.ForceNotify(); await Task.Delay(100); continue; } this.logger.AddUserMessage(string.Format("Module 0x{0:X2} ({1}) has refused to enter high-speed mode.", parsed.DeviceId, DeviceId.DeviceCategory(parsed.DeviceId))); anyRefused = true; } if (anyRefused) { return(null); } return(result); }
/// <summary> /// Copy a single memory range to the PCM. /// </summary> private async Task <bool> WriteMemoryRange( MemoryRange range, byte[] image, bool justTestWrite, ToolPresentNotifier notifier, CancellationToken cancellationToken) { int devicePayloadSize = device.MaxSendSize - 12; // Headers use 10 bytes, sum uses 2 bytes. for (int index = 0; index < range.Size; index += devicePayloadSize) { if (cancellationToken.IsCancellationRequested) { return(false); } await notifier.Notify(); int startAddress = (int)(range.Address + index); int thisPayloadSize = Math.Min(devicePayloadSize, (int)range.Size - index); Message payloadMessage = protocol.CreateBlockMessage( image, startAddress, thisPayloadSize, startAddress, justTestWrite ? BlockCopyType.TestWrite : BlockCopyType.Copy); logger.AddUserMessage( string.Format( "Sending payload with offset 0x{0:X4}, start address 0x{1:X6}, length 0x{2:X4}.", index, startAddress, thisPayloadSize)); await this.WritePayload(payloadMessage, notifier, cancellationToken); // Not checking the success or failure here. // The debug pane will show if anything goes wrong, and the CRC check at the end will alert the user. } return(true); }
/// <summary> /// Does everything required to switch to VPW 4x /// </summary> public async Task <bool> VehicleSetVPW4x(VpwSpeed newSpeed, ToolPresentNotifier notifier) { if (!device.Supports4X) { if (newSpeed == VpwSpeed.FourX) { // where there is no support only report no switch to 4x logger.AddUserMessage("This interface does not support VPW 4x"); } return(true); } // Configure the vehicle bus when switching to 4x if (newSpeed == VpwSpeed.FourX) { logger.AddUserMessage("Attempting switch to VPW 4x"); await device.SetTimeout(TimeoutScenario.ReadProperty); // The list of modules may not be useful after all, but // checking for an empty list indicates an uncooperative // module on the VPW bus. List <byte> modules = await this.RequestHighSpeedPermission(notifier); if (modules == null) { // A device has refused the switch to high speed mode. return(false); } Message broadcast = this.protocol.CreateBeginHighSpeed(DeviceId.Broadcast); await this.device.SendMessage(broadcast); // Check for any devices that refused to switch to 4X speed. // These responses usually get lost, so this code might be pointless. Message response = null; while ((response = await this.device.ReceiveMessage()) != null) { Response <bool> refused = this.protocol.ParseHighSpeedRefusal(response); if (refused.Status != ResponseStatus.Success) { // This should help ELM devices receive responses. await notifier.ForceNotify(); continue; } if (refused.Value == false) { // TODO: Add module number. this.logger.AddUserMessage("Module refused high-speed switch."); return(false); } } } else { logger.AddUserMessage("Reverting to VPW 1x"); } // Request the device to change await device.SetVpwSpeed(newSpeed); TimeoutScenario scenario = newSpeed == VpwSpeed.Standard ? TimeoutScenario.ReadProperty : TimeoutScenario.ReadMemoryBlock; await device.SetTimeout(scenario); return(true); }
/// <summary> /// Load the executable payload on the PCM at the supplied address, and execute it. /// </summary> public async Task <bool> PCMExecute(byte[] payload, int address, ToolPresentNotifier notifier, CancellationToken cancellationToken) { logger.AddUserMessage("Uploading kernel to PCM."); logger.AddDebugMessage("Sending upload request with payload size " + payload.Length + ", loadaddress " + address.ToString("X6")); // Note that we request an upload of 4k maximum, because the PCM will reject anything bigger. // But you can request a 4k upload and then send up to 16k if you want, and the PCM will not object. int claimedSize = Math.Min(4096, payload.Length); // Since we're going to lie about the size, we need to check for overflow ourselves. if (address + payload.Length > 0xFFCDFF) { logger.AddUserMessage("Base address and size would exceed usable RAM."); return(false); } Message request = protocol.CreateUploadRequest(address, claimedSize); if (!await TrySendMessage(request, "upload request")) { return(false); } if (!await this.WaitForSuccess(this.protocol.ParseUploadPermissionResponse, cancellationToken)) { logger.AddUserMessage("Permission to upload kernel was denied."); return(false); } if (cancellationToken.IsCancellationRequested) { return(false); } logger.AddDebugMessage("Going to load a " + payload.Length + " byte payload to 0x" + address.ToString("X6")); await this.device.SetTimeout(TimeoutScenario.SendKernel); // Loop through the payload building and sending packets, highest first, execute on last int payloadSize = device.MaxSendSize - 12; // Headers use 10 bytes, sum uses 2 bytes. int chunkCount = payload.Length / payloadSize; int remainder = payload.Length % payloadSize; int offset = (chunkCount * payloadSize); int startAddress = address + offset; // First we send the 'remainder' payload, containing any bytes that won't fill up an entire upload packet. logger.AddDebugMessage( string.Format( "Sending remainder payload with offset 0x{0:X}, start address 0x{1:X}, length 0x{2:X}.", offset, startAddress, remainder)); Message remainderMessage = protocol.CreateBlockMessage( payload, offset, remainder, address + offset, remainder == payload.Length ? BlockCopyType.Execute : BlockCopyType.Copy); await notifier.Notify(); Response <bool> uploadResponse = await WritePayload(remainderMessage, notifier, cancellationToken); if (uploadResponse.Status != ResponseStatus.Success) { logger.AddDebugMessage("Could not upload kernel to PCM, remainder payload not accepted."); return(false); } // Now we send a series of full upload packets for (int chunkIndex = chunkCount; chunkIndex > 0; chunkIndex--) { if (cancellationToken.IsCancellationRequested) { return(false); } offset = (chunkIndex - 1) * payloadSize; startAddress = address + offset; Message payloadMessage = protocol.CreateBlockMessage( payload, offset, payloadSize, startAddress, offset == 0 ? BlockCopyType.Execute : BlockCopyType.Copy); logger.AddDebugMessage( string.Format( "Sending payload with offset 0x{0:X6}, start address 0x{1:X6}, length 0x{2:X4}.", offset, startAddress, payloadSize)); uploadResponse = await WritePayload(payloadMessage, notifier, cancellationToken); if (uploadResponse.Status != ResponseStatus.Success) { logger.AddDebugMessage("Could not upload kernel to PCM, payload not accepted."); return(false); } int bytesSent = payload.Length - offset; int percentDone = bytesSent * 100 / payload.Length; this.logger.AddUserMessage( string.Format( "Kernel upload {0}% complete.", percentDone)); } // Consider: return kernel version rather than boolean? UInt32 kernelVersion = await this.GetKernelVersion(); this.logger.AddUserMessage("Kernel Version: " + kernelVersion.ToString("X8")); return(true); }
/// <summary> /// Read the full contents of the PCM. /// Assumes the PCM is unlocked an were ready to go /// </summary> public async Task<Response<Stream>> ReadContents(PcmInfo info, CancellationToken cancellationToken) { try { this.device.ClearMessageQueue(); // This must precede the switch to 4X. ToolPresentNotifier toolPresentNotifier = new ToolPresentNotifier(this.logger, this.messageFactory, this.device); await toolPresentNotifier.Notify(); // switch to 4x, if possible. But continue either way. // if the vehicle bus switches but the device does not, the bus will need to time out to revert back to 1x, and the next steps will fail. if (!await this.VehicleSetVPW4x(VpwSpeed.FourX)) { this.logger.AddUserMessage("Stopping here because we were unable to switch to 4X."); return Response.Create(ResponseStatus.Error, (Stream)null); } await toolPresentNotifier.Notify(); // execute read kernel Response<byte[]> response = await LoadKernelFromFile("kernel.bin"); if (response.Status != ResponseStatus.Success) { logger.AddUserMessage("Failed to load kernel from file."); return new Response<Stream>(response.Status, null); } if (cancellationToken.IsCancellationRequested) { return Response.Create(ResponseStatus.Cancelled, (Stream)null); } await toolPresentNotifier.Notify(); // TODO: instead of this hard-coded 0xFF9150, get the base address from the PcmInfo object. if (!await PCMExecute(response.Value, 0xFF9150, cancellationToken)) { logger.AddUserMessage("Failed to upload kernel to PCM"); return new Response<Stream>( cancellationToken.IsCancellationRequested ? ResponseStatus.Cancelled : ResponseStatus.Error, null); } logger.AddUserMessage("kernel uploaded to PCM succesfully. Requesting data..."); await this.device.SetTimeout(TimeoutScenario.ReadMemoryBlock); int startAddress = info.ImageBaseAddress; int endAddress = info.ImageBaseAddress + info.ImageSize; int bytesRemaining = info.ImageSize; int blockSize = this.device.MaxReceiveSize - 10 - 2; // allow space for the header and block checksum byte[] image = new byte[info.ImageSize]; while (startAddress < endAddress) { if (cancellationToken.IsCancellationRequested) { return Response.Create(ResponseStatus.Cancelled, (Stream)null); } await toolPresentNotifier.Notify(); if (startAddress + blockSize > endAddress) { blockSize = endAddress - startAddress; } if (blockSize < 1) { this.logger.AddUserMessage("Image download complete"); break; } if (!await TryReadBlock(image, blockSize, startAddress)) { this.logger.AddUserMessage( string.Format( "Unable to read block from {0} to {1}", startAddress, (startAddress + blockSize) - 1)); return new Response<Stream>(ResponseStatus.Error, null); } startAddress += blockSize; } await this.Cleanup(); // Not sure why this does not get called in the finally block on successfull read? MemoryStream stream = new MemoryStream(image); return new Response<Stream>(ResponseStatus.Success, stream); } catch(Exception exception) { this.logger.AddUserMessage("Something went wrong. " + exception.Message); this.logger.AddDebugMessage(exception.ToString()); return new Response<Stream>(ResponseStatus.Error, null); } finally { // Sending the exit command at both speeds and revert to 1x. await this.Cleanup(); } }
/// <summary> /// Compare CRCs from the file to CRCs from the PCM. /// </summary> private async Task <bool> CompareRanges( IList <MemoryRange> ranges, byte[] image, BlockType blockTypes, ToolPresentNotifier notifier, CancellationToken cancellationToken) { logger.AddUserMessage("Requesting CRCs from PCM..."); await notifier.Notify(); // The kernel will remember (and return) the CRC value of the last block it // was asked about, which leads to misleading results if you only rewrite // a single block. So we send a a bogus query to reset the last-used CRC // value in the kernel. Query <UInt32> crcReset = new Query <uint>( this.device, () => this.protocol.CreateCrcQuery(0, 0), (message) => this.protocol.ParseCrc(message, 0, 0), this.logger, cancellationToken, notifier); await crcReset.Execute(); await this.device.SetTimeout(TimeoutScenario.ReadCrc); bool successForAllRanges = true; foreach (MemoryRange range in ranges) { if ((range.Type & blockTypes) == 0) { this.logger.AddUserMessage( string.Format( "Range {0:X6}-{1:X6} - Not needed for this operation.", range.Address, range.Address + (range.Size - 1))); continue; } await notifier.Notify(); this.device.ClearMessageQueue(); bool success = false; UInt32 crc = 0; // You might think that a shorter retry delay would speed things up, // but 1500ms delay gets CRC results in about 3.5 seconds. // A 1000ms delay resulted in 4+ second CRC responses, and a 750ms // delay resulted in 5 second CRC responses. The PCM needs to spend // its time caculating CRCs rather than responding to messages. int retryDelay = 1500; Message query = this.protocol.CreateCrcQuery(range.Address, range.Size); for (int attempts = 0; attempts < 10; attempts++) { if (cancellationToken.IsCancellationRequested) { break; } await notifier.Notify(); if (!await this.device.SendMessage(query)) { // This delay is fast because we're waiting for the bus to be available, // rather than waiting for the PCM's CPU to finish computing the CRC as // with the other two delays below. await Task.Delay(100); continue; } Message response = await this.device.ReceiveMessage(); if (response == null) { await Task.Delay(retryDelay); continue; } Response <UInt32> crcResponse = this.protocol.ParseCrc(response, range.Address, range.Size); if (crcResponse.Status != ResponseStatus.Success) { await Task.Delay(retryDelay); continue; } success = true; crc = crcResponse.Value; break; } this.device.ClearMessageQueue(); if (!success) { this.logger.AddUserMessage("Unable to get CRC for memory range " + range.Address.ToString("X8") + " / " + range.Size.ToString("X8")); successForAllRanges = false; continue; } range.ActualCrc = crc; this.logger.AddUserMessage( string.Format( "Range {0:X6}-{1:X6} - File: {2:X8} - PCM: {3:X8} - ({4}) - {5}", range.Address, range.Address + (range.Size - 1), range.DesiredCrc, range.ActualCrc, range.Type, range.DesiredCrc == range.ActualCrc ? "Same" : "Different")); } await notifier.Notify(); foreach (MemoryRange range in ranges) { if ((range.Type & blockTypes) == 0) { continue; } if (range.ActualCrc != range.DesiredCrc) { return(false); } } this.device.ClearMessageQueue(); return(successForAllRanges); }
/// <summary> /// Write the calibration blocks. /// </summary> private async Task <bool> Write(CancellationToken cancellationToken, byte[] image, WriteType writeType, ToolPresentNotifier notifier) { await notifier.Notify(); BlockType relevantBlocks; switch (writeType) { case WriteType.Compare: relevantBlocks = BlockType.All; break; case WriteType.TestWrite: relevantBlocks = BlockType.Calibration; break; case WriteType.Calibration: relevantBlocks = BlockType.Calibration; break; case WriteType.Parameters: relevantBlocks = BlockType.Parameter; break; case WriteType.OsAndCalibration: relevantBlocks = BlockType.Calibration | BlockType.OperatingSystem; break; case WriteType.Full: relevantBlocks = BlockType.All; break; default: throw new InvalidDataException("Unsuppported operation type: " + writeType.ToString()); } // Which flash chip? await notifier.Notify(); await this.device.SetTimeout(TimeoutScenario.ReadProperty); Query <UInt32> chipIdQuery = new Query <uint>( this.device, this.protocol.CreateFlashMemoryTypeQuery, this.protocol.ParseFlashMemoryType, this.logger, cancellationToken, notifier); Response <UInt32> chipIdResponse = await chipIdQuery.Execute(); if (chipIdResponse.Status != ResponseStatus.Success) { logger.AddUserMessage("Unable to determine which flash chip is in this PCM"); return(false); } logger.AddUserMessage("Flash memory type: " + chipIdResponse.Value.ToString("X8")); // known chips // http://ftp1.digi.com/support/documentation/jtag_v410_flashes.pdf string Amd = "Amd"; // 0001 string Intel = "Intel"; // 0089 string I4471 = "28F400B5-B 512Kb"; // 4471 string A2258 = "Am29F800B 1Mbyte"; // 2258 string I889D = "28F800B5-B 1Mbyte"; // 889D logger.AddUserMessage("Flash memory ID: " + chipIdResponse.Value.ToString("X8")); if ((chipIdResponse.Value >> 16) == 0x0001) { logger.AddUserMessage("Flash memory manufactuer: " + Amd); } if ((chipIdResponse.Value >> 16) == 0x0089) { logger.AddUserMessage("Flash memory manufactuer: " + Intel); } if ((chipIdResponse.Value & 0xFFFF) == 0x4471) { logger.AddUserMessage("Flash memory type: " + I4471); } if ((chipIdResponse.Value & 0xFFFF) == 0x2258) { logger.AddUserMessage("Flash memory type: " + A2258); } if ((chipIdResponse.Value & 0xFFFF) == 0x889D) { logger.AddUserMessage("Flash memory type: " + I889D); } await notifier.Notify(); IList <MemoryRange> ranges = this.GetMemoryRanges(chipIdResponse.Value); if (ranges == null) { return(false); } logger.AddUserMessage("Computing CRCs from local file..."); this.GetCrcFromImage(ranges, image); if (await this.CompareRanges( ranges, image, relevantBlocks, notifier, cancellationToken)) { // Don't stop here if the user just wants to test their cable. if (writeType != WriteType.TestWrite) { this.logger.AddUserMessage("All ranges are identical."); return(true); } } // Stop now if the user only requested a comparison. if (writeType == WriteType.Compare) { this.logger.AddUserMessage("Note that mismatched Parameter blocks are to be expected."); this.logger.AddUserMessage("Parameter data can change every time the PCM is used."); return(true); } // Erase and rewrite the required memory ranges. await this.device.SetTimeout(TimeoutScenario.Maximum); foreach (MemoryRange range in ranges) { // We'll send a tool-present message during the erase request. if ((range.ActualCrc == range.DesiredCrc) && (writeType != WriteType.TestWrite)) { continue; } if ((range.Type & relevantBlocks) == 0) { continue; } this.logger.AddUserMessage( string.Format( "Processing range {0:X6}-{1:X6}", range.Address, range.Address + (range.Size - 1))); if (writeType != WriteType.TestWrite) { this.logger.AddUserMessage("Erasing"); Query <byte> eraseRequest = new Query <byte>( this.device, () => this.protocol.CreateFlashEraseBlockRequest(range.Address), this.protocol.ParseFlashEraseBlock, this.logger, cancellationToken, notifier); eraseRequest.MaxTimeouts = 5; // Reduce this when we know how many are likely to be needed. Response <byte> eraseResponse = await eraseRequest.Execute(); if (eraseResponse.Status != ResponseStatus.Success) { this.logger.AddUserMessage("Unable to erase flash memory: " + eraseResponse.Status.ToString()); this.RequestDebugLogs(cancellationToken); return(false); } if (eraseResponse.Value != 0x80) { this.logger.AddUserMessage("Unable to erase flash memory. Code: " + eraseResponse.Value.ToString("X2")); this.RequestDebugLogs(cancellationToken); return(false); } } if (writeType == WriteType.TestWrite) { this.logger.AddUserMessage("Testing..."); } else { this.logger.AddUserMessage("Writing..."); } await notifier.Notify(); await WriteMemoryRange( range, image, writeType == WriteType.TestWrite, notifier, cancellationToken); } bool match = await this.CompareRanges(ranges, image, relevantBlocks, notifier, cancellationToken); if (writeType == WriteType.TestWrite) { // TODO: the app should know if any errors were encountered. The user shouldn't need to check. this.logger.AddUserMessage("Test complete. Were any errors logged above?"); return(true); } if (match) { this.logger.AddUserMessage("Flash successful!"); return(true); } this.logger.AddUserMessage("==============================================="); this.logger.AddUserMessage("THE CHANGES WERE -NOT- WRITTEN SUCCESSFULLY"); this.logger.AddUserMessage("==============================================="); if (cancellationToken.IsCancellationRequested) { this.logger.AddUserMessage(""); this.logger.AddUserMessage("The operation was cancelled."); this.logger.AddUserMessage("This PCM is probably not usable in its current state."); this.logger.AddUserMessage(""); } else { this.logger.AddUserMessage("Don't panic. Also, don't try to drive this car."); this.logger.AddUserMessage("Please try flashing again. Preferably now."); this.logger.AddUserMessage("In most cases, the second try will succeed."); this.logger.AddUserMessage(""); this.logger.AddUserMessage("If this happens three times in a row, please"); this.logger.AddUserMessage("start a new thread at pcmhacking.net, and"); this.logger.AddUserMessage("include the contents of the debug tab."); this.logger.AddUserMessage(""); this.RequestDebugLogs(cancellationToken); } return(false); }