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-2016)	https://subdiesel.wordpress.com
// c++14

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

using namespace std::chrono;

 * @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)
constexpr auto interval {50ms};
 * @brief stock period for flashing mode is 800 ms,
 *        does not have to match stock here
constexpr auto dpfLightPeriod {800ms};
 * @brief defines DPF light output over time when active regeneration is on
constexpr std::array<bool, dpfLightPeriod / interval> dpfLightCustomPattern
{   {   1, 1, 0, 0,  1, 1, 0, 0,
        0, 0, 0, 0,  0, 0, 0, 0

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

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

    // 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 (c) 2011 SubaruDieselCrew

	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);

Disassembly using objdump which is part of GNU binutils. Binary had been generated by GCC. I added quite verbose comments for those who don’t know SH (SuperH) disassembly well enough.

void calc_DPFLight_continue()
  c: 91 1b  mov.w   0x46,r1 ! 9c1f   // r1 = &DPFLightModeEnum_b = 0xFFFF9C1F (sign extension of value 0x9c1f)
  e: 60 10  mov.b   @r1,r0           // r0 = DPFLightModeEnum_b
 10: 88 01  cmp/eq  #1,r0            // DPFLightModeEnum_b == ONsteady ?
 12: 8f 02  bf.s    0x1a             // if not --> jump
 14: 71 ff  add     #-1,r1           // r1 = 0xFFFF9C1E = &DPFLight_bool
 16: 00 0b  rts                      // return (after following instruction)
 18: 21 00  mov.b   r0,@r1           // DPFLight_bool = true

 1a: 92 15  mov.w   0x48,r2 ! b222   // r2 = &DPF_Regeneration_bool_SSM = 0xFFFFB222
 1c: 62 20  mov.b   @r2,r2           // r2 = DPF_Regeneration_bool_SSM
 1e: 63 2c  extu.b  r2,r3            // r3 = (uint8)DPF_Regeneration_bool_SSM
 20: 23 38  tst     r3,r3            // DPF_Regeneration_bool_SSM == false ?
 22: 8d 0e  bt.s    0x42             // is false --> DPFLight_bool = false; return
 24: 00 09  nop
 26: 91 10  mov.w   0x4a,r1 ! 9c53   // r1 = &DPFLightCounter_b = 0xFFFF9C53
 28: e2 0e  mov     #14,r2           // r2 = CounterMax - 2 = 14
 2a: 60 10  mov.b   @r1,r0           // r0 = DPFLightCounter_b
 2c: 60 0c  extu.b  r0,r0            // r0 = counter = (uint8)DPFLightCounter_b
 2e: 30 26  cmp/hi  r2,r0            // counter > 14 ?
 30: 8d 02  bt.s    0x38             // if yes --> DPFLightCounter_b = 0
 32: 70 01  add     #1,r0            // ++counter
 34: a0 01  bra     0x3a             // --> DPFLightCounter_b = counter
 36: 60 0c  extu.b  r0,r0
 38: e0 00  mov     #0,r0            // r0 = 0
 3a: 21 00  mov.b   r0,@r1           // DPFLightCounter_b = r0
 3c: d1 04  mov.l   0x50,r1 ! 943c0  // r1 = &table[0]
 3e: 02 1c  mov.b   @(r0,r1),r2      // r2 = table[counter]
 40: 91 04  mov.w   0x4c,r1 ! 9c1e
 42: 00 0b  rts
 44: 21 20  mov.b   r2,@r1           // DPFLight_bool = r2; return
 46: 9c 1f                  // 0x9c1f
 48: b2 22                  // 0xb222
 4a: 9c 53                  // 0x9c53
 4c: 9c 1e                  // 0x9c1e
 4e: 00 09  nop
 50: 00 09                  // 0x943c0 --> blob position 3c0
 52: 43 c0

3c0: 01 01  .word 0x0101    // table[16] = { 1, 1, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 };
3c2: 00 00  .word 0x0000
3c4: 01 01  .word 0x0101
3c6: 00 00  .word 0x0000
3c8: 00 00  .word 0x0000
3ca: 00 00  .word 0x0000
3cc: 00 00  .word 0x0000
3ce: 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). Same code should also work for all current Euro 4, 5 and 6 models.

