Page 1 of 1

Disassemble of type 2 cluster

Posted: 26 Apr 2022, 09:29
by Go4IT
I've started to disassemble a Mondeo MK4 type 2 cluster with the big red LCD screen. It is completely different from the Convers (Type 3). It is from the FL (BS7T) and seem equal to CS7T.

I've started by updating the firmware to the latest version, readout the EEPROM (which is a SPI flash memory of 256kb) and find out how it id connected to the MCU.

The MCU is a MC9S12, so a HCS12X family, like in many other modules (BCM, PAM, ...)

I got the firmware parts from the update download and noe try to readout the actual MCU flash contents using BDM.

My goal is to disassemble the firmware to find out how it works, just for fun and education :-)

I'll put my findings of hardware here https://mk4-wiki.denkdose.de/artikel/ip ... 0849/start

Re: Disassemble of type 2 cluster

Posted: 26 Apr 2022, 20:03
by Go4IT
I found BDM header pads on the board:
ipc_type-2_bdm-port.png
I then looked for the MODA and MODB pins, because they decide how the MCU is run after reset. The pin descriptions where missing in the official datasheet but i could find them reading the docs. Fortunately they are both directly connected to GND, so MCU is starting in "unsecured single chip mode".

It took me a while, but finally i managed to readout the contents of the MCUs "Memory". Because the HCS12 family MCUs uses bank-switching for specific parts of the addressable memory range, this not straight forward like reading "flat" memory locations out of other MCUs. Because the HCS12(X) is only 16 Bit, it can only address 0x0000 - 0xFFFF bytes.
MC9S12_local_memory_map.png
The memory locations from 0x0000 - 0x07FF are reserved for memory mapped device registers. Could be interesting to look at, because some important settings are made here.
To start disassembling the reset-vectors are important. They point us to the entry-point address of the firmware. Those are located at the end of the memory map from 0xFF10 - 0xFFF9.
On reset, the vector at 0xFFFE is used. On COP-watchdog 0xFFFA.

This is, what we find in the dump:

Code: Select all

Offset(h) 00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F

