Skip to content

About

Reverse engineering of Ford Ecoboost 1.6 firmware

Resources

Stars

2 stars

Watchers

1 watching

Forks

Latest commit

 

History

5 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Cooling-Fan Calibration Maps — Default DV4A Firmware

Scope and source integrity

This report documents the four confirmed cooling-fan map objects in the default/original firmware image:

Image:      DV4A-14C204-SB.BIN
SHA-256:   da37907baf7c2c4086c48b4249458a6f36c8f98a7a4b3ce66682270e521d350a
Size:       4 MiB
Architecture: Infineon TriCore, little-endian
Image base: 0x80000000

Address conversion for the raw image:

file offset = address - 0x80000000

The analysis is static. No ECU communication or flashing was performed.

Unit conversions

Conversions shown in this report are presentation-only; firmware float values remain unchanged.

°C   = (°F - 32) × 5 / 9
km/h = mph × 1.609344

Temperature values used by the maps

Firmware value Metric conversion
24°F −4.44°C
70°F 21.11°C
80°F 26.67°C
90°F 32.22°C
100°F 37.78°C
110°F 43.33°C
205°F 96.11°C
210°F 98.89°C
215°F 101.67°C
220°F 104.44°C
225°F 107.22°C
230°F 110.00°C

Vehicle-speed values used by the maps

Firmware value Metric conversion
3 mph 4.83 km/h
10 mph 16.09 km/h
30 mph 48.28 km/h
40 mph 64.37 km/h
60 mph 96.56 km/h

Map inventory

Ghidra label Address File offset Dimensions Interpreted purpose
FAN_AC_PRESSURE_AMBIENT_MAP 0x80173C68 0x173C68 6 × 6 Base fan demand from A/C pressure and ambient temperature
FAN_AC_SPEED_CORRECTION_MAP 0x80173B8C 0x173B8C 5 × 5 Vehicle-speed reduction subtracted from A/C-derived fan demand
FAN_COOLANT_TEMP_AMBIENT_MAP 0x80173F0C 0x173F0C 6 × 6 Base fan demand from coolant and ambient temperatures
FAN_COOLANT_SPEED_CORRECTION_MAP 0x80173E68 0x173E68 5 × 5 Vehicle-speed reduction subtracted from coolant-derived fan demand

All four objects are typed in the persistent Ghidra project as FanMap2DDescriptor. Their axes use FanAxisPoint entries, and the matrices are row-major float arrays.


1. A/C pressure and ambient-temperature demand

Ghidra label: FAN_AC_PRESSURE_AMBIENT_MAP
Address:      0x80173C68
File offset:  0x173C68
Dimensions:   6 × 6
Output:       fan demand, percent

Axes

  • X: pressure-like values 60, 200, 250, 275, 325, 341
  • Y: ambient temperature:
    • 24°F (−4.44°C)
    • 70°F (21.11°C)
    • 80°F (26.67°C)
    • 90°F (32.22°C)
    • 100°F (37.78°C)
    • 110°F (43.33°C)

The X values are strongly characteristic of A/C high-side pressure, probably in psi, but the exact engineering unit is inferred because no Ford/Bosch symbol file is available.

Default matrix — fan demand (%)

Ambient temperature 60 200 250 275 325 341
24°F (−4.44°C) 21 21 71 71 81 100
70°F (21.11°C) 21 40 71 71 81 100
80°F (26.67°C) 21 45 71 71 81 100
90°F (32.22°C) 21 69 75 81 81 100
100°F (37.78°C) 65 71 90 90 100 100
110°F (43.33°C) 82 100 100 100 100 100

3-D visualization

The plot uses ambient temperature in °C. The footer preserves the original Fahrenheit breakpoints.

A/C pressure and ambient fan-demand 3-D map

Interpretation: fan demand generally rises with both the pressure-like input and ambient temperature. The map reaches 100% across much of the high-pressure/high-ambient region.


2. A/C vehicle-speed correction

