Skip to content

CAN transport ​

Below the commands sits one exchange pattern: a request goes out on the MUXEN CAN bus addressed to one device, and one answer comes back. This chapter describes how that exchange is framed, in case you are reading a candump capture, writing a device firmware, or trying to work out why a bus behaves the way it does.

Nothing here is needed to use muxen-uds.

Addressing on the wire ​

MUXEN uses 29-bit (extended) CAN identifiers. A UDS exchange uses two of them, one per direction:

request  (client → device):  0x01000000 | (deviceId << 12) | clientId
response (device → client):  0x01000000 | (clientId << 12) | deviceId

Both device and client addresses are 12 bits. The client address defaults to 0x27F and is settable with -c/--client-id.

For device 0x280 (640) and the default client, that is request id 0x01280 27F and response id 0x0127F 280, both with the extended flag set. A receiver filters on the response id with the mask 0x03FFFFFF.

Three broadcast frames are used outside the UDS pair:

PurposeIdentifierPayload
UID request0x00F00FFF, RTR, DLC 0—
UID answer (from devices)0x00F00nnn, matched with mask 0x03FFF0008 bytes of UID
Enable UDS by UID0x00F01000 | deviceIdthe target's 8-byte UID
Communication offbroadcast, sent before and during a firmware transferencoded by the shared MUXEN library

A UID answer shorter than 8 bytes is discarded rather than padded — a truncated frame would otherwise contribute whatever followed it in the buffer as UID bytes, and that UID would end up in the scan output and in deployed.json.

ISO-TP ​

Requests and responses larger than a single CAN frame are segmented with ISO-TP (ISO 15765-2): a Single Frame when the payload fits, or a First Frame / Flow Control / Consecutive Frame sequence when it does not.

The source carries two implementations, and picks one at run time:

PathUsedNotes
Kernel can_isotpwhenever the kernel provides ita SOCK_DGRAM/CAN_ISOTP socket from libmuxenuds, non-blocking, poll() with the command's timeout
In-processonly where the kernel does nota state machine on a raw CAN socket, driven by a GLib main loop

The choice is a probe, not a build option: muxen-uds tries to create a CAN_ISOTP socket and falls back to the in-process implementation when that fails.

Both request a separation time (STmin) of 10 ms between consecutive frames from the sender and advertise a block size of 0 — send everything, no intermediate flow control.

One socket per exchange, except for a firmware upload ​

A command opens an ISO-TP socket for each request and closes it once the answer is in. On the kernel path that close is slow — the kernel waits before releasing a bound can_isotp socket, tens of milliseconds — which nobody notices on a request or two.

A firmware upload is thousands of exchanges, one per 32-byte record, so firmware keeps one socket for the whole transfer instead. It gives that socket up only when an exchange fails: an attempt that got no answer is retried on a fresh socket, exactly as it would be by any other command. Between two blocks, anything that reached the socket in the meantime is discarded before the next request is sent.

The in-process path is defensive about malformed input: a frame with a DLC of 0 or above 8 is ignored, a Single Frame claiming more bytes than it carries is ignored, a Consecutive Frame out of sequence aborts the transfer with an overflow flow-control frame, and a payload larger than the receive buffer is refused rather than truncated.

The receive buffer is 5000 bytes, which bounds any single UDS response and any routine --payload.

Timeouts and retries ​

OperationTimeoutAttempts
Most commands2000 ms3
Most commands, --speedy500 ms1
writeconfig — table read2000 ms3 (1 with --speedy)
writeconfig — the write itself2000 ms3
firmware — download request, transfer exit30000 ms5
firmware — a transfer block1500 ms10
firmware — the last transfer block5000 ms10

Retries are 200 ms apart on the firmware path. Re-sending a request is safe: UDS is request/response, and re-sending the same transfer block sequence is the standard ISO 14229 recovery for a lost frame.

The scans have their own windows: scan listens for --timeout seconds; uid collects for 4000 ms while re-broadcasting the request every 1000 ms (2000/500 with --speedy).

Why answers are matched by identifier ​

A fresh ISO-TP socket is opened per read. An answer that arrives after its own request timed out is therefore delivered on the socket of the next request and reads as a perfectly valid response to it.

Service 0x22 (ReadDataByIdentifier) echoes the requested identifier in its positive response, and that echo is the only thing pairing an answer with a request. muxen-uds checks it: an answer carrying another identifier is dropped and the request re-sent, never handed back as the value of the one that was asked for.

That check is what stands between a busy bus and a configuration dump in which three parameters all claim to be #000 ProductId — and which would then be restored, or deployed, as if it were correct.

The services used ​

ServiceCodeCommand
ECU Reset0x11reset (hard reset, with a 16-bit delay in milliseconds)
Read Data By Identifier0x22readconfig and every command that reads a table
Write Data By Identifier0x2Ewriteconfig
Routine Control0x31locate, factory, routine
Request Download0x34firmware, announcing address and size
Transfer Data0x36firmware, one block per SREC S3 record
Transfer Exit0x37firmware

Routine control actions are 1 start, 2 stop, 3 request results. The vendor routine identifiers muxen-uds uses:

RoutineIdUsed by
Reset parameters0x0001factory --reset-device-id, --reset-device-configuration
Locate0x0002locate
Allocate configuration0x0004factory --allocate-device-configuration

factory encodes its choice as a 16-bit flag word appended to the routine request: bit 0 resets the CAN address, 0xFFFE resets every other configuration section.

A negative response is a frame beginning with 0x7F; its third byte is the error code. readconfig treats a negative response to identifier n as "the table ends here" rather than as a failure, which is how it knows where to stop.

Reading a parameter answer ​

A positive 0x22 response carries, after the 0x62 service echo and the 16-bit identifier:

<name>\n <typeSize> <type> <value…>

The name is terminated by a line feed (0x0A), type is 0 signed, 1 unsigned, 2 string, and typeSize is the width in bytes — the length of the string for a string. A name longer than 256 characters makes the whole answer be refused rather than truncated, because a partial name could collide with another parameter.

Integration of multiplexed solutions
MUXEN and the MUXEN logo are trademarks of MUXEN SAS.