Skip to content
Permalink
Browse files

Merge pull request #95 from LegacyNsfw/nsfw/ckernel-classes

Separating kernel-specific code into new classes.
  • Loading branch information...
LegacyNsfw committed Feb 14, 2019
2 parents 5467092 + 5557d32 commit 2bd5d318b23e2dcbeef334c9d2afbf9250dc2320
@@ -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)
{
@@ -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)
@@ -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
@@ -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.");
@@ -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");

@@ -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];

@@ -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)
{
@@ -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);
@@ -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();
}
}

@@ -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;
@@ -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;
}
}
}
Oops, something went wrong.

0 comments on commit 2bd5d31

Please sign in to comment.
You can’t perform that action at this time.
You signed in with another tab or window. Reload to refresh your session. You signed out in another tab or window. Reload to refresh your session.