0000FF10  C0 10 C0 13 C0 16 C0 19 C0 1C C0 1F C0 22 C0 25  À.À.À.À.À.À.À"À%
0000FF20  C0 28 C0 2B C0 2E C0 31 C0 34 C0 37 C0 3A C0 3D  À(À+À.À1À4À7À:À=
0000FF30  C0 40 C0 43 C0 46 C0 49 C0 4C C0 4F C0 52 C0 55  À@ÀCÀFÀIÀLÀOÀRÀU
0000FF40  C0 58 C0 5B C0 5E C0 61 C0 64 C0 67 C0 6A C0 6D  ÀXÀ[À^ÀaÀdÀgÀjÀm
0000FF50  C0 70 C0 73 C0 76 C0 79 C0 7C C0 7F C0 82 C0 85  ÀpÀsÀvÀyÀ|À.À‚À…
0000FF60  C0 88 C0 8B C0 8E C0 91 C0 94 C0 97 C0 9A C0 9D  ÀˆÀ‹ÀŽÀ‘À”À—ÀšÀ.
0000FF70  C0 A0 C0 A3 C0 A6 C0 A9 C0 AC C0 AF C0 B2 C0 B5  À À£À¦À©À¬À¯À²Àµ
0000FF80  C0 B8 C0 BB C0 BE C0 C1 C0 C4 C0 C7 C0 CA C0 CD  À¸À»À¾ÀÁÀÄÀÇÀÊÀÍ
0000FF90  C0 D0 C0 D3 C0 D6 C0 D9 C0 DC C0 DF C0 E2 C0 E5  ÀÐÀÓÀÖÀÙÀÜÀßÀâÀå
0000FFA0  C0 E8 C0 EB C0 EE C0 F1 C0 F4 C0 F7 C0 FA C0 FD  ÀèÀëÀîÀñÀôÀ÷ÀúÀý
0000FFB0  C1 00 C1 03 C1 06 C1 09 C1 0C C1 0F C1 12 C1 15  Á.Á.Á.Á.Á.Á.Á.Á.
0000FFC0  C1 18 C1 1B C1 1E C1 21 C1 24 C1 27 C1 2A C1 2D  Á.Á.Á.Á!Á$Á'Á*Á-
0000FFD0  C1 30 C1 33 C1 36 C1 39 C1 3C C1 3F C1 42 C1 45  Á0Á3Á6Á9Á<Á?ÁBÁE
0000FFE0  C1 48 C1 4B C1 4E C1 51 C1 54 C1 57 C1 5A C1 5D  ÁHÁKÁNÁQÁTÁWÁZÁ]
0000FFF0  C1 60 C1 63 C1 66 C1 69 C1 6C C1 6F C1 72 D1 C2  Á`ÁcÁfÁiÁlÁoÁrÑÂ
The RESET-Vector points to memory location 0xD1C2 and this is where the fun begins. After loading the dump into IDA with HCS12X support, it can instantly decompile a lot of functions:

Code: Select all

USER_VEC:FFF6                 fdb _Vswi
USER_VEC:FFF8                 fdb _Vtrap              ; Unimplemented instruction trap
USER_VEC:FFFA                 fdb _Vcop               ; COP watchdog reset
USER_VEC:FFFC                 fdb _Vclkmon            ; Clock monitor reset
USER_VEC:FFFE                 fdb _Vreset             ; System reset or illegal access reset
USER_VEC:FFFE
USER_VEC:FFFE                 END ;
The reset-vector leads to the init routine:

Code: Select all

ROM:D1C2 ; =============== S U B R O U T I N E =======================================
ROM:D1C2
ROM:D1C2
ROM:D1C2                 ; public _Vreset
ROM:D1C2 _Vreset:                                ; DATA XREF: USER_VEC:FFFE↓o
ROM:D1C2                 lds     #$3FFF
ROM:D1C5                 movb    #$FD, RPAGE     ; RAM Page Index Register
ROM:D1CA                 movb    #$20, byte_12 ; ' '
...
Because of the paging and the missing data from there, it can't to all now.

Re: Disassemble of type 2 cluster

Posted: 27 Apr 2022, 09:07
by Go4IT
From the docs: "In single-chip modes the MCU has no external bus. All memory accesses and program fetches are internal". As we are in a single-chip mode (selected by the MODA+MODB tied to GND), we only care about internal memory. Yeah, we already know that the only external memory connected to our MCU is the EEPROM (M25P20) and this is via SPI, no bus involved.

Let's take a look at the entry-point code. IDA was so kind to create a sub called "_Vreset" for us:

Code: Select all

ROM:D1C2                 ; public _Vreset
ROM:D1C2 _Vreset:                                ; DATA XREF: USER_VEC:FFFE↓o
ROM:D1C2                 lds     #$3FFF
ROM:D1C5                 movb    #$FD, RPAGE     ; RAM Page Index Register
ROM:D1CA                 movb    #$20, byte_12 ; ' '
ROM:D1CF                 movb    #$FF, PPAGE     ; Program Page Index Register
ROM:D1D4                 ldx     #$CDED
ROM:D1D7                 ldy     2,x+
ROM:D1D9
ROM:D1D9 loc_D1D9:                               ; CODE XREF: _Vreset+25↓j
ROM:D1D9                                         ; _Vreset+39↓j ...
ROM:D1D9                 ldaa    5,x+
ROM:D1DB                 beq     loc_D214
ROM:D1DD                 bpl     loc_D1E1
ROM:D1DF                 leax    2,x
ROM:D1E1
ROM:D1E1 loc_D1E1:                               ; CODE XREF: _Vreset+1B↑j
ROM:D1E1                 bita    #$60 ; '`'
ROM:D1E3                 bne     loc_D1E9
ROM:D1E5                 ldy     -2,x
ROM:D1E7                 bra     loc_D1D9
ROM:D1E9 ; ---------------------------------------------------------------------------
ROM:D1E9
ROM:D1E9 loc_D1E9:                               ; CODE XREF: _Vreset+21↑j
ROM:D1E9                 pshx
ROM:D1EA                 tsta
ROM:D1EB                 bmi     loc_D1FD
ROM:D1ED                 tfr     y, d
ROM:D1EF                 subd    -2,x
ROM:D1F1                 ldx     -4,x
ROM:D1F3
ROM:D1F3 loc_D1F3:                               ; CODE XREF: _Vreset+35↓j
ROM:D1F3                 movb    1,y+, 1,x+
ROM:D1F7                 ibne    d, loc_D1F3
ROM:D1FA                 pulx
ROM:D1FB                 bra     loc_D1D9
ROM:D1FD ; ---------------------------------------------------------------------------
ROM:D1FD
ROM:D1FD loc_D1FD:                               ; CODE XREF: _Vreset+29↑j
ROM:D1FD                 ldd     -2,x
ROM:D1FF                 pshd
ROM:D200                 movb    -5,x, GPAGE     ; Global Page Index Register
ROM:D205                 ldx     -4,x
ROM:D207
ROM:D207 loc_D207:                               ; CODE XREF: _Vreset+4C↓j
ROM:D207                 ldab    1,y+
ROM:D209                 gstab   1,x+
ROM:D20C                 cpy     0,sp
ROM:D20E                 bcs     loc_D207
ROM:D210                 pulx
ROM:D211                 pulx
ROM:D212                 bra     loc_D1D9
ROM:D214 ; ---------------------------------------------------------------------------
ROM:D214
ROM:D214 loc_D214:                               ; CODE XREF: _Vreset+19↑j
ROM:D214                 ldx     #$2002
ROM:D217                 clrb
ROM:D218                 bra     loc_D21C
ROM:D21A ; ---------------------------------------------------------------------------
ROM:D21A
ROM:D21A loc_D21A:                               ; CODE XREF: _Vreset+5D↓j
ROM:D21A                 std     2,x+
ROM:D21C
ROM:D21C loc_D21C:                               ; CODE XREF: _Vreset+56↑j
ROM:D21C                 cpx     #$2002
ROM:D21F                 bcs     loc_D21A
ROM:D221                 movb    #$7F, GPAGE     ; Global Page Index Register
ROM:D226                 jsr     sub_EF89
ROM:D229
ROM:D229 loc_D229:                               ; CODE XREF: _Vreset:loc_D229↓j
ROM:D229                 bra     loc_D229
ROM:D229 ; End of function _Vreset
The Mnemonic "LDS" set's the stack-pointer (SP) to 0x3FFF. The stack is used for data and return-addresses, e.g. when a sub-function is called within the code, the CPU will change the program-counter (PC) to that new address. But before, the addess of the next command is placed (pushed) onto the stack. This way a "RET" command (return from subfunction) can simply pick the address from the stack an set PC to it. So for the nature of stacks, the address denotes the highest possible memory location and the stack grows downwards in address range. Usually there is no lower limit for the stack, so the program itself needs to take care that it does not overlap into other regions. Also such stacks are located in RAM.
In our case, surprise surprise, the SP is set to the highest possible RAM address 0x3FFF (see memory map above).

Next it set's the RAM-PAGE register, which is located at 0x0016 with the immediate value 0xFD. This register holds which part of the 32KB RAM is mapped into the 4KB memory window (page) in the address range 0x1000 - 0x1FFF.

Think of the RPAGE number as an index to choose which part (page) of the available 32KB RAM should be shown (mapped) in the local address space of the CPU.

Again docs help: "The reset value of 0xFD ensures that there is a linear RAM space available between addresses 0x1000 and 0x3FFF out of reset
The fixed 4K page from 0x2000–0x2FFF of RAM is equivalent to page 254 (page number 0xFE).
The fixed 4K page from 0x3000–0x3FFF of RAM is equivalent to page 255 (page number 0xFF)."
Now we have three 4KB RAM pages linear available from 0x1000 up to 0x3FFF, which is 12KB of RAM. Usually the RAM is unitialized, so it can have 0x00 but also any other, arbitrary content out of reset.

The next command is a bit creepy, as it writes the value 0x20 to a reserved resgister located at 0x0012. The docs don't tell anything about it, maybe it's custom?

After that, the Flash page (PPAGE register at 0x0030) is set to 0xFF, which selects the highest Flash location to be shown in the 16KB window from 0x8000-0xBFFF. Page index 0xFD is fixed shown from 0x4000-0x7FFF (below the page window) and 0xFF at 0xC000-0xFEFF (above page window). This is remarkable, as it means that flash content from 0xFF0000 is mirrored at 0xC000 and 0x8000. I head expected page index 0xFE here, as it would give a linear space from flash at 0x4000 up to 0xFFEF... but that may be changed later.

Flash pages can we switched by code writing into the PPAGE register directly, or by some JMP or CALL commands, which can carry a desired page with the target address. Those commands push the current page-index onto the stack and the corresponding return commands restore them later.

"ldx #$CDED"
loads register X (a 16 Bit register) with immediate value 0xCDED. (beware that the hash-symbol "#" in front of a number denote to load the value itself, otherwise it would load the content of a memory location)

"ldy 2,x+"
The adressing mode here is a classical indexing access, like done with pointers in structs of C. The meaning ist to load the two byte content found at memory location where register X is pointing to into register Y. After that operation, the address in register X is incremented by 2, so it points to the next byte following.

The name "loc_D1D9" denotes, that some other parts of the firmware jumps back here.

"ldaa 5,x+"
Load accumulator register "A" (the upper half of register "D") with value found at memory location X is pointing to. X will be incremented by 5 after the operation.

"beq loc_D214"
Jumps to address "loc_D214" if Zero-flag is set. This was done by the former operation if the loaded value of A is "0".

Using the debugger

The USBDM suite brings also a debugger /commandline-interpreter. (usbdmscript.exe). It works like the Segger one. Start it, choose chip-type and interface, connect to device (without any power applied!) and go...

Code: Select all

% settarget hcs12x
USBDM DLL Version = 4.12.1.262
BDM List:
 0 - USBDM-JS16-SWD_SER-0001 : USBDM HCS08,HCS12,CFV1,ARM-SWD BDM
Found 1 devices
:setTarget HCS12
% openbdm
Opening USBDM-JS16-SWD_SER-0001
BDM Version = HW=97, SW=4C

% connect
:connect
BDM status => Ackn, Speed-sync, Vpp-Off, Vdd-External, RSTO=1, No Reset, CFVx-running
Speed = 2009 kHz (3823 ticks, sync=63.7 us)
Ackn, Speed-sync, Vpp-Off, Vdd-External, RSTO=1, No Reset, CFVx-running
%
Now we can simply step each instruction and show it's side effects on memory and registers! :-)
Use "regs" command to show the current register status:

Code: Select all

% regs
regs
 PC =524D,   D =   0,   X =   0,   Y =   0,  SP =   0,
PC = 0x524D : 0x0402 0x0201
Here you see that registers are empty after reset and PC is set to 0x524D for whatever reason. But we don't care, as we will set the PC to a value we got from the reset vector table 0xD1C2 (start of code) using the "wpc" command:

Code: Select all

% wpc 0xD1C2
:wpc 0xD1C2
% regs
regs
 PC =D1C2,   D =   0,   X =   0,   Y =   0,  SP =   0,
PC = 0xD1C2 : 0xCF3F 0xFF18
Now we can comfortably single-step each instruction:

Code: Select all

% step
:step, from PC = 0x0000D1C2 : 0xCF3F 0xFF18, to PC = 0x0000D1C5 : 0x180B 0xFD00,
% regs
regs
 PC =D1C5,   D =   0,   X =   0,   Y =   0,  SP =3FFF,
PC = 0xD1C5 : 0x180B 0xFD00
see as SP was set to 0x3FFF

Code: Select all

% step
:step, from PC = 0x0000D1C5 : 0x180B 0xFD00, to PC = 0x0000D1CA : 0x180B 0x2000,
% regs
regs
 PC =D1CA,   D =   0,   X =   0,   Y =   0,  SP =3FFF,
PC = 0xD1CA : 0x180B 0x2000
% step
:step, from PC = 0x0000D1CA : 0x180B 0x2000, to PC = 0x0000D1CF : 0x180B 0xFF00,
% regs
regs
 PC =D1CF,   D =   0,   X =   0,   Y =   0,  SP =3FFF,
PC = 0xD1CF : 0x180B 0xFF00
% step
:step, from PC = 0x0000D1CF : 0x180B 0xFF00, to PC = 0x0000D1D4 : 0xCECD 0xEDED,
% regs
regs
 PC =D1D4,   D =   0,   X =   0,   Y =   0,  SP =3FFF,
PC = 0xD1D4 : 0xCECD 0xEDED
% step
:step, from PC = 0x0000D1D4 : 0xCECD 0xEDED, to PC = 0x0000D1D7 : 0xED31 0xA634,
% regs
regs
 PC =D1D7,   D =   0,   X =CDED,   Y =   0,  SP =3FFF,
PC = 0xD1D7 : 0xED31 0xA634
here, register "X" was loaded with 0xCDED

Code: Select all

% step
:step, from PC = 0x0000D1D7 : 0xED31 0xA634, to PC = 0x0000D1D9 : 0xA634 0x2737,
% regs
regs
 PC =D1D9,   D =   0,   X =CDEF,   Y =CDF7,  SP =3FFF,
PC = 0xD1D9 : 0xA634 0x2737
% step
:step, from PC = 0x0000D1D9 : 0xA634 0x2737, to PC = 0x0000D1DB : 0x2737 0x2A02,
% regs
regs
 PC =D1DB,   D =E400,   X =CDF4,   Y =CDF7,  SP =3FFF,
PC = 0xD1DB : 0x2737 0x2A02
% step
:step, from PC = 0x0000D1DB : 0x2737 0x2A02, to PC = 0x0000D1DD : 0x2A02 0x1A02,
% regs
regs
 PC =D1DD,   D =E400,   X =CDF4,   Y =CDF7,  SP =3FFF,
PC = 0xD1DD : 0x2A02 0x1A02
% step
:step, from PC = 0x0000D1DD : 0x2A02 0x1A02, to PC = 0x0000D1DF : 0x1A02 0x8560,
% regs
regs
 PC =D1DF,   D =E400,   X =CDF4,   Y =CDF7,  SP =3FFF,
PC = 0xD1DF : 0x1A02 0x8560
% step
:step, from PC = 0x0000D1DF : 0x1A02 0x8560, to PC = 0x0000D1E1 : 0x8560 0x2604,
% regs
regs
 PC =D1E1,   D =E400,   X =CDF6,   Y =CDF7,  SP =3FFF,
PC = 0xD1E1 : 0x8560 0x2604
% step
:step, from PC = 0x0000D1E1 : 0x8560 0x2604, to PC = 0x0000D1E3 : 0x2604 0xED1E,
% regs
regs
 PC =D1E3,   D =E400,   X =CDF6,   Y =CDF7,  SP =3FFF,
PC = 0xD1E3 : 0x2604 0xED1E
% step
:step, from PC = 0x0000D1E3 : 0x2604 0xED1E, to PC = 0x0000D1E9 : 0x3497 0x2B10,
% regs
regs
 PC =D1E9,   D =E400,   X =CDF6,   Y =CDF7,  SP =3FFF,
PC = 0xD1E9 : 0x3497 0x2B10
% step
:step, from PC = 0x0000D1E9 : 0x3497 0x2B10, to PC = 0x0000D1EA : 0x972B 0x10B7,
% regs
regs
 PC =D1EA,   D =E400,   X =CDF6,   Y =CDF7,  SP =3FFD,
PC = 0xD1EA : 0x972B 0x10B7
% step
:step, from PC = 0x0000D1EA : 0x972B 0x10B7, to PC = 0x0000D1EB : 0x2B10 0xB764,
% regs
regs
 PC =D1EB,   D =E400,   X =CDF6,   Y =CDF7,  SP =3FFD,
PC = 0xD1EB : 0x2B10 0xB764
% step
:step, from PC = 0x0000D1EB : 0x2B10 0xB764, to PC = 0x0000D1FD : 0xEC1E 0x3B18,
% regs
regs
 PC =D1FD,   D =E400,   X =CDF6,   Y =CDF7,  SP =3FFD,
PC = 0xD1FD : 0xEC1E 0x3B18
%

Re: Disassemble of type 2 cluster

Posted: 27 Apr 2022, 13:31
by Go4IT
I assume that because flash windows 4000-BFFF and C000-FFEF are fixed, that they contain constants and library functions which needed by all subroutines of the code and that code is arranged that way that similar functions are fit in on window.
Remember that software was written in C (or C++) and not in Assembler and it is up to the linker to optimize for this. Usually a programmer is not bothered by this paging.

One thing still puzzles me. The update shows what's get loaded onto the IPC cluster:
updated_new.PNG
When i add 511kb + 263kb + 2kb (rounded) i will end with 776kb of data to be stored. But the MC9S12 has only 512kb of Flash. Where is all that data to be stored?

From the VBF, the main part "BS7T-14C026-BF" is loaded to global address 0x78_0000 up to 0x7F_CC00.
The firmware part "BS7T-14C026-DE" is loaded to global address 0x74_0000 up to 0x77_FFFF, but that address space is not used on our MCU (see memory map above)?!

The "calibration data" is loaded to 0x13_F93A which is in the internal EEPROM area. It is shown in local address 0x093A (internal EEPROM-Size = 4kb = 0x1000, so in global map EEPROM ranges from 0x13_0000 up to 0x13_FFFF. If bootloader stores the calibationdata content at 0x13_F93A. EPAGE was set to 0xFE in reset-sub, so EEPROM-window shows in range 0x0800 - 0x0BFF what is at global space 0x13_F800 - 0x13_FFFF.)

Re: Disassemble of type 2 cluster

Posted: 28 Apr 2022, 17:30
by Go4IT
I forgott that the external EEPROM (in fact it is also a Flash memory, but looks/works like an EEPROM) is very large, compared with them in type-3 dashboards (Convers+). It can take up to 256KByte of data. So i suspect that MCU take the full image of BS7T-14C026-BF (512KB) and BS7T-14C026-DE (256KB) goes into external EEPROM.

Re: Disassemble of type 2 cluster

Posted: 09 May 2022, 20:21
by Go4IT
Today i measured out how the background illumination LEDs are controlled. Electrically there are two LEDs in a series, driven by a Transistor-CCS (constant current source). All those CCS's are driven by a NPN-Transistor which is connected with it's base to PP4 of the MC9S12.
You can simply put +5V at PP4 (Pin 28) and all background LEDs will turn on!
MC9S12_LED-Pins.png
Now i want to learn how to set this port via BDM!

I use "usbdmscript.exe" for this. Start it, connect to BDM and set params:

Code: Select all

openbdm 0
settarget hcs12
connect
reset
Port P "Data Direction Register" (DDRP) is mapped to memory location 0x021A. Here we set Port B pins either as input (bits set to "0") or output (bits set to "1"). Just write using

Code: Select all

wb 0x021A 0xFF
in script commandline and it will turn all Port P pins to output.

To set a pin to HIGH (1) or LOW (0), write to "Port P I/O Register" (PTP) at 0x0218. Writing 0x10 (0b0000_1000 = Bit 4 is set) to Port P I/O register using

Code: Select all

wb 0x0218 0x10
will activate the background illu:
IMG_6738.JPG
Writing 0x20 will activate PP5 and thus enable LCD illu:
IMG_6739.JPG
Of course both can be turned on by writing 0x30 "wb 0x0218 0x30":
IMG_6737.JPG
Then i tested all the other ports and pins but only found one direct LED of the Airbag, driven by Port PT5.

Code: Select all

wb 0x0202 0xFF
to set DDRT to output, followed by

Code: Select all

wb 0x0200 0x20
to enable LED:
IMG_6740.JPG
This can help to find code where those LEDs are turned on/off, e.g. the Airbag.

I expect the other LEDs routed through a separate chip or matrix.

Re: Disassemble of type 2 cluster

Posted: 11 May 2022, 17:46
by Go4IT
Now i took a look into the update-files and a BDM-readout to find where update-data is going.
A module scan tells me what is on that IPC
updated_new.png
So the whole firmware consists of several files, loaded into the MCU internal Flash and maybe also into the external EEPROM (Flash).
The first part is in "CS7T-14C026-BF", is 0x7_CC00 bytes (511 KB) in size and starts at 0x0078_0000 of global address map of MCU. So it occupies the address space from 0x0078_0000 up to 0x007F_CC00.
The second part "CS7T-14C026-DE.VBF" is 0x4_0000 bytes long (262 KB) an should start at 0x0074_000 and ranges up to 0x0077_FFFF, so in fact it is positioned before the first part in global address map.
The third part "CS7T-14C088-BF" is only 0x59E bytes (1,4 KB) and should go to 0x0013_F93A.

Now, let's take a look at memory map of our chip which has 512 KB of internal Flash:
MC9S12XHZ512_memory_map.png
The flash is divided into 16 KB pages, so we have 32 pages. As Flash-memory is top-aligned in address space it will range down from 0x007F_FFFF to 0x0078_0000. Each page is given an Index, also downwards from 0xFF (highest page) to 0xDF. There are two static pages, page index 0xFF is always mapped to 0xC000 of local address space and index 0xFD to 0x4000. The variable part is mapped to 0x8000 - 0xBFFF and represents the selected page index. On RESET it is set to index 0xFE so the mapped pages represents the uppermost 3 pages of the Flash memory.

I did a BDM readout of the local address space 0x0000-0xFFFF and so i expect to find these mapped areas here:
BDM file Update-data file
0x4000 - 0x7FFF <= 0x007F_4000 - 0x007F_7FFF
0xC000 - 0xFFFF <= 0x007F_C000 - 0x007F_FFFF

Code: Select all

00004000  02 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
00004010  00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
00004020  00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
00004030  00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
00004040  00 00 00 00 00 00 00 00 00 00 00 00 03 03 03 03  ................
00004050  03 03 03 03 00 00 00 00 00 00 02 02 00 00 00 02  ................
00004060  00 00 00 00 00 00 00 00 03 00 03 03 02 03 07 00  ................
00004070  07 00 00 02 02 02 02 02 00 00 00 00 08 00 00 00  ................
00004080  0B 80 00 0B 80 00 3C 44 40 7E 00 00 37 92 00 38  .€..€.<D@~..7’.8

007F4000  02 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
007F4010  00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
007F4020  00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
007F4030  00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
007F4040  00 00 00 00 00 00 00 00 00 00 00 00 03 03 03 03  ................
007F4050  03 03 03 03 00 00 00 00 00 00 02 02 00 00 00 02  ................
007F4060  00 00 00 00 00 00 00 00 03 00 03 03 02 03 07 00  ................
007F4070  07 00 00 02 02 02 02 02 00 00 00 00 08 00 00 00  ................
007F4080  0B 80 00 0B 80 00 3C 44 40 7E 00 00 37 92 00 38  .€..€.<D@~..7’.8

Code: Select all

0000C000  3F 3F 3F 3F 3F 3F 3F 3F 3F 3F 3F 3F 3F 3F 3F 3F  ????????????????
0000C010  06 C3 E1 06 C3 E1 06 C3 E1 06 C3 E1 06 C3 E1 06  .Ãá.Ãá.Ãá.Ãá.Ãá.
0000C020  C3 E1 06 C3 E1 06 C3 E1 06 C3 E1 06 C3 E1 06 C3  Ãá.Ãá.Ãá.Ãá.Ãá.Ã
0000C030  E1 06 C3 E1 06 C3 E1 06 C3 E1 06 C3 E1 06 C3 E1  á.Ãá.Ãá.Ãá.Ãá.Ãá

007FC000  3F 3F 3F 3F 3F 3F 3F 3F 3F 3F 3F 3F 3F 3F 3F 3F  ????????????????
007FC010  06 C3 E1 06 C3 E1 06 C3 E1 06 C3 E1 06 C3 E1 06  .Ãá.Ãá.Ãá.Ãá.Ãá.
007FC020  C3 E1 06 C3 E1 06 C3 E1 06 C3 E1 06 C3 E1 06 C3  Ãá.Ãá.Ãá.Ãá.Ãá.Ã
007FC030  E1 06 C3 E1 06 C3 E1 06 C3 E1 06 C3 E1 06 C3 E1  á.Ãá.Ãá.Ãá.Ãá.Ãá
Ok, that fits! :-)

