SD2 analysis and reverse engineering

Published: 2024-03-04

Last updated: 2024-03-09

Overview

The Sistema Diagnosi 2 (or SD2) is an automotive diagnostic tool developed by Digitek under contract to Ferrari in the mid-1990s, and it is designed to interface with ECUs in both Ferrari and Maserati vehicles built around that time. The SD2 consists of Windows-based software (WSDC32) that runs on a PC, a piece of custom hardware in the form of a tablet-like computer called the Tester, and a collection of cables that connect the Tester to one of the dozens of different ECUs that the system supports.

In the interest of preserving the capabilities of the SD2 and making them available to DIY-minded enthusiasts, I decided to reverse-engineer the system and document my findings. This article is an attempt to document the design and function of the SD2 itself, but I am also working on open-source libraries that implement several of the diagnostic protocols used by SD2-supported ECUs. These include Keyword Protocol 71 ("KWP-71"), FIAT-9141, and Marelli 1AF.

Research methodology and notes

The software that was distributed with the SD2 package included a binary file named SD2_SISOP.HEX. This file is an operating system image that provides the core functionality on the Tester. If we look inside it, a number of embedded strings reveal that it's a Wind River VxWorks 5 image, and it includes a symbol table! A careful analysis of the offsets in the symbol table (using Ghidra) allows us to deduce the in-memory offsets for each segment in the image when it is running on the target hardware:

Segment Memory offset File offset Size
.text 0100_0000 0000_0000 0x52DF0
.data 0106_C000 0005_2DF0 0x14464
.bss 0108_0464 n/a 0xE7FC+

The .text segment contains VxWorks system functions, C standard library routines, and some code that is specific to the SD2 Tester. The SD2-specific code includes screen display functions (such as the Ferrari and Maserati startup/splash logos), keypad management, sound buzzer control, and the 5-baud "slow init" sequence that is used by many K-Line diagnostic protocols. Also contained in this segment is the BSP code to support the hardware specific to the SD2 Tester, which is built around a Motorola 68360 QICC processor (using the "CPU32" architecture.)

In addition to the main OS/system image, the SD2 system uses a series of binary files with the .ecu extension. Each of these files provides support for a single ECU's diagnostic protocol, and they are loaded dynamically by the core Tester code in a sort of plug-in architecture. Analysis of the .ecu files shows that they're actually object files in the old and fairly obscure A.out executable format. I found that Ghidra did not have support for the A.out format, so I wrote it myself. It's currently available on the branch identified in this pull request.

The Tester has a couple different types of ports for connecting to an ECU. Two of these ports, labeled "ISO1" and "ISO2", use the common half-duplex 12V/0V signaling scheme that is associated with the ISO9141 standard. These ISO connections use a signal line (the "K-line") and a GND. Sometimes -- depending on the ECU -- a second signal line called the "L-line" is used for initialization. Many of the in-car connectors are a three-pin design that was common across FIAT, Alfa, and Lancia products at the time, and the three pins map to K, L, and GND.

The code on the Tester uses ioctl() calls to the serial port devices to configure the ports, flush buffers, etc. Many of the ioctl functions are known standard values, such as 3 (FIOOPTIONS), 4 (FIOBAUDRATE), and 0x1A (FIORFLUSH). However, the code also contains many uses of 0x50, 0x51, 0x54, and other values. These are higher values than any standard ioctl functions of which I'm aware, so I suspected they are special serial/tty functions used by the VxWorks serial driver. The QICC has integrated serial port controllers, and I was able to match up register addresses given in the QICC datasheet with registers that are read/written within the SD2 serial driver code for the mystery ioctl functions. (The driver code in the SD2 image is in a group of functions named _scc*, and _sccIoctl does indeed handle ioctl functions 0x50, 0x51, 0x54 in addition to the standard ones like FIOBAUDRATE, etc.) I was able to determine that ioctls 0x50 and 0x51 are used to disable and enable the serial port's receive capability, respectively. According to documentation, this also flushes anything in the FIFO. See the QICC User Manual.

Tester's X25043 EEPROM memory map

One of the hardware components in the Tester is a Xicor X25043, which combines a watchdog timer and EEPROM storage in a single package. The EEPROM is used as nonvolatile storage for several pieces of information:

