Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Separating kernel-specific code into new classes. #95

Merged
merged 6 commits into from Feb 14, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
19 changes: 17 additions & 2 deletions Apps/PcmHammer/MainForm.cs
Expand Up @@ -933,7 +933,15 @@ private async void readFullContents_BackgroundThread()

// Do the actual reading.
DateTime start = DateTime.Now;
Response<Stream> readResponse = await this.vehicle.ReadContents(info, this.cancellationTokenSource.Token);

CKernelReader reader = new CKernelReader(
this.vehicle,
this);

Response<Stream> readResponse = await reader.ReadContents(
info,
cancellationTokenSource.Token);

this.AddUserMessage("Elapsed time " + DateTime.Now.Subtract(start));
if (readResponse.Status != ResponseStatus.Success)
{
Expand Down Expand Up @@ -1127,13 +1135,20 @@ private async void write_BackgroundThread(WriteType writeType)
}

DateTime start = DateTime.Now;
await this.vehicle.Write(

CKernelWriter writer = new CKernelWriter(
this.vehicle,
new Protocol(),
this);

await writer.Write(
image,
writeType,
kernelVersion,
validator,
needToCheckOperatingSystem,
this.cancellationTokenSource.Token);

this.AddUserMessage("Elapsed time " + DateTime.Now.Subtract(start));
}
catch (IOException exception)
Expand Down
Expand Up @@ -9,13 +9,28 @@
namespace PcmHacking
{
/// <summary>
/// From the application's perspective, this class is the API to the vehicle.
/// Reader classes use a kernel to read the entire flash memory.
/// </summary>
/// <remarks>
/// Methods in this class are high-level operations like "get the VIN," or "read the contents of the EEPROM."
/// </remarks>
public partial class Vehicle : IDisposable
public class CKernelReader
{
private readonly Vehicle vehicle;
private readonly Protocol protocol;
private readonly ILogger logger;

public CKernelReader(Vehicle vehicle, ILogger logger)
{
this.vehicle = vehicle;

// This seems wrong... Some alternatives:
// a) Have the caller pass in the message factory and message-parser methods
// b) Have the caller pass in a smaller KernelProtocol class - with subclasses for each kernel -
// This would only make sense if it turns out that this one reader class can handle multiple kernels.
// c) Just create a smaller KernelProtocol class here, for the kernel that this class is intended for.
this.protocol = new Protocol();

this.logger = logger;
}

/// <summary>
/// Read the full contents of the PCM.
/// Assumes the PCM is unlocked an were ready to go
Expand All @@ -24,21 +39,21 @@ public async Task<Response<Stream>> ReadContents(PcmInfo info, CancellationToken
{
try
{
await notifier.Notify();
this.device.ClearMessageQueue();
await this.vehicle.SendToolPresentNotification();
this.vehicle.ClearDeviceMessageQueue();

// 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, notifier))
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);
}

await notifier.Notify();
await this.vehicle.SendToolPresentNotification();

// execute read kernel
Response<byte[]> response = await LoadKernelFromFile("read-kernel.bin");
Response<byte[]> response = await vehicle.LoadKernelFromFile("read-kernel.bin");
if (response.Status != ResponseStatus.Success)
{
logger.AddUserMessage("Failed to load kernel from file.");
Expand All @@ -50,11 +65,11 @@ public async Task<Response<Stream>> ReadContents(PcmInfo info, CancellationToken
return Response.Create(ResponseStatus.Cancelled, (Stream)null);
}

await notifier.Notify();
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 PCMExecute(response.Value, 0xFF8000, notifier, cancellationToken))
if (!await this.vehicle.PCMExecute(response.Value, 0xFF8000, cancellationToken))
{
logger.AddUserMessage("Failed to upload kernel to PCM");

Expand All @@ -65,12 +80,12 @@ public async Task<Response<Stream>> ReadContents(PcmInfo info, CancellationToken

logger.AddUserMessage("kernel uploaded to PCM succesfully. Requesting data...");

await this.device.SetTimeout(TimeoutScenario.ReadMemoryBlock);
await this.vehicle.SetDeviceTimeout(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
int blockSize = this.vehicle.DeviceMaxReceiveSize - 10 - 2; // allow space for the header and block checksum

byte[] image = new byte[info.ImageSize];

Expand All @@ -82,7 +97,7 @@ public async Task<Response<Stream>> ReadContents(PcmInfo info, CancellationToken
}

// The read kernel needs a short message here for reasons unknown. Without it, it will RX 2 messages then drop one.
await notifier.ForceNotify();
await this.vehicle.ForceSendToolPresentNotification();

if (startAddress + blockSize > endAddress)
{
Expand All @@ -108,7 +123,7 @@ public async Task<Response<Stream>> ReadContents(PcmInfo info, CancellationToken
startAddress += blockSize;
}

await this.Cleanup(); // Not sure why this does not get called in the finally block on successfull read?
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);
Expand All @@ -122,7 +137,7 @@ public async Task<Response<Stream>> ReadContents(PcmInfo info, CancellationToken
finally
{
// Sending the exit command at both speeds and revert to 1x.
await this.Cleanup();
await this.vehicle.Cleanup();
}
}

Expand All @@ -133,52 +148,30 @@ private async Task<bool> TryReadBlock(byte[] image, int length, int startAddress
{
this.logger.AddDebugMessage(string.Format("Reading from {0} / 0x{0:X}, length {1} / 0x{1:X}", startAddress, length));

for (int sendAttempt = 1; sendAttempt <= MaxSendAttempts; sendAttempt++)
for (int sendAttempt = 1; sendAttempt <= Vehicle.MaxSendAttempts; sendAttempt++)
{
if (cancellationToken.IsCancellationRequested)
{
break;
}

Message message = this.protocol.CreateReadRequest(startAddress, length);
Response<byte[]> readResponse = await this.vehicle.ReadMemory(
() => this.protocol.CreateReadRequest(startAddress, length),
(payloadMessage) => this.protocol.ParsePayload(payloadMessage, length, startAddress),
cancellationToken);

if (!await this.device.SendMessage(message))
if(readResponse.Status != ResponseStatus.Success)
{
this.logger.AddDebugMessage("Unable to send read request.");
continue;
}

for (int receiveAttempt = 1; receiveAttempt <= MaxReceiveAttempts; receiveAttempt++)
{
if (cancellationToken.IsCancellationRequested)
{
break;
}

Message payloadMessage = await this.device.ReceiveMessage();
if (payloadMessage == null)
{
this.logger.AddDebugMessage("No payload following read request.");
continue;
}

this.logger.AddDebugMessage("Processing message");
byte[] payload = readResponse.Value;
Buffer.BlockCopy(payload, 0, image, startAddress, length);

Response<byte[]> payloadResponse = this.protocol.ParsePayload(payloadMessage, length, startAddress);
if (payloadResponse.Status != ResponseStatus.Success)
{
this.logger.AddDebugMessage("Not a valid payload message or bad checksum");
continue;
}

byte[] payload = payloadResponse.Value;
Buffer.BlockCopy(payload, 0, image, startAddress, length);
int percentDone = (startAddress * 100) / image.Length;
this.logger.AddUserMessage(string.Format("Recieved block starting at {0} / 0x{0:X}. {1}%", startAddress, percentDone));

int percentDone = (startAddress * 100) / image.Length;
this.logger.AddUserMessage(string.Format("Recieved block starting at {0} / 0x{0:X}. {1}%", startAddress, percentDone));

return true;
}
return true;
}

return false;
Expand Down
169 changes: 169 additions & 0 deletions Apps/PcmLibrary/CKernelVerifier.cs
@@ -0,0 +1,169 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

namespace PcmHacking
{
public class CKernelVerifier
{
private readonly byte[] image;
private readonly IEnumerable<MemoryRange> ranges;
private readonly Vehicle vehicle;
private readonly Protocol protocol;
private readonly ILogger logger;

public CKernelVerifier(byte[] image, IEnumerable<MemoryRange> ranges, Vehicle vehicle, Protocol protocol, ILogger logger)
{
this.image = image;
this.ranges = ranges;
this.vehicle = vehicle;
this.protocol = protocol;
this.logger = logger;
}

/// <summary>
/// Get the CRC for each address range in the file that the user wants to flash.
/// </summary>
private void GetCrcFromImage()
{
Crc crc = new Crc();
foreach (MemoryRange range in this.ranges)
{
range.DesiredCrc = crc.GetCrc(this.image, range.Address, range.Size);
}
}

/// <summary>
/// Compare CRCs from the file to CRCs from the PCM.
/// </summary>
public async Task<bool> CompareRanges(
IList<MemoryRange> ranges,
byte[] image,
BlockType blockTypes,
CancellationToken cancellationToken)
{
logger.AddUserMessage("Calculating CRCs from file...");
this.GetCrcFromImage();

logger.AddUserMessage("Requesting CRCs from PCM...");
await this.vehicle.SendToolPresentNotification();

// 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 = this.vehicle.CreateQuery<uint>(
() => this.protocol.CreateCrcQuery(0, 0),
(message) => this.protocol.ParseCrc(message, 0, 0),
cancellationToken);
await crcReset.Execute();

await this.vehicle.SetDeviceTimeout(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 this.vehicle.SendToolPresentNotification();
this.vehicle.ClearDeviceMessageQueue();
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 this.vehicle.SendToolPresentNotification();
if (!await this.vehicle.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.vehicle.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.vehicle.ClearDeviceMessageQueue();

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 this.vehicle.SendToolPresentNotification();

foreach (MemoryRange range in ranges)
{
if ((range.Type & blockTypes) == 0)
{
continue;
}

if (range.ActualCrc != range.DesiredCrc)
{
return false;
}
}

this.vehicle.ClearDeviceMessageQueue();

return successForAllRanges;
}
}
}