Ghidra label: FAN_AC_SPEED_CORRECTION_MAP
Address:      0x80173B8C
File offset:  0x173B8C
Dimensions:   5 × 5
Output:       correction, percent

Axes

  • X: vehicle speed:
    • 3 mph (4.83 km/h)
    • 10 mph (16.09 km/h)
    • 30 mph (48.28 km/h)
    • 40 mph (64.37 km/h)
    • 60 mph (96.56 km/h)
  • Y: A/C fan request: 20, 30, 50, 60, 100%

The vehicle-speed interpretation has medium-high confidence based on values and data flow from candidate RAM 0xD00103A0.

Default matrix — correction (%)

A/C fan request 3 mph (4.83 km/h) 10 mph (16.09 km/h) 30 mph (48.28 km/h) 40 mph (64.37 km/h) 60 mph (96.56 km/h)
20% 0 0 0 15 20
30% 0 0 0 20 30
50% 0 0 0 30 35
60% 0 0 0 5 45
100% 0 0 0 0 6

3. Coolant and ambient-temperature demand

Ghidra label: FAN_COOLANT_TEMP_AMBIENT_MAP
Address:      0x80173F0C
File offset:  0x173F0C
Dimensions:   6 × 6
Output:       fan demand, percent

Axes

  • X: engine coolant temperature:
    • 205°F (96.11°C)
    • 210°F (98.89°C)
    • 215°F (101.67°C)
    • 220°F (104.44°C)
    • 225°F (107.22°C)
    • 230°F (110.00°C)
  • Y: ambient temperature:
    • 24°F (−4.44°C)
    • 70°F (21.11°C)
    • 80°F (26.67°C)
    • 90°F (32.22°C)
    • 100°F (37.78°C)
    • 110°F (43.33°C)

The temperature interpretation has high confidence from the breakpoints, data flow, and resulting percentage demand.

Default matrix — fan demand (%)

Ambient temperature 205°F (96.11°C) 210°F (98.89°C) 215°F (101.67°C) 220°F (104.44°C) 225°F (107.22°C) 230°F (110.00°C)
24°F (−4.44°C) 0 9 12 20 60 100
70°F (21.11°C) 0 10 15 20 60 100
80°F (26.67°C) 0 10 15 25 70 100
90°F (32.22°C) 0 10 15 30 80 100
100°F (37.78°C) 0 10 15 30 80 100
110°F (43.33°C) 0 10 15 30 80 100

3-D visualization

Both temperature axes use °C. The footer preserves the original Fahrenheit breakpoints.

Coolant and ambient fan-demand 3-D map

Interpretation: demand begins near zero at 205°F (96.11°C), rises gradually through 220°F (104.44°C), then rises sharply to 60–80% at 225°F (107.22°C) and 100% at 230°F (110.00°C).


4. Coolant vehicle-speed correction

Ghidra label: FAN_COOLANT_SPEED_CORRECTION_MAP
Address:      0x80173E68
File offset:  0x173E68
Dimensions:   5 × 5
Output:       correction, percent

Axes

  • X: vehicle speed:
    • 3 mph (4.83 km/h)
    • 10 mph (16.09 km/h)
    • 30 mph (48.28 km/h)
    • 40 mph (64.37 km/h)
    • 60 mph (96.56 km/h)
  • Y: coolant fan request: 10, 11, 30, 50, 100%

Default matrix — correction (%)

Coolant fan request 3 mph (4.83 km/h) 10 mph (16.09 km/h) 30 mph (48.28 km/h) 40 mph (64.37 km/h) 60 mph (96.56 km/h)
10% 0 0 0 0 0
11% 0 0 3 11 11
30% 0 0 0 5 15
50% 0 0 0 0 0
100% 0 0 0 0 0

Related default calibration

Address:      0x80174720
File offset:  0x174720
Ghidra label: FAN_MIN_ACTIVE_COMMAND_PCT
Type:         float
Value:        7.5%

This scalar is the default firmware's minimum active fan command.

Confirmed data flow

