Part Number: AM2432
DLR Backup Manager Communication Loss — Problem Analysis Report
1. Summary
This report analyzes two independent defects, both observed sporadically only during the DLR stress test of repeatedly power-cycling ordinary ring nodes:
| No. | Defect | Ownership | Status |
|---|---|---|---|
| Issue 1 | The DLR Backup Manager spuriously takes over as Active Supervisor on a single-port link down, causing ring role/state churn | TI DLR host driver (SDK 2026.00.00.06) | Fixed locally and verified |
| Issue 2 | PRU firmware host TX queue (Q3/LwIP) deadlock: busy_m and the collision slot are never released, permanently breaking all IP communication (including EIP I/O over UDP) on that device |
TI ethernetip_adapter PRU firmware |
Host-side recovery validated; TI root fix pending |
Issue 2 directly matches the field symptom: the Manager reports the DLR ring as healthy, yet the Backup Manager cannot be pinged and its EIP I/O connection drops and cannot reconnect.
2. Test Topology
M.port1 ——— N1 —— N2 —— N3 —— N4 ——— B.port1
M.port2 —————————— direct cable —————————— B.port2
-
M: Manager (Active Ring Supervisor, precedence 1) -
B: Backup Manager (Backup Ring Supervisor, precedence 0) -
N1..N4: ordinary ring nodes (non-TI devices, ring-node functionality only) -
Normally M blocks M.port2; data flows M.port1 — node chain — B.port1; the M.port2↔B.port2 direct cable carries only Beacon/DLR frames.
3. Symptoms
-
Occurs sporadically after repeatedly power-cycling the ordinary nodes (the Manager and Backup Manager stay powered).
-
The Manager reports the DLR ring as normal, but the Backup Manager is unreachable.
-
The Backup Manager times out pinging any device (Manager, nodes); the Manager also times out pinging the Backup Manager.
-
The Backup Manager's EIP I/O communication fails at the same time (I/O data is sent via UDP/LwIP, sharing the host TX queue with ARP/ICMP).
-
Breaking and then restoring the ring restores communication, without rebooting the device.
Reproduction: two TI managers configured as Active / Backup Ring Supervisor (both blocking port 2), four ordinary nodes chained in the ring. Keep both managers powered and repeatedly power-cycle the nodes (a few seconds per round). Issue 1 hit 2 times in 12 link-down events; Issue 2 appeared multiple times in the same stress run (always after link recovery).
4. Issue 1: DLR Backup Manager Spurious Takeover on Single-Port Link Down
4.1 Relevant mechanisms in the stock code
(1) Link-down handling
The P0/P1 link-break handlers in icss_dlr.c (EIP_DLR_port0ProcessLinkBrk() / EIP_DLR_port1ProcessLinkBrk()) set the node state to NODE_FAULT, stop that port's IEP watchdog, flush the learning table, and report a ring fault. This path itself is correct.
(2) Beacon-timeout ISR unconditionally re-arms the watchdog
At the entry of EIP_DLR_beaconTimeoutISR_P0/P1 (icss_dlr.c:873 for P0, icss_dlr.c:1022 for P1):
/*Clear the interrupt*/
HW_WR_REG32(intStatusOffset, PORT0_WD_ISR_MASK);
...
/*Need to restart the Watchdog because it's not an auto load timer*/
EIP_DLR_disableIEPWatchdog(dlrHandle, PORT0_WATCH_DOG_ID);
EIP_DLR_enableIEPWatchdog(dlrHandle, PORT0_WATCH_DOG_ID); /* no link check */
The IEP watchdog is not an auto-reload timer, so it must be re-armed after expiry — hence the unconditional disable + enable. The problem is that this happens before any state/link check: if a port is already down (and will never see a Beacon again), one timeout interrupt that was already pending at link-down (or arrives right after) still enters the ISR and re-enables the watchdog; from then on that port keeps generating timeout interrupts every beaconTimeout (currently 4000 µs) with no way to stop itself.
(3) Backup takeover decision ignores the other port
In EIP_DLR_beaconTimeoutSupProcessing() (icss_dlr_sup.c:880 onwards), the "backup supervisor in FAULT state" branch uses only isSwitchOverPeriodActive to distinguish "first timeout" from "continuous timeout":
if(isSwitchOverPeriodActive == DLR_TRUE)
{
/* Second and subsequent timeouts: declare the active manager lost */
EIP_DLR_disableIEPWatchdog(dlrHandle, portNum - 1);
EIP_DLR_resetStateMachine(dlrHandle);
EIP_DLR_configSupervisor(dlrHandle); /* set ACTIVE, send Beacons */
isSwitchOverPeriodActive = DLR_FALSE;
}
else
{
/* First timeout: enter the switchover window, stop both watchdogs, enable only P0 */
EIP_DLR_disableIEPWatchdog(dlrHandle, PORT0_WATCH_DOG_ID);
EIP_DLR_disableIEPWatchdog(dlrHandle, PORT1_WATCH_DOG_ID);
EIP_DLR_enableIEPWatchdog(dlrHandle, PORT0_WATCH_DOG_ID);
isSwitchOverPeriodActive = DLR_TRUE;
}
Nothing in this decision checks whether the other port is still receiving Beacons (nor whether this port's own link is still valid).
4.2 Defect walk-through
With the two code paths above, a single-port link down is enough to trigger takeover:
-
The node-side port (P1/PHY1) goes down → state machine enters
NODE_FAULT, P1 watchdog is stopped; -
But the Beacon-timeout interrupt that was already pending at link-down (or arrives right after) still enters the ISR, which re-enables the P1 watchdog unconditionally at entry;
-
P1 is down, so no Beacon arrives and timeouts recur every 4 ms, re-entering the FAULT branch;
-
The first timeout arms the switchover window (
isSwitchOverPeriodActive = TRUE); the second executesresetStateMachine + configSupervisorand takes over as Active Supervisor; -
Meanwhile the Manager's Beacons on P2 never stopped. Per DLR semantics, the backup may take over only when neither port receives Beacons;
-
The firmware then clears the ACTIVE bit by precedence (backup 0 < manager 1), and the host runs
transitionToBackupSupervisor()on the idle→fault event, demoting back to Backup within about 1 ms.
4.3 Field event trace
[55347868] P1 LINK DOWN drvLS=01
[55347868] DLR_P1_BRK link=0 node=NORMAL en:status=0x00230000
[55347868] FDB FLUSH port1 entries=33
[55347868] DLR -> FAULT node=FAULT ring=2
[55347868] BCN_TO_P1 node=FAULT sm=0x0201
[55347872] BCN_TO_P1 node=FAULT sm=0x0201
[55347872] DLR RESET SM (previous node=FAULT)
[55347872] DLR RESET SM (previous node=IDLE)
[55347872] SUP -> ACTIVE EN=0x27 <-- spurious takeover
[55347873] ISR_P2 evts=0x0108 ... [RING_FAULT_TRANS STOP_BOTH_TMR ...]
[55347873] SUP -> BACKUP EN=0x23 <-- demoted about 1 ms later
-
Within the same observation window:
P1 LINK DOWN12 times,BCN_TO_P17 times, 2 spurious takeovers (about 1 in 6; sporadic). -
During the same period the Manager side shows no role change, so the churn happens only on the backup side.
4.4 Impact
-
Each hit produces a brief "dual supervisor / role flap" on the ring, and the Backup Manager sends its own Beacons;
-
The takeover path calls
EIP_DLR_dRAMInit(), which clears the ActiveSup MAC/IP in SMEM; the demotion path does not restore them;updateSupervisorDetails()may writeaddr.supMACAddressand the learning exception table as00:00:00:00:00:00; -
CIP status, the participant table, and ring-fault counters are repeatedly reset, degrading long-term DLR network stability.
4.5 Fix (local)
| Location (stock file) | Change |
|---|---|
icss_dlr.c beacon-timeout ISR (P0/P1) |
Check this port's live link (MDIO) first; on link down → stop the watchdog, clear TIMER_RUNNING/START_TIMER, set isrAck, and return without re-arming |
icss_dlr.c beacon-timeout ISR (P0/P1) |
Record genuine timeouts in lastBcnTimeoutTick[port] for the backup decision |
icss_dlr.c NORMAL branch + EIP_DLR_switchToNormal() |
Clear isSwitchOverPeriodActive so a previous fault's switchover window does not persist |
icss_dlr_sup.c backup/FAULT branch |
Add a peer-port criterion before takeover: other port link up + its watchdog running + no new timeout within an observation window → conclude "the other port still has Beacons", do not arm, do not take over |
icss_dlr_sup.c switchover arm branch |
Change from "stop both watchdogs, enable only P0" to "keep both watchdogs running" so the peer-port criterion is meaningful when evaluated |
icss_dlr.h |
Add lastBcnTimeoutTick[2] and two link/watchdog query declarations |
Genuine manager-loss scenarios are unaffected: when both ports go silent, the other port times out too, the criterion fails, and takeover still happens; worst-case takeover time increases from about 2× to 3× beaconTimeout.
4.6 Verification
With the new firmware under the same stress test (repeated node power cycling), the event trace no longer shows SUP -> ACTIVE; on P1 link down it shows BCN_TO P1 ignored (link down); legitimate takeovers occur only when the Manager side is truly silent.
5. Issue 2: PRU Firmware Host TX Queue (Q3) Deadlock
5.1 Stock queue protocol and driver TX path
(1) Queue descriptor (PRU1 DRAM, shared by host and firmware)
| Offset | Field | Written by | Meaning |
|---|---|---|---|
| +0 | rd_ptr |
PRU | firmware read pointer |
| +2 | wr_ptr |
host | host write pointer |
| +4 | busy_s |
host | host ownership flag |
| +5 | status |
firmware | bit0 busy_m (firmware busy), bit1 collision |
| +6/+7 | max_fill / overflow |
— | statistics |
In addition, each port has a collision slot at colStatusAddr+1/+2: when the target queue is busy the host moves the frame into the collision queue and writes (queue<<1)|1; the firmware clears it after consuming the collision queue.
(2) Driver TX path (MCU+ SDK icss_emac.c, ICSS_EMAC_txPacketEnqueue(), switch mode)
/* 1. Read status.busy_m */
collision_queue_selected = (temp & 0x00000100U); /* Check the "busy_m" bit */
if(collision_queue_selected != 0U)
{
hostStatPtr->txNumCollision++;
/* 2. Check whether this port's collision slot is already occupied */
col_queue_already_occupied = HW_RD_REG8(colStatusAddr + port);
if(col_queue_already_occupied != 0)
{
hostStatPtr->txDroppedPackets++;
hostStatPtr->txCollisionDroppedPackets++;
return ICSS_EMAC_ERR_COLLISION_FAIL; /* No space in collision queue */
}
}
...
/* 3. If free, put the frame into the collision queue and set the port's collision slot */
collision_status = (queuePriority << 1) | 0x0001U;
HW_WR_REG8(colStatusAddr + port, collision_status);
So: once the firmware leaves a queue's busy_m set indefinitely, every transmission takes the collision path; the host sets the collision slot on the first attempt, the firmware never clears it, and every subsequent transmission is dropped at step 2 with ERR_COLLISION_FAIL, while txDroppedPackets/txCollisionDroppedPackets keep increasing.
5.2 Field evidence
(1) TX counter deltas around a ping (Backup Manager port1; driver statistics plus TX return-code counts)
| Counter | Before ping | After ping | Delta |
|---|---|---|---|
txBC (driver statistic) |
32 | 32 | 0 |
txNumCollision (driver statistic) |
21818 | 22368 | +550 |
txCollisionDroppedPackets (driver statistic) |
21814 | 22364 | +550 |
ERR_TX_NO_LINK return count |
666073 | 666073 | 0 |
| other drops (queue full, etc.) return count | 10318 | 10318 | 0 |
That is: the link is up (no NO_LINK drops) and queues are not full, yet every transmission fails on the collision path — an exact match for the code path in 5.1. Over the same period port2 shows txBC +534 (all broadcasts leave via port 2, which faces the Manager's blocked port).
(2) Raw queue descriptor values in the fault state (read from PRU1 DRAM)
P1 Q0 wr=1088 rd=1088 used=0 busy=0 busym=0 ovf=0
P1 Q1 wr=11a4 rd=11a4 used=0 busy=0 busym=0 ovf=0
P1 Q2 wr=1328 rd=1328 used=0 busy=0 busym=0 ovf=0
P1 Q3 wr=15a4 rd=15a4 used=0 busy=0 busym=1 ovf=0
P1 colStatus=7 (= (3<<1)|1, Q3 collision slot asserted)
-
The deadlocked queue is P1 Q3 (
ICSS_EMAC_QUEUE4, the LwIP transmit queue); -
The queue itself is empty (
wr==rd,used=0) — no data backlog; the stuck part is the handshake bits; -
status.busy_m=1andcolStatus=7are held together while all other queues are normal.
(3) Formation time
[1918964] P1 LINK UP drvLS=101
[1919003] DLR -> NORMAL node=NORMAL ring=1
[1919919] *** TXQ P1 STUCK colStatus=7 busy_m=1 ***
The deadlock forms after link restore, DLR returning to NORMAL, and the host resuming enqueues to Q3 — i.e., the firmware fails to complete or properly abort an in-flight TX transaction across the link/ring state transition.
5.3 Ownership conclusion
| State | Set by | Cleared by | Notes |
|---|---|---|---|
Queue status.busy_m |
PRU firmware | PRU firmware | The stock driver only reads this bit (as a TX-path condition); no code writes busy_m |
Collision slot colStatusAddr+1/+2 |
Host (TX path, step 3 in 5.1) | PRU firmware (after consuming the collision queue) | The stock driver only writes non-zero values, never 0; +0/+3 are host acknowledgements to the firmware |
Queue wr/rd |
Host writes wr, firmware writes rd | — | In the field wr==rd: data is empty |
Therefore the indefinitely held busy_m=1 and collision-slot assertion are a firmware state machine that failed to converge, not a driver logic error; link events, port control registers, and DLR state transitions never make the firmware release these two states.
5.4 Impact
-
Q3 is the LwIP transmit queue; in this product EIP I/O data is sent over UDP/LwIP, sharing Q3 with ARP/ICMP → I/O and ping fail together (the PLC-side connection drops and data stops updating);
-
DLR/Beacon (firmware-internal path) and L2 forwarding are unaffected, which is why the Manager still sees the ring as healthy;
-
The receive direction is fine: the learning table still learns the peer MAC and the DLR state machine works.
5.5 Recovery validation and host-side workaround
-
Experiment: in the
busy_m=1 && wr==rdstate, having only the host clear thebusy_mbit instatus, clear thecolStatuscollision slot, and setwr = rdrestores transmission immediately (collision counters stop, ping and EIP I/O recover); -
This also proves the PHY, driver TX path, and LwIP are all fine; the only unreleased states are the two handshake states owned by the firmware;
-
A host-side workaround can therefore periodically check queue descriptors and, after several consecutive samples (about 600 ms) of
busy_m=1 && wr==rd, perform the same clear; the outage per event is shortened from "permanent" to under 1 s; -
Limits of the workaround: all Q3 transmissions still fail during the 600 ms window; if the PLC connection timeout is shorter than that, the connection still drops and reconnects; repeated deadlocks on the same port within a short interval require throttling the recovery action (to avoid repeatedly clearing firmware state).
Risk note: this clears firmware-owned handshake bits and is used only in the verified "
busy_m=1 && used=0" case; rebooting the device recovers from any anomaly.
5.6 Recommended TI fix
The PRU firmware should complete or roll back in-flight host TX transactions on link down/up, port reset, and DLR state transitions, releasing the queue busy_m and the corresponding collision-slot assertion; at minimum it must not leave a "queue empty but busy_m=1" state after link recovery. Attach the field data and descriptor records from sections 5.1–5.3.
6. Excluded Hypotheses
| Hypothesis | Evidence | Conclusion |
|---|---|---|
| Learning table points the peer MAC to the blocked port 2 | Learning-table query shows the peer MAC on PORT1; after clearing, it re-learns on PORT1 and ping still fails | Excluded |
| Port linkStatus cache stuck | Driver cache matches live PHY state; link state is correct after a real link down/up experiment | Excluded |
| ARP/L3 configuration | ARP table shows the correct Manager entry; local IP/mask correct | Excluded |
| Manager-side anomaly | Manager side: no role change, no Rapid Fault, no abnormal Announce Suppression | Excluded |
| Whole-host TX stall | Only Q3 is stuck; Q0–Q2 idle and the EIP real-time path healthy | Narrowed to Q3 |
7. Requests to Texas Instruments
-
Confirm Issue 1 and provide a driver fix: on a single-port link down, the backup supervisor's beacon-timeout ISR must not re-enable the watchdog of the down port, and the takeover decision must verify that neither port is receiving Beacons. Please confirm the change direction in
icss_dlr.c/icss_dlr_sup.cand provide an SDK release containing the fix. -
Fix Issue 2 in the PRU firmware: on link down/up, port reset, and DLR state transitions, complete or roll back any in-flight host TX transaction and release the queue
status.busy_mplus the corresponding collision-slot assertion. Please confirm whether "queue empty (wr==rd) whilebusy_m=1andcolStatusremains asserted" is a known firmware defect. -
Confirm interface semantics: is the
colStatusAddr+1/+2collision slot guaranteed to be cleared by the firmware after it consumes the collision queue? Isstatus.busy_monly cleared by the firmware's internal transmit-complete event?