Now i wonder what page index was mapped at the moment i took this snapshot by BDM. The register which tells the MCU memory controller is located at 0x0030 in register-space.

Code: Select all

00000030  FE 00 00 00 00 00 00 3C 00 00 F1 00 00 00 80 00  þ......<..ñ...€.
So it was snapped right after RESET as it points to the default index page 0xFE. This means i should find
0x8000 - 0xBFFF <= 0x007F_8000 - 0x007F_BFFF

Code: Select all

00008000  3F 3F 3F 3F 3F 3F 3F 3F 3F 3F 3F 3F 3F 3F 3F 3F  ????????????????
00008010  3F 3F 3F 3F 3F 3F 3F 3F 3F 3F 3F 3F 3F 3F 3F 3F  ????????????????
00008020  3F 3F 3F 3F 3F 3F 3F 3F 3F 3F 3F 3F 3F 3F 3F 3F  ????????????????
00008030  3F 3F 3F 3F 3F 3F 3F 3F 3F 3F 3F 3F 3F 3F 3F 3F  ????????????????
...
0000BFC0  1D 7F 00 BF 22 7F 00 BF 27 7F 00 BF 2C 7F 00 BF  ...¿"..¿'..¿,..¿
0000BFD0  31 7F 00 00 00 00 00 00 00 00 00 00 00 00 00 00  1...............
0000BFE0  00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
0000BFF0  00 00 00 00 00 00 00 00 00 00 00 BF 36 7F 00 3F  ...........¿6..?