Byte address Data
00-01 Tester serial number
02 Digital potentiometer (DS1803) value for display contrast
03 Digital potentiometer (DS1803) value for display luminance
04 Buzzer state (on/off)
05 Selected language
06 Communication type to PC (00 == RS232, 01 == CAN)
07-08 16-bit BE value, related to CAN configuration?
09-0A 16-bit BE value, related to CAN configuration?
0B Workshop data number. Read and stored in _numDatiOfficina. Capped at 0x10.
98-9B Key to set either Ferrari mode (41 C6 16 7E) or Maserati mode (27 81 44 6B)
A0-AF Info about flash storage (num blocks free, etc.)
C0-C6 Date/time

Naming convention for ECU support files

Each ECU supported by SD2 is internally given a unique ID string that is used to label files and directories. The strings are eight characters long and in the format MDDD####, where:

  • M is a single-character representation of the manufacturer (e.g. B for Bosch)
  • DDD is a three-character abbreviation of the type of ECU. Sometimes this is from the Italian word, such as ANT for antifurto (anti-theft), and sometimes it is a product name such as MOT for Motronic.
  • #### is a zero-prefixed decimal number. These numbers seem to be globally unique in the list of supported ECUs.

Examples:

  • BMOT0145 for the Bosch Motronic 5.2 as found in the F355
  • BSOS0088 for the Bilstein suspension ECU as found in the 550
  • MCAM0151 for the Marelli gearbox ECU as found in the 355 F1

WSDC32

The software that runs on the Windows PC is named WSDC32, and it consists of a couple parts:

  • A main application that is responsible for loading ECU support files to the Tester.
  • ECU-specific child applications that are launched from the main application. Each of these applications contains the code that sends commands through the Tester to a connected ECU for the purpose of requesting parameters, reading fault codes, etc.

Tester-to-PC serial connection

When WSDC32 and the Tester are connected via an RS232 link, they communicate using a message-based software protocol. Each of these messages has the same header structure, with fields to give the total message length and identify the message type. The messages provide the ability to draw to the Tester's screen, read or write the filesystem in the Tester's flash memory, and start/stop ECU diagnostics.

Fields in message header:

Byte index Field description
00 0x50 for WSDC32-to-Tester commands in Ferrari mode
0x4d for WSDC32-to-Tester commands in Maserati mode
0x54 for Tester-to-WSDC32 responses in Ferrari mode
01-02 16-bit big endian count of bytes in this message (including this field, but not including the prefix byte before it). Although the size of this field implies that this protocol allows messages up to 64K bytes in size, the Tester will only send/receive messages with the high byte fixed to 00, limiting the total message size to 0xff bytes.
03 00 with normal RS232 connection
04 ?
05 Index of VxWorks pipe into which this message should be sent (see below)
06 Message type / function ID
07 Start of payload. Payload content differs per message type. When a simple "success" indicator is needed, the payload is usually a single byte set to 0x01.

The message types sent between the PC and the Tester are divided into several different categories, and each of these categories is handled by a different thread running on the Tester. When a message is received by the Tester, it is directed to the appropriate thread by being placed into one of several VxWorks pipes, as indicated by the byte in the message's header at offset 6 that gives the pipe ID:

Pipe ID Message function category Name of handling thread
00 System status and system configuration; drawing to Tester screen _applModGen
04 ECU-specific request using ISO line 1 _applModGest####, where #### is the SD2 numeric ID of an ECU module
05 ECU-specific request using ISO line 2 _applModGest####, where #### is the SD2 numeric ID of an ECU module
06 ECU-specific request using RS232 _applModGest####, where #### is the SD2 numeric ID of an ECU module
07 ECU-specific request using CAN bus _applModGest####, where #### is the SD2 numeric ID of an ECU module
08 Keypad input processing? _modApplTasti
09 Tester filesystem operations and software loading _modApplScSw
0A Analog-to-digital converter configuration/sampling _modApplAdConvIO

Response messages from Tester

In general, if the Tester only needs to ack a command or send a generic "success" response, the response will be 8 bytes long and take the following form:

54 00 07 xx xx xx xx 01

