Skip to content

The mirrors ​

The mirror is the point of the whole exercise: a local interface on the Brain that behaves like the remote one, so standard Linux tooling works on a segment it is not wired to.

            Brain (Linux)                        STM32 Gateway (Zephyr)
┌─────────────────────────────────┐          ┌───────────────────────────┐
│  user tools (candump, picocom)  │          │  portal firmware          │
│            │                    │          │  (unconfirmed MCUboot     │
│   portal0 (virtual CAN)  or     │          │   image, reverts on       │
│   /dev/tty-portal0 (PTY)        │          │   power-cycle)            │
│            │                    │          │                           │
│      muxen-portal (daemon)      │          │  MUXEN side  remote side  │
│            │                    │  ISO-TP  │              CAN bus or   │
│           can0 ─────────────────┼──────────┼── (tunnel) ── serial ─────┼── remote segment
│            │                    │  tunnel  │                           │   (devices under
│       muxen-uds ── firmware ────┼──────────┼── MCUboot / app fw        │    diagnosis)
└─────────────────────────────────┘  upload  └───────────────────────────┘
PathCarrier
Firmware upload, device reset, scanmuxen-uds (existing, unchanged)
Tunnel data + control planeISO-TP on dedicated control and data ID pairs (ACCESS = 3) on the MUXEN bus
Access to a remote CAN busSocketCAN portal0
Access to a remote serial portPTY /dev/tty-portal0[-chN]
Access to a remote LIN busSocketCAN portal0chN, one per channel

The mirror type follows the gateway type announced in HELLO; it is never something you pass on the command line. Both mirror kinds are virtual, so kernel-side line settings on them are meaningless — the remote settings are session parameters the gateway applies to its physical interface.

CAN mirror ​

A vxcan pair whose user-facing end is named portal0 (configurable). The daemon binds the peer end. If vxcan is unavailable in the kernel it falls back to vcan, with the daemon echo-filtering its own injected frames.

sh
candump portal0
candump portal0,0:0,#FFFFFFFF     # data frames AND error frames
cansend portal0 18EEFF00#0011223344556677

Behaviours worth knowing:

  • The mirror declares itself classic. The user end is created with MTU 16, so an application attempting a CAN-FD write() fails synchronously with EMSGSIZE, exactly as on a real classic controller — no silent drop. The FD transport of the tunnel never implies an FD remote bus.
  • RTR frames are transmitted on the remote bus, so a tool that polls with remote frames works.
  • Error frames written by the user are dropped and counted. Emitting an "error frame" on a physical bus is meaningless.
  • Remote CAN errors appear as SocketCAN error frames on portal0, injected by the daemon — possible precisely because the mirror is virtual. Arm the error mask or you will not see them.
  • Fresh TX-queue drops on the gateway raise a CAN_ERR_CRTL (TX overflow) error frame on portal0, so an application watching error frames sees the loss in near real time instead of polling muxen-portal status.

Fidelity limits ​

  • No local echo. On a real interface, frames you send are echoed to other local sockets once ACKed on the bus. On the mirror they are not, and the daemon does not simulate it — a fake echo would claim an ACK that may never happen downstream.
  • Your own TX is never mirrored back as RX, exactly as a CAN controller does not receive its own frames. candump portal0 shows remote-originated traffic only: your stimuli are absent, only the replies show.

Serial mirrors ​

One PTY per open channel. The daemon owns the master sides; each slave is exposed through a stable symlink <name>-chN (/dev/tty-portal0-ch1, …), and <name> itself (/dev/tty-portal0) points at the first channel opened — so single-channel sessions keep the short name.

sh
picocom -b 19200 /dev/tty-portal0-ch2
stty -F /dev/tty-portal0-ch2 9600
muxen-portal open-channel 3 --rate 9600     # additive, no re-upload

Line settings follow termios ​

Local termios settings on the PTY are ignored except baudrate and framing changes, which the daemon detects and forwards to the gateway as a hot re-OPEN. So stty and picocom -b transparently reconfigure the remote port, in milliseconds and with no re-upload.

The daemon keeps the applied configuration as a dedup cache and forwards only real changes. Tools that re-assert identical settings at startup — picocom, pyserial and most others do — cause no re-OPEN and no remote-port restart.

