@@ -73,17 +73,20 @@ For full option details, see `/opt/can_tools/can_tools.conf.example`.
7373
7474#### Applying configuration changes
7575
76- A conf edit (or a reinstall that updates a script) only takes effect:
77- - at the ** next reboot** , or
78- - by running ` sudo systemctl restart canhat_fd_setup.service ` while the
79- ** machine is stopped** (hydraulics locked, implement down, key state
80- decided).
81-
82- Never restart it, or run ` ip link set ... down/up ` , while the machine is
83- live - bringing CAN down/up while the controller is sending/receiving is
84- what caused the 2026-10-14 incident. Until the next reboot (or that manual,
85- machine-stopped restart), the running ` canhat_fd_setup.service ` keeps using
86- the previously-applied settings.
76+ > [ !WARNING]
77+ > ** Never edit ` /opt/can_tools/can_tools.conf ` while the machine is live, and
78+ > never run ` systemctl restart canhat_fd_setup.service ` or `ip link set ...
79+ > down/up` by hand while it is live.** ` can_watchdog.service` re-reads the
80+ > conf file on ** every cycle** (every ` WD_LOOP_S ` , 1 s by default) - it does
81+ > ** not** wait for a reboot the way this used to be documented. See
82+ > [ ` docs/can_watchdog.md ` ] ( docs/can_watchdog.md#configuration-is-re-read-live-the-dangerous-part )
83+ > for exactly which keys take effect immediately, which ones only pause the
84+ > watchdog (safe), and which ones get applied live to a running interface the
85+ > next time the watchdog happens to flush/recover it (dangerous - this is how
86+ > the 2026-10-14 incident happened). Only edit the conf with the machine
87+ > mechanically stopped (hydraulics locked, implement lowered, key state
88+ > decided), and reboot (or restart ` canhat_fd_setup.service ` , still with the
89+ > machine stopped) right after.
8790
8891### Systemd Services
8992
@@ -126,6 +129,194 @@ disable` above and put the machine back into the FD/Plus race this section
126129exists to avoid. Update a CAN HAT Plus machine by hand too - copy over the
127130changed files/units yourself instead of running ` install.sh ` .
128131
132+ ## Operation
133+
134+ This section is an overview; the full operator runbook (complete reason-code
135+ table, CRIT triage, ` --clear-degraded ` caveats, the exact ` can_pause ` /
136+ ` can_diag.sh ` invocations, and the manual unbind/bind procedure) is in
137+ [ ` docs/can_watchdog.md ` ] ( docs/can_watchdog.md ) . Read that before touching a
138+ machine that is in operation.
139+
140+ ### System Architecture
141+
142+ The CAN subsystem starts in this boot sequence order:
143+
144+ 1 . ` canhat_fd_rename.service ` (` Type=oneshot ` ,
145+ ` After=sys-subsystem-net-devices-can0.device sys-subsystem-net-devices-can1.device ` ,
146+ ` Before=canhat_fd_setup.service docker.service ` ) - pins ` can0 ` /` can1 ` to
147+ their physical SPI devices per ` CAN_SPI_MAP ` (default
148+ ` spi0.0:can0 spi1.0:can1 ` ) using the lib's ` can_pin_name ` (`ip link set
149+ <iface > down` then ` ... name <want >`, with a probe-order swap if the two
150+ SPI devices enumerate in the opposite order).
151+ 2 . ` canhat_fd_setup.service ` (` Type=oneshot ` , ` After=canhat_fd_rename.service ` )
152+ - brings each interface up via the lib's ` can_setup_iface ` : `ip link set
153+ <if > down` then ` up type can bitrate ... restart-ms ... berr-reporting ...
154+ [ dbitrate ... fd on | fd off] ` , then ` ip link set <if > txqueuelen ...`.
155+ ** No ` ethtool ` is used or installed** on the machine - everything goes
156+ through ` ip link set ` / sysfs.
157+ 3 . ` can_watchdog.service ` (` Type=simple ` , ` Restart=always ` ,
158+ ` Wants=canhat_fd_setup.service ` , ` After=canhat_fd_setup.service ` ) - the
159+ resident watchdog described below.
160+
161+ ` canhat_fd_rename.service ` does declare `Before=canhat_fd_setup.service
162+ docker.service` (so renaming happens before both ` canhat_fd_setup.service`
163+ and ` docker.service ` start), but there is ** no further ` Before= ` ordering**
164+ from ` canhat_fd_setup.service ` or ` can_watchdog.service ` onto whatever
165+ application or container speaks CAN - beyond that one ` Before= ` , only the
166+ ` After= ` chain above between the three can_tools units themselves.
167+ If an app or container needs to start strictly after the interfaces are up
168+ and the watchdog is running, that has to be arranged on the consumer's own
169+ unit (or handled by the app's own reconnect-on-ENETDOWN logic; see
170+ ` CAN_DEPENDENT_UNITS ` in ` docs/can_watchdog.md ` ).
171+
172+ ` can_watchdog ` is ** FD-only** : ` install.sh ` only ever installs/enables it
173+ alongside the FD units, and it is never enabled on a CAN HAT Plus machine
174+ (see "CAN HAT Plus" below).
175+
176+ ### Deployment Workflow
177+
178+ ** Step 1: Verify what would change (optional but recommended)**
179+
180+ ``` bash
181+ sudo ./install.sh --check
182+ ```
183+
184+ Prints a unified diff for every file that would change and exits non-zero if
185+ there is anything to do. ` install.sh --check ` run again right after a plain
186+ ` install.sh ` should show no differences and exit 0 (running ` --check ` twice
187+ in a row with ** no** ` install.sh ` in between shows the same diff both times -
188+ ` --check ` itself never changes anything).
189+
190+ ** Step 2: Install or upgrade**
191+
192+ ``` bash
193+ sudo ./install.sh
194+ ```
195+
196+ Idempotent and non-destructive: files are only written if their contents
197+ differ, running services are never stopped/restarted/bounced, and an
198+ existing ` /opt/can_tools/can_tools.conf ` is never overwritten. The legacy
199+ ` can_monitor.timer ` /` can_monitor.service ` units ** are** stopped, disabled and
200+ removed by this step if still present - that is the one "stops a service"
201+ exception, and it is intentional (` can_monitor ` is retired).
202+
203+ ** Step 3: Apply new settings - machine stopped, not live**
204+
205+ See the warning in "Applying configuration changes" above and
206+ [ ` docs/can_watchdog.md ` ] ( docs/can_watchdog.md#configuration-is-re-read-live-the-dangerous-part ) :
207+ ` can_watchdog ` re-reads the conf ** every cycle** , not at reboot, so ** never
208+ edit the conf while the machine is running** - no key is safe to change live
209+ (e.g. adding ` can1 ` to ` CAN_FLUSH_IFACES ` can down/up a live ` can1 ` ). Changing ` CAN_RESTART_MS ` / ` CAN_BERR_REPORTING ` / ` CAN_FD ` makes
210+ the watchdog detect a mismatch against the still-running kernel settings and
211+ switch that interface to ** observe-only** (` reason=config_mismatch_reboot_required ` )
212+ until a reboot (or a machine-stopped ` canhat_fd_setup.service ` restart)
213+ actually applies them. Changing ` CAN_BITRATE ` / ` CAN_DBITRATE ` /
214+ ` CAN_TXQUEUELEN ` is ** not** checked for a mismatch at all, so it is applied
215+ live, to a running interface, the next time the watchdog calls
216+ ` can_setup_iface ` for any reason (a TX flush, a bus-off recovery, a stall
217+ rebind) - with the machine still operating. Only ever edit the conf with the
218+ machine mechanically stopped, then reboot (or restart
219+ ` canhat_fd_setup.service ` , still stopped) right away.
220+
221+ ### Watchdog Behavior and Logging
222+
223+ Once running, ` can_watchdog.service ` monitors each CAN interface every
224+ ` WD_LOOP_S ` (1 s default) and recovers autonomously from most error
225+ conditions. ** It does not automatically reboot the machine** (see Issue #10 ).
226+ A one-paragraph summary of each mechanism follows; see
227+ [ ` docs/can_watchdog.md ` ] ( docs/can_watchdog.md ) for the exact conditions,
228+ backoff schedules and the complete reason-code table.
229+
230+ - ** TX flush** (ERROR-WARNING/ERROR-PASSIVE, ` CAN_FLUSH_IFACES ` , default
231+ ` can0 ` only): if all of rx/tx/rx_fifo/tx_fifo stay unchanged for 2
232+ consecutive cycles, the watchdog re-runs ` can_setup_iface ` (a down/up) to
233+ discard stale queued TX frames so they are not resent once the ECU comes
234+ back. Any other time the interface is in ERROR-WARNING/ERROR-PASSIVE, the
235+ watchdog does not touch it at all.
236+ - ** Bus-off recovery** : on ` BUS-OFF ` , the watchdog waits for the rx-offload
237+ queue to settle, then re-runs ` can_setup_iface ` (down/up; the ifindex does
238+ not change, so application sockets stay valid). Repeated bus-off within a
239+ short window backs off exponentially (1 s up to 30 s); a storm (5+ within
240+ 60 s) logs one CRIT but recovery keeps running.
241+ - ** rx-offload stall detection** : if ` rx_packets ` is flat while
242+ ` rx_fifo_errors ` /` tx_fifo_errors ` keep growing for 3 consecutive cycles
243+ (or 2 cycles right after a bus-off recovery, or 3 bus-off recoveries in a
244+ row with no rx progress), the watchdog performs an ** unbind/bind** cycle
245+ (not a down/up - the rx-offload lock this works around survives a plain
246+ down/up). This changes the ifindex, so any application holding an open CAN
247+ socket must reopen it; see ` CAN_DEPENDENT_UNITS ` in
248+ ` docs/can_watchdog.md ` to restart specific units/containers afterward.
249+ - ** Degraded mode** : if a step above cannot complete (Oops/BUG detected in
250+ the kernel log, an unbind/bind timeout, a rebind-quota overrun, 3
251+ consecutive ` ip ` timeouts system-wide, ...), the affected interface(s) are
252+ marked degraded (` /run/can_tools/degraded_<iface> ` ) and the watchdog stops
253+ touching them - it only re-logs a CRIT every 10 minutes
254+ (` reason=manual_reboot_required ` ). See ` docs/can_watchdog.md ` for which
255+ reasons are safe to clear with ` can_watchdog.sh --clear-degraded <if> ` and
256+ which genuinely require a reboot.
257+
258+ #### Reading Watchdog Logs
259+
260+ ``` bash
261+ journalctl -u can_watchdog -n 50 -f # last 50 lines, follow
262+ journalctl -u can_watchdog -p crit # CRIT lines only (degraded/storm/Oops/etc.)
263+ ```
264+
265+ Common log line shape: ` iface=<if> reason=<reason> <details...> ` (a repeated
266+ ` iface+reason ` pair is rate-limited to one line per ` WD_LOG_RATE_S ` , with
267+ ` suppressed=N ` on the next line that does get through). The 60 s periodic
268+ INFO summary line (`iface=.. rx=.. tx=.. rxfifo=.. txfifo=.. ifindex=..
269+ degraded=0|1` ) has no ` reason=` field - that is the only line with
270+ ` degraded= ` . See ` docs/can_watchdog.md ` for the full reason-code table and
271+ what each one means operationally.
272+
273+ #### Diagnosing a Stopped Interface
274+
275+ ``` bash
276+ cat /sys/class/net/can0/statistics/rx_packets # should be increasing
277+ cat /sys/class/net/can0/statistics/rx_fifo_errors # usually 0 / not growing
278+ cat /sys/class/net/can0/statistics/tx_fifo_errors # usually 0 / not growing
279+ ```
280+
281+ ` rx_packets ` flat while ` rx_fifo_errors ` /` tx_fifo_errors ` grow, for 3
282+ consecutive watchdog cycles, is exactly the rx-offload stall signal above -
283+ the watchdog is already acting on it; you do not need to intervene unless it
284+ shows up as degraded. ` ip -s link show can0 ` does not expose the fifo
285+ counters - use sysfs as above.
286+
287+ ### Pause and Diagnostics
288+
289+ See [ ` docs/can_watchdog.md ` ] ( docs/can_watchdog.md#pause ) for the working
290+ ` can_pause ` /` can_unpause ` /` can_diag.sh ` invocations and for ` can_diag.sh ` 's
291+ actual argument (a duration in seconds, not an interface name), output path,
292+ and what to do if it was killed with SIGKILL mid-capture. In short:
293+ - ` can_pause ` /` can_unpause ` are ** shell functions in ` can_tools_lib.bash ` ** ,
294+ not standalone commands - there is nothing on ` $PATH ` called ` can_pause ` .
295+ - ` can_diag.sh ` is ** bench / machine-stopped use only** : it pauses the
296+ watchdog and turns on bus-error reporting on every interface while it
297+ captures.
298+
299+ ### Netlink Hangs (rtnl_hung)
300+
301+ ` rtnl_hung ` is a ** global, all-interface** condition, not a per-interface
302+ one: every watchdog cycle calls one bounded ` ip -d link show type can ` to
303+ read interface state; if * that call itself* (not an unbind/bind) times out
304+ for ` WD_RTNL_HUNG_CONSEC ` (3) consecutive cycles, ** every** configured
305+ interface is marked degraded with ` reason=rtnl_hung ` , and the watchdog stops
306+ calling ` ip ` at all - it falls back to sysfs-only observation for everything
307+ until a reboot. A single interface's unbind hanging instead produces
308+ ` reason=unbind_timeout ` on just that interface (see
309+ ` docs/can_watchdog.md ` ). Either way, recovery is ** manual reboot only** -
310+ there is no live recovery from a genuinely hung netlink socket.
311+
312+ ### CAN HAT Plus (Standard CAN)
313+
314+ The notes in the "Systemd Services" section about CAN HAT Plus still apply:
315+
316+ - ` install.sh ` does not install or enable ` canhat_plus_* ` units.
317+ - ` can_watchdog ` is FD-only and will not run on a CAN HAT Plus machine.
318+ - Set up CAN HAT Plus by hand (see the "Systemd Services" section).
319+
129320## CAN Interface Setup
130321
131322### PCAN setup
0 commit comments