A/C-derived request

0x8027B7A2  Fan_CalcAcPressureRequest
    inputs: candidate A/C pressure at 0xD000288C
            candidate ambient temperature at 0xD0016460
    map:    0x80173C68  FAN_AC_PRESSURE_AMBIENT_MAP
    helper: 0x8025819C  interpolation
    output: 0xD000C948

The A/C speed-correction map at 0x80173B8C participates in the A/C request path.

Coolant-derived request

0x8027BD08  Fan_CalcCoolantRequest
    inputs: candidate coolant temperature at 0xD0002A2C
            candidate ambient temperature at 0xD0016460
            candidate vehicle speed at 0xD00103A0
    maps:   0x80173F0C  FAN_COOLANT_TEMP_AMBIENT_MAP
            0x80173E68  FAN_COOLANT_SPEED_CORRECTION_MAP
    helper: 0x8025819C  interpolation
    output: 0xD000C940

Arbitration and diagnostic override

0x8027BEB0  Fan_UpdateAllNormalRequests
0x8027C5AA  Fan_ArbitrateNormalRequests
0x8027CA7C  final normal/failsafe request write to 0xD000C944
0x80234E64  Fan_ApplyDiagOverrideAndConditionOutput

The final physical transport after output conditioning—direct PWM/peripheral or a fan-control-module message—has not yet been conclusively identified.

Confidence and limitations

Confirmed

  • Map addresses, dimensions, descriptors, axis arrays, and row-major float matrices.
  • Coolant and ambient temperature breakpoint values.
  • Vehicle-speed-like breakpoint values.
  • Percentage output values.
  • Integration of the maps into A/C and coolant fan-demand calculations.
  • Normal request arbitration into RAM 0xD000C944.
  • Default minimum active command of 7.5%.

Inferred physical meanings

  • 0xD000288C as filtered A/C high-side pressure: high confidence.
  • Pressure-axis engineering unit as psi: high confidence, not symbol-confirmed.
  • 0xD0002A2C as engine coolant temperature in °F: high confidence.
  • 0xD0016460 as ambient-air temperature in °F: high confidence.
  • 0xD00103A0 as vehicle speed in mph: medium-high confidence.

Firmware integrity checksums (IMPORTANT before flashing)

This ECU protects the calibration with THREE independent integrity layers. A modified image must satisfy all three, or the PCM boots, runs for a few minutes, then crashes when its background integrity monitor runs. This was discovered the hard way over two failed VBF flashes; a PCM-Flash-style binary flasher that "updates CRC" fixes all three, which is why binary flashing worked while the VBF (missing a layer) did not.

Correct fix order (do NOT reorder): Layer 3 → Layer 1 → Layer 2. The fix_all_checksums.py wrapper runs them in order and verifies all three.

Layer 1 — Bosch block checksums (medc17)

Standard MED17 CRC32 / ADD32 / ADD16 block checksums plus the Dataset #0 RSA/signature adjustment region at 0x1FFF78–0x1FFFFB. Corrected by medc17_checksum_tool.py (reports 11/11 valid). WinOLS and most tools handle this layer. Its adjust region is OUTSIDE every Layer-3 sub-block and outside the Layer-2 region, so running it second is safe.

Layer 2 — 16-bit calibration "balance / monitoring" checksum (hidden)

A 16-bit balance word that medc17/WinOLS do NOT compute (PCM-Flash-style flashers do). Reverse-engineered by differencing two OEM datasets identical except in Dataset #0 calibration (DV4A-14C204-SB.VBF vs -JE.VBF); both OEM balance words reproduce exactly.