Rejected changes fail cleanly. A request invalid on its face (a baud above the gateway's advertised maximum, 5 or 6 data bits, mark/space parity, 1.5 stop bits) fails fast locally without a tunnel round trip; a plausible one the gateway refuses fails on the reply. Either way the daemon restores the PTY termios to the effective configuration, so an application following the POSIX contract — re-read after tcsetattr, as real UART drivers require — sees immediately that the request did not take. The rejection is logged with the translated reason and counted as termiosRejected in status.

The one accepted limit: between the application's tcsetattr and the restore (microseconds locally, tens of milliseconds via a gateway refusal) a tcgetattr shows the requested value. The termios API is local and synchronous; there is no asynchronous error channel.

Out of scope: break and idle conditions, 9-bit modes, and exotic ioctls (modem lines, break) are not forwarded.

Backpressure ​

Remote RX passes through a per-channel daemon-side ring (default 64 KiB, pty-buffer) and is written to the PTY master non-blocking.

SituationBehaviour
No reader on the slaveIncoming bytes are discarded and counted without accumulating. The buffer is purged when a reader attaches, so a tool connecting mid-session sees fresh stream, not a 30 s backlog.
Reader present but slowRing overflow: oldest dropped, counted.
No reader for ~3 sThe daemon sends FLOW(0) and the gateway stops shipping a stream the Brain would discard — sparing the whole MUXEN bus. Any reader attaching, or any write through the PTY, resumes it.

Bridging, keep-alive and control never block on the PTY. A blocking write on a full master would freeze the daemon, starve the PING and revert the gateway; that is the failure this rule exists to forbid.

status exposes reader presence, the paused state and both drop counters.

LIN mirrors ​

One vxcan per LIN channel, named <name>chN — portal0ch1, portal0ch2, portal0ch3. This is the per-channel shape of the serial PTYs with the plumbing of the CAN mirror, reused verbatim.

The base name is capped at 11 characters so that <name>chN and its vxcan peer <name>chNd both fit a 15-character interface name.

The sllin mapping ​

Frames follow the mapping the Linux sllin line discipline already uses to expose a LIN bus as SocketCAN, so can-utils reads and drives a LIN bus with no LIN-specific tooling on the Brain:

Frame fieldLIN meaning
CAN IDLIN frame ID, 0x00–0x3F, standard (11-bit) frame. PID parity is computed and checked by the gateway, so the Brain always sees the bare ID
payloadthe response bytes, checksum stripped and verified
RTRheader only. Received: a header went by with no response. Sent: emit this header and publish whatever answers
CAN_ERR_FLAGa LIN error record, with the class in data[0]
sh
$ candump -td portal0ch1,0:0,#FFFFFFFF
 portal0ch1  022  [4] A1 B2 C3 D4        # slave answered header 0x22
 portal0ch1  03C  [0] remote request     # header seen, no response
 portal0ch1  20000004 [8] ...            # ERR frame: checksum error

$ cansend portal0ch1 022#R               # master: header, capture response
$ cansend portal0ch1 030#AABBCCDD        # master: header + publish data

Error classes in data[0]:

ValueClassMeaning
0x01CHECKSUMresponse checksum mismatch
0x02FRAMINGUART framing or bit error
0x03NO_RESPONSEheader sent or seen, nothing answered before the timeout
0x04PID_PARITYbad PID parity on an observed header
0x05OVERRUNUART overrun

Diagnostic IDs 0x3C/0x3D get no special treatment — they appear as ordinary frames, which is what a diagnostician wants to see.

Master or monitor ​

Per channel, selected at open; --listen-only picks monitor.

  • Master — the Brain drives the schedule, one header per frame you send. This is the operationally necessary role: on these installations the MUXEN board is the LIN master, so with the product firmware swapped out nothing else generates headers and a passive-only portal would mirror a silent bus. Jitter is tunnel latency (milliseconds), which is fine for diagnosis but does not reproduce a timing-critical production schedule.
  • Monitor — the gateway never transmits and only reports what a foreign master puts on the wire. A write to the mirror is dropped and counted rather than rejected, the same contract as a full TX queue.

The checksum rule is --lin-checksum classic|enhanced|auto, default auto (classify per frame by the LIN 2.x rule; IDs 0x3C–0x3F are always classic). The applied value comes back in STATUS and shows in stats.

Reconfiguration is set-rate and open-channel, as on serial. There is no termios equivalent — a vxcan carries no line settings, so the remote baud only ever changes through an explicit command.

Slave emulation and gateway-resident schedule tables are not supported.

Mirror lifetime and permissions ​

One session gives one mirror per open channel: exactly one on a CAN gateway, one PTY per channel opened on serial, one vxcan per channel opened on LIN.

Mirrors exist only while a session is active. They are destroyed immediately on a deliberate close; on session loss they linger for mirror-linger (default 180 s) while the daemon re-establishes the session and resumes on the same mirrors, so external diagnostic tools holding portal0 or a PTY open survive the outage.

Access is deliberately wide open: PTY slaves are created 0666 and portal0 is an ordinary SocketCAN interface, usable without privileges like any raw CAN socket. Shell access to the Brain already implies full unauthenticated access to can0, so the mirrors grant nothing new.

Timing fidelity ​

This is the one place where a mirror is not like the real interface, and it matters when you interpret what you see.

  • RX traffic is injected as it arrives, so tools see local injection time. The gateway's own microsecond timestamp is kept in the daemon's statistics only.
  • Original spacing is not re-simulated. The tunnel preserves byte order and contiguity, not silences. Every inter-byte gap you could measure on the Brain is an artefact of tunnel batching.
  • Inter-frame gaps remain the application's responsibility — Modbus RTU's 3.5-character boundary, for instance — exactly as on a local port with a deep buffer.
  • TX is forwarded immediately, and remote transmission is confirmed only implicitly through the STATUS TX counter. There is no per-frame acknowledgment.

Because timing cannot survive the tunnel, the shape of the traffic is measured on the gateway instead and shipped as a histogram — see muxen-portal timing in Monitoring a session.

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