007F8000  3F 3F 3F 3F 3F 3F 3F 3F 3F 3F 3F 3F 3F 3F 3F 3F  ????????????????
007F8010  3F 3F 3F 3F 3F 3F 3F 3F 3F 3F 3F 3F 3F 3F 3F 3F  ????????????????
007F8020  3F 3F 3F 3F 3F 3F 3F 3F 3F 3F 3F 3F 3F 3F 3F 3F  ????????????????
007F8030  3F 3F 3F 3F 3F 3F 3F 3F 3F 3F 3F 3F 3F 3F 3F 3F  ????????????????
...
007FBFC0  1D 7F 00 BF 22 7F 00 BF 27 7F 00 BF 2C 7F 00 BF  ...¿"..¿'..¿,..¿
007FBFD0  31 7F 00 00 00 00 00 00 00 00 00 00 00 00 00 00  1...............
007FBFE0  00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
007FBFF0  00 00 00 00 00 00 00 00 00 00 00 BF 36 7F 00 3F  ...........¿6..?
Yesss, also true :-)

The three RESET-Vectors are located from 0xFFFA - 0xFFFF in local address map. This area was ruled by the uppermost part of the imagedata in page index 0xFF:

Code: Select all

Offset(h) 00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F
0000FFF0  C1 60 C1 63 C1 66 C1 69 C1 6C C1 6F C1 72 D1 C2  Á`ÁcÁfÁiÁlÁoÁrÑÂ
Where the WORD at 0xFFFE is the local address to jump if an external reset through the RESET-pin or a power-on reset was issued.
At 0xFFFC is the target address if an clock monitor reset occured.
And 0xFFFA points to code which get's executed on an COP watchdog issued reset.

As the HCS12 is big endian, the reset address would be 0xD1C2. So now we got the entrypoint of the firmware, hurray! ;-)
Now load this into IDA Pro.

Re: Disassemble of type 2 cluster

Posted: 11 May 2022, 18:16
by Go4IT
IDA Pro knows HCS12X processors. The various kinds and their specialties are put into a file "hcs12x.cfg" which is located unter the "cfg" folder of the IDA Pro program root dir. This file can be edited by a simple text editor (i use notepad++). In there we find a memory-map

Code: Select all

; MEMORY MAP
area DATA FSR         0x0000:0x0400
area DATA EEPROM_     0x0C00:0x1000
area DATA RAM_        0x2000:0x4000
area CODE ROM         0x4000:0xFF00
area DATA USER_VEC    0xFF00:0x10000
And there is also much more other usefull information in there, which tells IDA about the meanings of memory locations. And for shure IDA knows about the reset-vector:

Code: Select all

USER_VEC:FFFE                 fdb _Vreset             ; System reset or illegal access reset
It has created an named location. Doubleklick on the "_Vreset" label or choose it from the left "Functions window" pane and you are there:

Code: Select all

ROM:D1C2                 ; public _Vreset
ROM:D1C2 _Vreset:                                ; DATA XREF: USER_VEC:FFFE↓o
ROM:D1C2                 lds     #$3FFF
ROM:D1C5                 movb    #$FD, RPAGE     ; RAM Page Index Register
ROM:D1CA                 movb    #$20, byte_12 ; ' '
ROM:D1CF                 movb    #$FF, PPAGE     ; Program Page Index Register
ROM:D1D4                 ldx     #$CDED
ROM:D1D7                 ldy     2,x+
ROM:D1D9
ROM:D1D9 loc_D1D9:                               ; CODE XREF: _Vreset+25↓j
Now, the tricky part is to tell IDA that there is much more code than shown here.

Re: Disassemble of type 2 cluster

Posted: 17 May 2022, 16:03
by Go4IT
I tried to understand the first instructions of the firmware, but it is also hard work because there is a lot "overhead" from the operating-system which may be RTOS or something like that. I do also think that it is not possible to "understand" the full firmware, so you need to focus to find "parts of interest".

If, for example, i'm after the milage algorithm, which decodes/encodes the trip/total milage, usually stored in an external device, i start my journey by looking at the electronics. Here i found that there is an external chip carrying the desired data. In this case of the Type 2 cluster it is a M25P20 Flash memory having 256 KB of size and an SPI interface. So the MCU will be the SPI Master and the memory chip the SPI slave.

The memory chip is is electrically connected to the MCU through it's SPI module. The module is connected to Port "S" pins PS4-PS7:
MC9S12XHZ512_LQFP-112_pinout.png
On the board, there is an extra level-converter between both parts to adopt the logical-levels 5V <-> 3.3V:
ext_flash_con.png
The pins are connected to the SPI module inside the MC9S12X MCU:
mc9s12x_spi_ports_schema.png
A closer look at the SPI module shows how it get's controlled by the software through some registers (yellow highlighted):
mc9s12x_spi_interface_block_diagram.png
The relevant register addresses are:
spi_register.png
Those are the one i now try to find inside the firmware. I assume that there are at least two low-level function directly utilizing the "SPI Data Register" ("SPIDR") at 0x00DD to read and write Flash-Commands and Bytes to the memory chip. In my first IDA disassemble from the linear readout 0x0000-0xFFFF i do not see any references to that register address. This is because the code accessing this registers are in a different Flash page, not the one shown by PPAGE register index after reset. So i first need to find out what other pages contains valid code.

One way for the software to switch the page is to directly write into PPAGE register. Another one is to implicitly do this by a branch-command like "call". There is a variant of the S12 call-command which takes a PPAGE index additionally to the target address. I found those by simply making a text-search in IDA for "call". Here only the first results of that search:

Code: Select all

Address	Function	Instruction
ROM:C30E	sub_C2FE	                call    $800B, #$F2
ROM:C353	_Vcop_0	                call    $8180, #$F2
ROM:C35A	_Vcop_0	                call    $8180, #$F2
ROM:C38A	_Vcop_0	                call    $80A8, #$F2
ROM:C38E	_Vcop_0	                call    $99C2, #$F2
ROM:C394	_Vcop_0	                call    $800B, #$F2
...
I then sorted an uniqued the pages and found that the base code utilizes these flash pages:

Code: Select all

$F0
$F2
$F3
$F6
$F7
$F9
$FA
$FF
Next i will create a binary file which contains the base readout with every of the above flash pages mirrored at x8000 and inspect the code.