Tag Archives: objdump

DPF Light Patch

This is meant for programmers or at least folks who understand coding in general. Here I am going to show how I implemented the DPF light patch, part of Diesel ECU Patch v1, on my former (Euro 4) car.
DPF Light Patch - Active Regeneration In Progress


Actual source code, updated to C++14:


// Copyright SubaruDieselCrew (2011-2017)   https://subdiesel.wordpress.com

#include <array>
#include <chrono>
//#include <bitset>
#include "sh.h"
#include "JZ2F401A.h"

using namespace std::chrono_literals;

/**
 * @brief DPF light flashing modes (stock)
 *
 */
enum class DPFLightMode {
    off = 0,
    /**
     * @brief soot-high warning aka vehicle speed request
     *
     */
    on_steady = 1,
    /**
     * @brief error
     *
     * (multiple causes: compulsory regeneration required, oil dilution critical, ash overfill, DPF limp-home mode;
     * see https://subdiesel.wordpress.com/2011/03/21/dpf-light/ )
     */
    flashing = 2,
};

/**
 * @brief time resolution (= CAN frame ID 0x600 interval)
 *
 */
const constexpr auto interval {50ms};
/**
 * @brief stock period for flashing mode is 800 ms,
 *        does not have to match stock here
 *
 */
const constexpr auto dpfLightPeriod {800ms};
/**
 * @brief defines DPF light output over time when active regeneration is on
 *
 */
const constexpr std::array<bool, dpfLightPeriod / interval> dpfLightCustomPattern
{
    1, 1, 0, 0,  1, 1, 0, 0,
    0, 0, 0, 0,  0, 0, 0, 0
};
// tested alternative: bitset; storage-efficient but much more lookup code
// const std::bitset<dpfLightPeriod / interval> bits {"1100110000000000"};

/**
 * @brief Implement custom flashing mode.
 *
 * Called every 50 ms (CAN-ID 0x600 interval)
 * from patched stock function "calcDpfLight".
 * Standard error flashing mode already handled by untouched
 * stock subroutine portion and this case this function won't get called.
 */
void calc_DPFLight_continue()
{
    // needed as original functionality has been overwritten for hook instructions
    if (DPFLightMode(*DPFLightModeEnum_b) == DPFLightMode::on_steady)
    {
        *DPFLight_bool = true;
        return;
    }

    // at this point DPFLightMode == DPFLightMode::off
    if (!*DPF_Regeneration_bool_SSM)
    {
        *DPFLight_bool = false;
        return;
    }

    // at this point active DPF regeneration is ON, do custom flashing
    // reusing DPF light counter var is safe
    auto counter = *DPFLightCounter_b;
    if (++counter >= dpfLightCustomPattern.size())
        counter = 0;
    *DPFLightCounter_b = counter;
    *DPFLight_bool = dpfLightCustomPattern.at(counter);
}



// Copyright SubaruDieselCrew (2011-2017)   https://subdiesel.wordpress.com
/*
	For stock ROM:
	Model	2009/2010 Impreza Turbo Diesel 2.0 6MT EDM 110 kW / 150 PS
	ROMID	6644D87207
	CID		JZ2F401A
	CVN		F5AD7142 FB841734
	PAK		22611AP283
*/

#ifndef JZ2F401A_H
#define JZ2F401A_H

#include "diesel_rom.h"

// RAM vars
static auto const DPFLight_bool = reinterpret_cast<volatile bool*>(0xFFFF9C1E);
static auto const DPFLightModeEnum_b = reinterpret_cast<volatile int8_t*>(0xFFFF9C1F);
static auto const DPFLightCounter_b = reinterpret_cast<volatile uint8_t*>(0xFFFF9C53);
static auto const DPF_Regeneration_bool_SSM = reinterpret_cast<volatile bool*>(0xFFFFB222);
…


SuperH disassembly using objdump which is part of GNU binutils. Binary had been generated by GCC.


void calc_DPFLight_continue()
c:  91 1d   mov.w   0x4a,r1 ! 9c1f
e:  60 10   mov.b   @r1,r0
10:  88 01   cmp/eq  #1,r0
12:  8d 17   bt.s    0x44
14:  71 ff   add     #-1,r1
16:  91 19   mov.w   0x4c,r1 ! b222
18:  61 10   mov.b   @r1,r1
1a:  21 18   tst     r1,r1
1c:  8d 0e   bt.s    0x3c
1e:  62 13   mov     r1,r2
20:  91 15   mov.w   0x4e,r1 ! 9c53
22:  e2 0f   mov     #15,r2
24:  61 10   mov.b   @r1,r1
26:  71 01   add     #1,r1
28:  61 1c   extu.b  r1,r1
2a:  31 26   cmp/hi  r2,r1
2c:  8f 02   bf.s    0x34
2e:  60 13   mov     r1,r0
30:  e0 00   mov     #0,r0
32:  e1 00   mov     #0,r1
34:  92 0b   mov.w   0x4e,r2 ! 9c53
36:  22 10   mov.b   r1,@r2
38:  d1 06   mov.l   0x54,r1 ! 945c0
3a:  02 1c   mov.b   @(r0,r1),r2
3c:  91 08   mov.w   0x50,r1 ! 9c1e
3e:  21 20   mov.b   r2,@r1
40:  00 0b   rts
42:  00 09   nop
44:  21 00   mov.b   r0,@r1
46:  00 0b   rts
48:  00 09   nop
4a:  9c 1f
4c:  b2 22
4e:  9c 53
50:  9c 1e
54:  00 09
56:  45 c0

5c0:  01 01   .word 0x0101
5c2:  00 00   .word 0x0000
5c4:  01 01   .word 0x0101
5c6:  00 00   .word 0x0000
5c8:  00 00   .word 0x0000
5ca:  00 00   .word 0x0000
5cc:  00 00   .word 0x0000
5ce:  00 00   .word 0x0000


As you can tell there is not much code required. Much more work, orders of magnitude (!), is necessary to reverse-engineer the related stock ROM portions in the first place, defining functions, disassembling machine instructions, naming local and global variables etc.

Usually, ROM and RAM addresses depend on the actual ROM version used. Above definitions work for outdated CID JZ2F401A (dated 2009-Sep). Apart from ROM specific variable addresses the same code will work for all known Euro 4/5/6 models.

Resultant binary data is meant to be inserted into a free unused ROM region. On Renesas SH microprocessors (i.e. SH7058S) free ROM space is rather easy to find – just look for big chunks of continuous FF-bytes. This is because those chips erase bytes to value 0xFF. Others, e.g. Infineon TriCore series, erase their internal flash ROM to zeroes instead.

To actually make use of the added logic, I had to modify (patch) a few bytes in the original calc_DPF_light subroutine, so that after doing some of its work it will call my own function, knowing its start address (0x9400C). Usually there is no free space in between stock functions, therefore we have to apply clever patching tricks to make room for a few new instructions and/or divert execution flow.

Finally, after carefully verifying the changes applied to the stock ROM, you have to correct checksums. Flashing software usually does this anyway, perhaps asking first. Checksum correction and verification is actually very easy to do for such Denso firmware.

Providing SDC-modified ROMs is possible, however will not be free due to amount of labour involved. Contact us if you’re interested.

Updates

  • 2017-04: minor update, actual disassembly
  • 2016-11: updated source to C++14
  • 2016-10: updated source to C++11 with Doxygen documentation
  • 2016-01: added disassembly