Compiled binary generated from above source code is 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.


  • 2016-11: updated source to C++14
  • 2016-10: updated source to C++11 with Doxygen documentation
  • 2016-01: added disassembly

6 responses to “DPF Light Patch

  1. I think I can see what you’re doing here. Is the address 0xFFFF9C1E accessible via the SSM2 protocol as well? (I can’t find your post where you explained the protocol and what addresses can be read/written via SSM2). Reason for asking: would it possible to flash the light ‘externally’, so without changing the ROM, but by monitoring CAN and then flicking the byte with SSM2 from a connected Arduino or Raspi-like device?


    • DPFLight_bool (0xFFFF9C1E) should be readable and writeable using SSM2 via Serial on Euro 4, certainly not writeable using (unpatched) SSM2 via CAN, though. IIRC this address does not lie within censored read-address ranges.
      However, just using SSM2 (“Write single address” command would work) won’t have the desired effect. Remember, stock subroutine will calculate this variable every 50 ms. You would need proper timing so that you overwrite this var after ROM functions calc_DPF_light and before create_CAN_frame_600 are being executed. I never tried this, you might get the light to flicker noticeably, certainly not ideal.
      A feasible non-flash method would be to translate CAN between ECU and the rest of the bus, inserting your own device, passing/modifying CAN frames as needed on the fly. Some advanced tuning boxes (piggyback devices like e.g. JB4 for BMW) do this. This method is also being used in conversion projects (e.g. getting non-Subaru engine with its foreign ECU to work with rest of Subaru car).


      • Right. Didn’t think about the ECU updating this address which it of course has to. Probably much more straightforward to read CAN and flash a light on whatever box I will install in the distant future.


  2. Another question if I may while I’m at it: whenever I search for hints on disassembling ROMs I read about IDApro. Now, I don’t run Windows in any form and found gdb is capable of disassembling Renesas SH assembly. Is this a viable route?
    The hints for IDApro seemed to suggest I need to know certain offsets and jumps through the ROM. While gdb will output instructions just fine, is there a way of telling I’m looking at real asm code and not just garbage?
    (From my above post you can tell I’d only like to read and poke around a little bit, I don’t intend to make changes)

    Thanks for any hints!


    • I had used objdump for quick and small disassembly, e.g. verifying generated small blobs from above code – faster than loading it into IDA.

      Make sure your objdump version has SuperH instructions enabled:
      objdump --info | grep "sh"
      Otherwise you need to get a special version (cross-compiled, perhaps called sh-elf-objdump), either via Linux package or compile it for yourself from source. Same for gcc compiler.

      I let gcc create both .elf and .bin, used these targets in my Makefile:

      $(OBJDUMP) -D $(OUTPUT_ELF) -m sh2e -D | less

      $(OBJDUMP) -D $(OUTPUT_BIN) -b binary -m sh2e -D | less

      IMHO for actual reverse-engineering, anything other than rather small isolated portions, just printing disassembly and/or using gdb is way too painful. Such a 1 MB ROM might exceed ½ km worth of disassembly if you would send the whole output to a printer. IDA Pro is not just about displaying disassembly, it is a whole database, connecting variables, basically cross references, letting you define functions, names, structs, enums etc. Without all that, it will be much much more work, especially since you are going to change assumptions on unknown stuff in the process. Therefore IDA is worth the money. Pro version is needed for SH and other ECU microprocessors unfortunately. For serious (commercial) work, you can also write your own IDA plugins (C++, Python, IDC scripting, …), i.e. to improve auto-detection, auto-define known stuff etc. – saving yourself tedious repetitive work.


      • Thanks for your comments. objdump is of course what I meant in the first place. Not sure why I keep saying gdb. And yes, I grabbed the source and compiled it for all possible architectures to cover SuperH.

        Didn’t realise all those features from IDA. Sounds like I might as well give up right here – not quite the asm guru anyway and now seeing just how naive my idea was.



Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s