/// <summary> /// Write the calibration blocks. /// </summary> private async Task <bool> Write(CancellationToken cancellationToken, byte[] image) { await this.vehicle.SendToolPresentNotification(); BlockType relevantBlocks; switch (this.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.OsPlusCalibrationPlusBoot: // Overwriting parameter blocks would break the EBCM pairing, // which is not what most users want. They just want a new OS // and the calibration to go along with it. // // The cast seems redundant, but C# converts the enum values // to ints when it does arithmetic. relevantBlocks = (BlockType)(BlockType.All - BlockType.Parameter); break; case WriteType.Full: relevantBlocks = BlockType.All; break; case WriteType.IPC: relevantBlocks = BlockType.IPC; break; default: throw new InvalidDataException("Unsuppported operation type: " + this.writeType.ToString()); } // Which flash chip? await this.vehicle.SendToolPresentNotification(); UInt32 chipId = await this.vehicle.QueryFlashChipId(cancellationToken); FlashChip flashChip = FlashChip.Create(chipId, this.logger); logger.AddUserMessage("Flash chip: " + flashChip.ToString()); if (flashChip.Size != image.Length) { this.logger.AddUserMessage(string.Format("File size {0:n0} does not match Flash Chip size {1:n0}!", image.Length, flashChip.Size)); await this.vehicle.Cleanup(); return(false); } CKernelVerifier verifier = new CKernelVerifier( image, flashChip.MemoryRanges, this.vehicle, this.protocol, this.logger); bool allRangesMatch = false; int messageRetryCount = 0; await this.vehicle.SendToolPresentNotification(); for (int attempt = 1; attempt <= 5; attempt++) { if (await verifier.CompareRanges( image, relevantBlocks, cancellationToken)) { allRangesMatch = true; // Don't stop here if the user just wants to test their cable. if (this.writeType == WriteType.TestWrite) { if (attempt == 1) { this.logger.AddUserMessage("Beginning test."); } } else { this.logger.AddUserMessage("All relevant ranges are identical."); if (attempt > 1) { Utility.ReportRetryCount("Write", messageRetryCount, flashChip.Size, this.logger); } return(true); } } // For test writes, report results after the first iteration, then we're done. if ((this.writeType == WriteType.TestWrite) && (attempt > 1)) { logger.AddUserMessage("Test write complete."); Utility.ReportRetryCount("Write", messageRetryCount, flashChip.Size, this.logger); return(true); } // Stop now if the user only requested a comparison. if (this.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. DateTime startTime = DateTime.Now; UInt32 totalSize = this.GetTotalSize(flashChip, relevantBlocks); UInt32 bytesRemaining = totalSize; foreach (MemoryRange range in flashChip.MemoryRanges) { // We'll send a tool-present message during the erase request. if (!this.ShouldProcess(range, relevantBlocks)) { continue; } this.logger.AddUserMessage( string.Format( "Processing range {0:X6}-{1:X6}", range.Address, range.Address + (range.Size - 1))); if (this.writeType == WriteType.TestWrite) { this.logger.AddUserMessage("Pretending to erase."); } else { if (!await this.EraseMemoryRange(range, cancellationToken)) { return(false); } } if (this.writeType == WriteType.TestWrite) { this.logger.AddUserMessage("Pretending to write..."); } else { this.logger.AddUserMessage("Writing..."); } this.logger.AddUserMessage("Address\t% Done\tTime Remaining"); Response <bool> writeResponse = await WriteMemoryRange( range, image, this.writeType == WriteType.TestWrite, startTime, totalSize, bytesRemaining, cancellationToken); if (writeResponse.RetryCount > 0) { this.logger.AddUserMessage("Retry count for this block: " + writeResponse.RetryCount); messageRetryCount += writeResponse.RetryCount; } if (writeResponse.Value) { bytesRemaining -= range.Size; } } } if (allRangesMatch) { this.logger.AddUserMessage("Flash successful!"); if (messageRetryCount > 2) { logger.AddUserMessage("Write request messages had to be re-sent " + messageRetryCount + " times."); } return(true); } // During a test write, we will return from the middle of the loop above. // So if we made it here, a real write has failed. this.logger.AddUserMessage("==============================================="); this.logger.AddUserMessage("THE CHANGES WERE -NOT- WRITTEN SUCCESSFULLY"); this.logger.AddUserMessage("==============================================="); if (this.writeType == WriteType.Calibration) { this.logger.AddUserMessage("Erasing Calibration to force recovery mode."); this.logger.AddUserMessage(""); foreach (MemoryRange range in flashChip.MemoryRanges) { if (range.Type == BlockType.Calibration) { await this.EraseMemoryRange(range, cancellationToken); } } } 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("This may indicate a hardware problem on the PCM."); this.logger.AddUserMessage("We tried, and re-tried, and it still didn't work."); this.logger.AddUserMessage(""); this.logger.AddUserMessage("Please start a new thread at pcmhacking.net, and"); this.logger.AddUserMessage("include the contents of the debug tab."); this.RequestDebugLogs(cancellationToken); } return(false); }
/// <summary> /// Read the full contents of the PCM. /// Assumes the PCM is unlocked and we're ready to go. /// </summary> public async Task <Response <Stream> > ReadContents(CancellationToken cancellationToken) { try { // Start with known state. await this.vehicle.ForceSendToolPresentNotification(); this.vehicle.ClearDeviceMessageQueue(); // Switch to 4x, if possible. But continue either way. if (Configuration.Enable4xReadWrite) { // 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.vehicle.VehicleSetVPW4x(VpwSpeed.FourX)) { this.logger.AddUserMessage("Stopping here because we were unable to switch to 4X."); return(Response.Create(ResponseStatus.Error, (Stream)null)); } } else { this.logger.AddUserMessage("4X communications disabled by configuration."); } await this.vehicle.SendToolPresentNotification(); // execute read kernel Response <byte[]> response = await vehicle.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 this.vehicle.SendToolPresentNotification(); // TODO: instead of this hard-coded 0xFF9150, get the base address from the PcmInfo object. // TODO: choose kernel at run time? Because now it's FF8000... if (!await this.vehicle.PCMExecute(response.Value, 0xFF8000, 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..."); // Which flash chip? await this.vehicle.SendToolPresentNotification(); UInt32 chipId = await this.vehicle.QueryFlashChipId(cancellationToken); FlashChip flashChip = FlashChip.Create(chipId, this.logger); logger.AddUserMessage("Flash chip: " + flashChip.ToString()); await this.vehicle.SetDeviceTimeout(TimeoutScenario.ReadMemoryBlock); this.logger.AddUserMessage("Address\t% Done\tTime Remaining"); byte[] image = new byte[flashChip.Size]; int retryCount = 0; int startAddress = 0; int endAddress = (int)flashChip.Size; int bytesRemaining = (int)flashChip.Size; int blockSize = this.vehicle.DeviceMaxReceiveSize - 10 - 2; // allow space for the header and block checksum DateTime startTime = DateTime.MaxValue; while (startAddress < endAddress) { if (cancellationToken.IsCancellationRequested) { return(Response.Create(ResponseStatus.Cancelled, (Stream)null)); } // The read kernel needs a short message here for reasons unknown. Without it, it will RX 2 messages then drop one. await this.vehicle.ForceSendToolPresentNotification(); if (startAddress + blockSize > endAddress) { blockSize = endAddress - startAddress; } if (blockSize < 1) { this.logger.AddUserMessage("Image download complete"); break; } if (startTime == DateTime.MaxValue) { startTime = DateTime.Now; } Response <bool> readResponse = await TryReadBlock( image, blockSize, startAddress, startTime, cancellationToken); if (readResponse.Status != ResponseStatus.Success) { 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; retryCount += readResponse.RetryCount; } logger.AddUserMessage("Read complete."); Utility.ReportRetryCount("Read", retryCount, flashChip.Size, this.logger); logger.AddUserMessage("Starting verification..."); CKernelVerifier verifier = new CKernelVerifier( image, flashChip.MemoryRanges, this.vehicle, this.protocol, this.logger); if (await verifier.CompareRanges( image, BlockType.All, cancellationToken)) { logger.AddUserMessage("The contents of the file match the contents of the PCM."); } else { logger.AddUserMessage("##############################################################################"); logger.AddUserMessage("There are errors in the data that was read from the PCM. Do not use this file."); logger.AddUserMessage("##############################################################################"); } await this.vehicle.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.vehicle.Cleanup(); } }
/// <summary> /// Write the calibration blocks. /// </summary> private async Task <bool> Write(CancellationToken cancellationToken, byte[] image, WriteType writeType) { await this.vehicle.SendToolPresentNotification(); 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: // Overwriting parameter blocks would break the EBCM pairing, // which is not what most users want. They just want a new OS // and the calibration to go along with it. // // The cast seems redundant, but C# converts the enum values // to ints when it does arithmetic. relevantBlocks = (BlockType)(BlockType.All - BlockType.Parameter); break; default: throw new InvalidDataException("Unsuppported operation type: " + writeType.ToString()); } // Which flash chip? await this.vehicle.SendToolPresentNotification(); await this.vehicle.SetDeviceTimeout(TimeoutScenario.ReadProperty); Query <UInt32> chipIdQuery = this.vehicle.CreateQuery <UInt32>( this.protocol.CreateFlashMemoryTypeQuery, this.protocol.ParseFlashMemoryType, cancellationToken); 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); } // TODO: Move the device types lookup to a function in Misc/FlashChips.cs // known chips in the P01 and P59 // 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 string unknown = "unknown"; // default case logger.AddUserMessage("Flash memory ID code: " + chipIdResponse.Value.ToString("X8")); switch ((chipIdResponse.Value >> 16)) { case 0x0001: logger.AddUserMessage("Flash memory manufactuer: " + Amd); break; case 0x0089: logger.AddUserMessage("Flash memory manufactuer: " + Intel); break; default: logger.AddUserMessage("Flash memory manufactuer: " + unknown); break; } switch (chipIdResponse.Value & 0xFFFF) { case 0x4471: logger.AddUserMessage("Flash memory type: " + I4471); break; case 0x2258: logger.AddUserMessage("Flash memory type: " + A2258); break; case 0x889D: logger.AddUserMessage("Flash memory type: " + I889D); break; default: logger.AddUserMessage("Flash memory type: " + unknown); break; } await this.vehicle.SendToolPresentNotification(); IList <MemoryRange> ranges = FlashChips.GetMemoryRanges(chipIdResponse.Value, this.logger); if (ranges == null) { return(false); } CKernelVerifier verifier = new CKernelVerifier( image, ranges, this.vehicle, this.protocol, this.logger); bool allRangesMatch = false; bool writeAttempted = false; for (int attempt = 1; attempt <= 5; attempt++) { if (await verifier.CompareRanges( ranges, image, relevantBlocks, cancellationToken)) { allRangesMatch = true; // Don't stop here if the user just wants to test their cable. if (writeType == WriteType.TestWrite) { if (attempt == 1) { this.logger.AddUserMessage("Beginning test."); } } else { this.logger.AddUserMessage("All ranges are identical."); return(true); } } if ((writeType == WriteType.TestWrite) && (attempt > 1)) { // 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); } // 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); } writeAttempted = true; // Erase and rewrite the required memory ranges. await this.vehicle.SetDeviceTimeout(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("Pretending to erase."); } else { if (!await this.EraseMemoryRange(range, cancellationToken)) { return(false); } } if (writeType == WriteType.TestWrite) { this.logger.AddUserMessage("Pretending to write..."); } else { this.logger.AddUserMessage("Writing..."); } await this.vehicle.SendToolPresentNotification(); await WriteMemoryRange( range, image, writeType == WriteType.TestWrite, cancellationToken); } } if (!writeAttempted) { this.logger.AddUserMessage("Assertion failed. WriteAttempted should be true."); } if (allRangesMatch) { this.logger.AddUserMessage("Flash successful!"); return(true); } this.logger.AddUserMessage("==============================================="); this.logger.AddUserMessage("THE CHANGES WERE -NOT- WRITTEN SUCCESSFULLY"); this.logger.AddUserMessage("==============================================="); if (writeType == WriteType.Calibration) { this.logger.AddUserMessage("Erasing Calibration to force recovery mode."); this.logger.AddUserMessage(""); foreach (MemoryRange range in ranges) { if (range.Type == BlockType.Calibration) { await this.EraseMemoryRange(range, cancellationToken); } } } 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("This may indicate a hardware problem on the PCM."); this.logger.AddUserMessage("We tried, and re-tried, and it still didn't work."); this.logger.AddUserMessage(""); this.logger.AddUserMessage("Please start a new thread at pcmhacking.net, and"); this.logger.AddUserMessage("include the contents of the debug tab."); this.RequestDebugLogs(cancellationToken); } return(false); }