Region summed : 0x140000 – 0x1FFE60   (exclusive end; Dataset #0 calibration payload)
Method        : sum of 16-bit little-endian words
Balance word  : low u16 at file offset 0x3FFC84
                (high u16 stays 0xFFFF; lives in the AppSW#1 DEADBEEF identity trailer)
Invariant     : sum16(region) + balance  ≡  0xADC5   (mod 0x10000)
Correction    : balance = (0xADC5 - sum16(region)) & 0xFFFF

OEM cross-check: SB stored 0x0091 (sum 0xAD34), JE stored 0xE787 (sum 0xC63E), both → 0xADC5. Tool: fix_layer2_checksum.py.

Layer 3 — Dataset #0 per-sub-block sum16 verification table (hidden)

Found by sniffing an actual working binary flash and diffing the three flashed regions against our BIN: the flasher had changed 14 bytes we never touched. It is a table of per-64 KiB-sub-block checksums covering all of Dataset #0. medc17 and the Layer-2 tool both miss it. A stale entry passes boot but trips the background monitor → delayed crash (this was the true root cause of both VBF crashes; Layer 2 alone was necessary but not sufficient).

Table         : file 0x1C8E20 (ECU 0x801C8E20), 13 entries x 16 bytes
Entry         : [start u32 LE][end u32 LE][sum u32 LE][~sum u32 LE]
Method (sum)  : 32-bit sum of 16-bit little-endian words over [start,end] inclusive
Self-check    : sum XOR ~sum == 0xFFFFFFFF   (all 13 entries)
Coverage      : DS0 0x80140078 – 0x801FFF6F in 64 KiB sub-blocks
Self-covering : the table sits inside the 0x801C0000 sub-block, so recomputing an
                entry changes a covered block -> iterate to a fixpoint (the tool does)
Correction    : sum = sum16words(sub-block); ~sum = sum ^ 0xFFFFFFFF

Validated: all 13 entries of the sniffed working image reproduce exactly. Our crashing file had 2 stale entries (the sub-blocks holding the output floor 0x80157C68 and the fan maps 0x80173xxx). Tool: fix_layer3_checksum.py.

General Bosch note (Layers 2 & 3): a block/sub-block closes with a balance/sum word (before a DEADBEEF marker for the block trailers) so its word-sum hits a fixed constant. Confirmed publicly (VietDiesel / TuningHost: "WinOLS and many flashers do not calculate such a checksum for the monitoring area; PCM Flash does").

Corrected artifacts and scripts

fix_all_checksums.py          Run all 3 layers in order (L3->L1->L2) + verify. USE THIS.
fix_layer3_checksum.py        Check/fix Layer-3 DS0 sub-block sum16 table
fix_layer2_checksum.py        Check/fix Layer-2 balance word; writes <name>.crc.<ext>
medc17_checksum_tool.py       Layer-1 analyze/correct (--correct -o / --json)
build_crc_vbf.py              Rebuild the full VBF from a fully-corrected BIN
fix_balance_checksum.py       (legacy) one-off Layer-2 fix for fan-v2

DV4A-14C204-SB.fan-v2.crc.BIN
  SHA-256: 5a5c05c6acc82535786c3d61c6df438055bc059620a9509021df2cb21888ed33
  Layer 1: 11/11    Layer 2: balance 0xBA67    Layer 3: 13/13 sub-block sums valid
  Byte-matches the sniffed working flash in DS0 except medc17's own signature-adjust
  region (both individually valid).
DV4A-14C204-SB.fan-v2.crc.VBF             21-block, file_crc32=0x7D32D756

DV4A-14C204-SB.fan-map.crc.BIN            fan maps only, A/C map left stock; all 3 layers valid
  SHA-256: 1594a81fcd95bf95895f99e7fa31b13d262ee141c05c48808e99c1b097c4000e
DV4A-14C204-SB.fan-map.crc.VBF            21-block, file_crc32=0x9A5A37FD

Do not reuse any earlier VBF/BIN that predates the Layer-3 fix — they carry stale sub-block sums and/or a stale balance word and caused the delayed crashes. Flash a current *.crc.* / *.full.VBF package (all three layers valid), and prefer the full 21-block layout.

About

Reverse engineering of Ford Ecoboost 1.6 firmware

Resources

Stars

2 stars

Watchers

1 watching

Forks

Releases

Packages

Contributors

Languages