Note that this ack message preserves the values from the original command (i.e. the request to which it is replying) in positions 03 through 06 (represented as xx above). Byte 00 is updated with the Tester's link-to-PC mode prefix byte (which is 0x54). Bytes 01-02 are updated to contain the new byte count (0x0007, in this case). An eighth byte is added to contain the value 0x01, which indicates success.

When interrogating a connected ECU that speaks a protocol such as KWP71, the Tester essentially acts as a passthrough. KWP71 blocks are sent by WSDC32 by way of SD2 message type 0x13, and the blocks are forwarded on to the ECU. When the ECU responds, the Tester will encapsulate the response with the SD2 serial message header and send it back to WSDC32.

A response that relays KWP-71 data received from the ECU looks like this:

00 01 02 03 04 05 06 07 08 09+
54 KWP payload size + 8 (hi) KWP payload size + 8 (lo) xx xx xx 13 01 KWP payload size KWP payload bytes...

Command messages to Tester

Message type Description
01 Request system diagnostic/config info from Tester. Response (25 bytes total):
01-02: 0x00 0x18
07: System loader major version, e.g. 0x03
08: System loader minor version, e.g. 0x01
09: System loader date (day) in BCD, e.g. 0x25
0A: System loader date (month) in BCD, e.g. 0x09
0B: System loader date (decade) in BCD, e.g. 0x19
0C: System loader date (year) in BCD, e.g. 0x98
0D: OS major version, e.g. 0x05
0E: OS minor version, e.g. 0x05
0F: OS date (day) in BCD, e.g. 0x04
10: OS date (month) in BCD, e.g. 0x03
11: OS date (decade) in BCD, e.g. 0x19
12: OS date (year) in BCD, e.g. 0x99
13-16: Free space on flash storage (big endian)
17-18: Tester serial number
02 Request Tester serial number (read from the the X25043 EEPROM). Total response size: 9 bytes
07 Reset the "workshop data number" to 0 (in the X25043's EEPROM). Standard success response.
08 Write/store the workshop data from the serial packet (starting at byte offset 07). Standard success response.
0A Read the workshop data and respond with it. Response format:
07: 01
08: Value from index 07 in the request message (requested workshop index)
09-76: The 6E bytes of workshop data
0B Start a thread running _applModGest#### for the ECU module specified by the ID number in the payload of this message. Parameter to the thread's entry point is the pipe number, which is given in the 0B command message as well. Standard success response.
11 Init communications with the ECU. For ECUs that require it, this means doing the 5-baud slow init. Address byte is at index 07 in the message. Some modules use a message with another byte at index 8 that indicates the number of bytes expected in the keyword response (e.g. 0x06 for FIAT-9141 or Marelli 1AF).
12 Send the keyword response sequence from the Tester to the PC.
13 Copies a KWP-71 packet from the RS-232 message to the KWP message buffer:
sendMsgBuf[0] = applPipeBuffer[2] - 4;
_bcopy(applPipeBuffer + 7,sendMsgBuf + 2,sendMsgBuf[0] - 2);
sendMsgBuf[sendMsgBuf[0]] = KWP71_END_OF_PKT;
14 Clears the slow-init-complete flag. Standard success response.
15 Display a string on the Tester's display. Standard success response.
16 Enable or disable the Tester's buzzer (depending on the value at position 07). Standard success response.
1C Shuts down _applModGest#### and clears the "running" flag in the global _applRun entry for the associated pipe (e.g. 04 or 05 for ISO1 and ISO2, respectively).
1D Clear the Tester's screen. Standard success response.
1E Close file. Standard success response.
20 Open file for writing. Filename is given in the message payload. Standard success response.
21 Write to the previously opened file. Payload format:
07-0A: Sequence number, starting at 00 00 00 00 for the first buffer to write
0B-(N-1): Buffer of bytes to write to the file, where n is the last byte index in the overall message.
N: 8-bit checksum plus 1, computed over the write buffer
Standard success response.
22 Close file. Standard success response if a checksum check passes (01 in position 07 of the message). If the checksum fails, the response has 02 in position 07 of the message.
23 Open file for reading. Filename is given in the message payload. Standard success response.
24 Read from file. Response:
02: Number of bytes read from the file (not to exceed 0x6E), plus 0xC.
07: 1
08-0B: Sequence num, bytes 07 through 0A from the Read message
0C-(N-1) : Bytes read from file
N: 8-bit checksum of bytes
25 Requests that the Tester respond with a checksum array to confirm the recently transferred file. Response is a total of 0x76 bytes long.
26 Makes a directory with the name provided in the message. Standard success response.
27 Delete/unlink the specified file. Standard success response.
28 Select file to rename. Standard success response.
29 Rename file (selected by message type 28) to new name (provided by this message). Standard success response.
2A Change directory. Standard success response.
2B Read next entry in current directory. Response:
08-11: 32-bit BE identifier copied from request message
12: 01 for directories, 02 for other (e.g. regular files)
13-16: 32-bit BE size
17-37: Date/time in the format JAN-01-1999 00:00:00.
38- : File/directory name. Max length of 89 chars to keep the total message length LTE 0x80.
30 Read ECU description and line configuration information out of an .INI file on disk (for all 64 available ECU slots on the Tester?) Standard success response.
31 Check the the entry in _descDiagEcuPres that matches the ECU ID number given in this message. If that entry's ecuDiagThreadRunning field (offset +0x50) is zero, then no reply is sent and the Tester waits for the next message. Otherwise, if ecuDiagThreadRunning is nonzero, one of the other fields in the struct is zeroed (depending on a value from the original request message) and a standard success response message is sent.
32 PWM enable
33 PWM disable
34 Input capture enable
35 Read the number of input capture ticks (low/high) at the index specified by position 07 in the input message (which must be either 00 or 01) and respond with this data:
01-02: 00 0F (bytes in payload)
07: 01 (success indicator)
08-0B: number of ticks for input capture, low
0C-0F: number of ticks for input capture, high
36 Input capture disable
37 Starts a thread for AD conversion loop
38 Respond with AD data (filtered value, value in mV, etc.):
01-02: 00 0F (bytes in payload)
07: 01 (success indicator)
08: filtered value
09: unknown, read from location 0107_978D
0A-0B: ADC reference value?
0C-0F: ADC value in millivolts
39 Set flag that stops AD conversion loop thread
3A Read the Tester's date/time and return it (total response size: 14 bytes)
3B Read a date/time from the packet and write it to the Tester's EEPROM. Standard success response.
3C Change from PC-connected to standalone mode. Standard success response.
3D Erase flash and reboot.
3E Draw a line on the Tester's screen, using coordinates encoded in the message payload. Standard success response.
3F Draw a box on the Tester's screen (with _drawBox), using coordinates encoded in the message payload. Standard success response.
40 Draw a box on the Tester's screen (with _drawBoxCut), using coordinates encoded in the message payload. Standard success response.
41 Call _visSchermaDiagModGen. Standard success response.
42 Switch Tester mode between Ferrari, Maserati, or both. Byte 07 in the request message is 00 (Ferrari & Maserati), 01 (Ferrari only), or 02 (Maserati only). The reply message is 9 bytes long, with position 07 being set to 01 (to indicate success) and position 08 mirroring the mode selection byte from the request message.
43 Write to the DS1302 timekeeping chip. Standard success response.
44 Run a function from an .ecu plugin
45 Load an object module file, the filename for which is given in the message at offset 07.
51 Run _applModGest### and _diagModGest####. ECU module number is given in the message. Offset 09 in the message is the pipe ID (which corresponds to the line/port being used to communicate to the ECU.)
52 ?
53 ?
80 Same as 13?

Tester filesystem

The Tester has flash memory which it mounts at /FN0. Within this, it stores ECU support files that were transferred to it by WSDC32. As an example, when BMOT0145 is loaded to the Tester, these files will be created:

  • /FN0/ecu/BMOT0145.ECU
  • /FN0/ecu/BMOT0145_TESTER.TXT
  • /FN0/ecu/SCARICO.INI

The configuration file SCARICO.INI is updated with an additional section for each ECU module that is added, up to the maximum supported by the Tester (which is 64 modules). With this one example module, the .ini file would have the following content:

[CONFIG]
n_ecu=1
ecu1=0145
[0145]
name=MOTRONIC BOSCH 5.2 F355
dir=BMOT0145
exe=BMOT0145.ECU
stringhe=BMOT0145_TESTER.TXT
linea_iso1=TRUE
linea_iso2=TRUE
linea_can=FALSE
linea_rs232=FALSE

ECUs and protocols

The .ecu file that is loaded to the Tester to provide support for an ECU actually contains the implementation of the software protocol that the ECU speaks. For example, the Motronic 5.2 ECU in the F355 speaks KWP71, so BMOT0145.ECU contains an implementation of KWP71 compiled into an A.out executable for the VxWorks-based Tester. Another ECU (such as the Motronic in the 512TR) may also use KWP71 -- but its support package includes a separate .ecu file with another implementation of KWP71. The advantage to this design is that each ECU may have minor differences in their implementation of protocols, and this can be accommodated by the individual .ecu files. The disadvantage is that the protocol implementations are often redundant, being replicated in each .ecu module.

To successfully perform diagnostics on an ECU, it is not only necessary to know the protocol being used, but also the location/address at which each parameter is stored, as well as the formula to convert the in-memory values to the appropriate physical units. For example, the F355 Motronic 5.2 stores engine RPM in a single byte at address 0x2001 with a value of 40 RPM per LSB.

Re-implementing with modern hardware / software

Most of the ECUs supported by SD2 are using uncommon software protocols, but fairly standard K-Line signaling (as is used for ISO9141). As a result, there are many USB cables on the market built around an FTDI serial transceiver chip and electronics for generating K-Line voltage levels and polarity. The selection of the FTDI chip is important because it allows bit-bang control of its I/O lines, which is necessary to achieve the 5-baud data rate for the slow-init sequences used to start communication with an ECU. (This data rate is much lower than the rates allowed by baud rate generators for any common USB/serial adapters.)

Fortunately, there are a number of commercially available USB K-line adapters that are often built with 16-pin OBD connectors, and they make use of the +12V line in the 16-pin port on the vehicle side. Most of the ECUs supported by SD2 are not accessible via such a 16-pin connector, instead often using the 3-pin Fiat/Alfa/Lancia diagnostic connector. In such a case, the 12V must be supplied with an external connection. There exist many 16-pin to 3-pin adapters that use alligator clips for the 12V reference.

Normal OS-provided serial port APIs (such as termios) cannot be used because they do not support the bit-bang interface control that is needed for the 5-baud init. Instead, libftdi may be used for this level of control. Although libftdi is a straightforward solution on Linux, it will only work on Windows systems that have a generic USB driver installed for the FTDI device. Installing such a driver can be done with a utility such as Zadig. This approach is better overall than attempting to use the FTDI-provided D2XX driver API on Windows, for the following reasons:

  • D2XX is not FOSS, while libftdi is.
  • The code for this project would be more complex because it would need to support two different APIs (D2XX for Windows and libftdi for everything else).
  • D2XX is not available in cross-building environments such as MXE, so the build process for Windows would be made less convenient.

Note about Aikonss K-line adapters: a pair of these purchased in late 2022 had bad FTDI chips and occasionally don't see bytes that arrived on the wire. I originally misattributed this problem to a software issue involving libftdi or my application code. The problem disappeared when I switched to the RossTech K-line interface.

libftdi programming notes

Time spent in the FTDI bitbang mode seems to cause some sort of internal FIFO on the FTDI part to fill up with garbage bytes (0xFF or 0xFC during experimentation -- possibly representing the line status?) The problem is that the libftdi function calls that flush the serial Tx/Rx buffers don't clear whatever buffer contains this unwanted data. I have found that, when switching from bitbang back to normal serial FIFO mode, I actually need to read up to 256 garbage bytes from the FTDI before it will start to provide real data that is coming in over the serial lines. When doing the slow-init sequence, the best strategy is simply to read data until either (a) the expected byte sequence is seen, or (b) time expires. Sometimes, the tester may not know the exact byte sequence to expect, other than the 0x55 sync byte. In these cases, the tester would need to wait for 0x55 and then read the appropriate number of bytes following it.

Note that Debian systems might have a package called modemmanager installed, and this seems to claim ownership of /dev/ttyUSB* devices. Even if the user is in the dialout group (which is recommended/required for access to tty devices), the presence of modemmanager will foul access to the device.