feat(retimer): Add tools to turn off TX on unused lanes

retimer_lanes.sh turns TX off / on, or shows the state, for a lane map
of one Agera3 retimer on both the sys and the line side, by TX squelch
(default) or lane power-down. It refuses 'off' on lanes with RX PMD
lock unless --force is given, and reads the state back to print PASS
or FAIL.

unused_lanes.sh applies that to a built-in table of 14 retimer rows and
one direct-TH6 row (TSC31) on each switch board; TH6 rows go through
'port ... enable=' in the syncd container. --asic, --th6, --only and
--force select what it touches; it ends with ok / refused / failed
counts.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0131orciRKrejchsuDk6UpXk
This commit is contained in:
2026-10-21 09:01:55 +08:00
co-authored by Claude Opus 5.5
parent b652ec8072
commit aed2be66b2
2 changed files with 201 additions and 0 deletions
@@ -0,0 +1,98 @@
#!/bin/bash
# Turn TX off/on, or show state, for lanes of one Agera3 retimer on the sys and line side.
usage() {
cat <<USAGE
Usage: $0 <asic 0|1> <retimer-addr> <lane_map> <off|on|status> [--method squelch|power] [--force]
squelch TX squelch on sys and line side; lanes stay powered (works with the installed agera3)
power TX+RX lane power-down, the call SONiC makes on admin down; needs an agera3 with 'power'
--force allow 'off' on lanes that receive a signal
USAGE
exit 1
}
[ $# -ge 4 ] || usage
ASIC=$1; ADDR=$2; MAP=$3; ACT=$4; shift 4
METHOD=squelch; FORCE=0
while [ $# -gt 0 ]; do
case $1 in
--method) METHOD=$2; shift 2 ;;
--force) FORCE=1; shift ;;
*) usage ;;
esac
done
case $ASIC in 0|1) ;; *) usage ;; esac
case $ACT in off|on|status) ;; *) usage ;; esac
case $METHOD in squelch|power) ;; *) usage ;; esac
[[ $ADDR =~ ^0x[0-9a-fA-F]+$ && $MAP =~ ^0x[0-9a-fA-F]{1,2}$ ]] || usage
MAPV=$((MAP))
[ "$MAPV" -ge 1 ] || usage
ag() { sudo timeout 30 agera3 "$ASIC" "$ADDR" --no-cleanup "$@" </dev/null 2>&1; }
if [ "$METHOD" = power ] && ! sudo agera3 -h 2>&1 | grep -q -E '^\s+power '; then
echo "agera3 on this system has no 'power' command; use --method squelch"
exit 4
fi
lock_mask() {
ag rx-pmd-lock get side "$1" lane_map=0xff | awk '$2=="|" && $3=="LOCKED" {m += 2^$1} END {printf "%d", m}'
}
lane_tx() {
local lm
lm=$(printf '0x%02x' $((1 << $2)))
if [ "$METHOD" = squelch ]; then
ag squelch get tx "$1" lane_map=$lm | sed -n 's/.*squelch is \([A-Z]*\).*/\1/p' | awk '{print ($1 == "CLEAR") ? "on" : "off"}'
else
ag power get "$1" lane_map=$lm | awk -v l="$2" '$1 == l && $2 == "|" {print $3}'
fi
}
txoff_mask() {
local m=0 l s
for l in 0 1 2 3 4 5 6 7; do
[ $((MAPV >> l & 1)) = 1 ] || continue
s=$(lane_tx "$1" $l)
case $s in
off) m=$((m | 1 << l)) ;;
on) ;;
*) echo "unknown"; return ;;
esac
done
printf '0x%02x' $m
}
report() {
local side
for side in sys line; do
printf 'asic%s %-6s %-4s lane_map=0x%02x rx_lock=0x%02x tx_off=%s (%s)\n' \
"$ASIC" "$ADDR" "$side" "$MAPV" "$(lock_mask $side)" "$(txoff_mask $side)" "$METHOD"
done
}
[ "$ACT" = status ] && { report; exit 0; }
if [ "$ACT" = off ] && [ $FORCE = 0 ]; then
live=$(( ($(lock_mask sys) | $(lock_mask line)) & MAPV ))
if [ $live != 0 ]; then
printf 'REFUSED: asic%s %s lanes 0x%02x receive a signal; use --force to override\n' "$ASIC" "$ADDR" $live
exit 2
fi
fi
for side in sys line; do
if [ "$METHOD" = squelch ]; then
ag squelch set tx $side lane_map=$MAP $([ $ACT = off ] && echo enable || echo disable)
else
ag power set $side lane_map=$MAP $ACT
fi | grep -E 'FAIL|Error'
done
want=0x00; [ "$ACT" = off ] && want=$(printf '0x%02x' $MAPV)
rc=0
for side in sys line; do
got=$(txoff_mask $side)
printf 'asic%s %-6s %-4s lane_map=0x%02x tx_off=%s expected=%s (%s)\n' "$ASIC" "$ADDR" "$side" "$MAPV" "$got" "$want" "$METHOD"
[ "$got" = "$want" ] || rc=3
done
[ $rc = 0 ] && echo "PASS" || echo "FAIL"
exit $rc
@@ -0,0 +1,103 @@
#!/bin/bash
# Apply off/on/status to the unused retimer lanes and TH6 ports listed below, on each ASIC.
# Same rows apply to both switch boards. lanes: retimer lane_map, sys and line side.
# tsc: TH6 TSC as in HW docs and the "#TSCn" labels in th6-helios.config.yml.
# th6_ports: BCM shell port name or range; used by th6 rows, and by retimer rows with --th6.
UNUSED=$(cat <<'TABLE'
# kind target lanes refdes tsc th6_ports note
retimer 0x102 0xff U22 23 cd80-cd83 no used ports
retimer 0x142 0xff U32 38 cd148-cd151 no used ports
retimer 0x21 0xff U39 7 cd16-cd19 no used ports
retimer 0x240 0xff U67 54 cd212-cd215 no used ports
retimer 0x264 0xff U73 62 cd244-cd247 no used ports
retimer 0x48 0xff U82 15 cd48-cd51 no used ports
retimer 0x202 0xff U92 46 cd180-cd183 no used ports
retimer 0x122 0x30 U18 27 cd111 lanes 4,5 unused
retimer 0x248 0x30 U63 58 cd235 lanes 4,5 unused
retimer 0x2 0x30 U70 3 cd15 lanes 4,5 unused
retimer 0x40 0x30 U75 11 cd47 lanes 4,5 unused
retimer 0x162 0x30 U80 42 cd171 lanes 4,5 unused
retimer 0x222 0x30 U87 50 cd203 lanes 4,5 unused
retimer 0x62 0x30 U96 19 cd79 lanes 4,5 unused
th6 TSC31 - - 31 cd112-cd115 direct TH6 TSC, no retimer
# th6 TSC34 - - 34 cd139 direct TH6 TSC34 lanes 6,7; down and unused in full rack, not in HW list
TABLE
)
DIR=$(cd "$(dirname "$0")" && pwd)
usage() {
cat <<USAGE
Usage: $0 <off|on|status> [--asic 0|1|all] [--method squelch|power] [--th6] [--only <target>] [--force]
retimer rows: retimer lanes on sys and line side, via retimer_lanes.sh
th6 rows : TH6 ports in the th6_ports column, via the BCM shell
--th6 : also act on the th6_ports of retimer rows (TH6 ports feeding those retimer lanes)
--only : act on one row, matched by its target column (e.g. 0x102 or TSC31)
USAGE
exit 1
}
[ $# -ge 1 ] || usage
ACT=$1; shift
ASICS="0 1"; METHOD=squelch; TH6=0; ONLY=""; FORCE=""
while [ $# -gt 0 ]; do
case $1 in
--asic) case $2 in all) ASICS="0 1" ;; 0|1) ASICS=$2 ;; *) usage ;; esac; shift 2 ;;
--method) METHOD=$2; shift 2 ;;
--th6) TH6=1; shift ;;
--only) ONLY=$2; shift 2 ;;
--force) FORCE=--force; shift ;;
*) usage ;;
esac
done
case $ACT in off|on|status) ;; *) usage ;; esac
[ -x "$DIR/retimer_lanes.sh" ] || { echo "retimer_lanes.sh not found next to $0"; exit 1; }
bcm() { docker exec "syncd$1" bcmcmd -t 60 "$2" </dev/null 2>&1 | tr -d '\r'; }
ps_state() { bcm "$1" "ps $2" | sed -E 's/\( +/(/' | awk '$1 ~ /^cd[0-9]+\(/ {print $1, $2}'; }
th6_ports() {
local a=$1 act=$2 ports=$3 st
st=$(ps_state "$a" "$ports")
[ -n "$st" ] || { echo "asic$a th6 $ports: no such ports"; return 3; }
if [ "$act" = status ]; then
echo "$st" | sed "s/^/asic$a th6 /"
return 0
fi
if [ "$act" = off ] && [ -z "$FORCE" ] && echo "$st" | awk '$2 == "up"' | grep -q .; then
echo "REFUSED: asic$a th6 $ports has link up: $(echo "$st" | awk '$2 == "up" {printf "%s ", $1}')"
return 2
fi
bcm "$a" "port $ports enable=$([ "$act" = off ] && echo false || echo true)" >/dev/null
sleep 1
st=$(ps_state "$a" "$ports")
echo "$st" | sed "s/^/asic$a th6 /"
if [ "$act" = off ]; then
echo "$st" | awk '$2 != "!ena"' | grep -q . && return 3
else
echo "$st" | awk '$2 == "!ena"' | grep -q . && return 3
fi
return 0
}
mapfile -t ROWS < <(grep -v -E '^[[:space:]]*(#|$)' <<<"$UNUSED")
ok=0; refused=0; failed=0
for a in $ASICS; do
for row in "${ROWS[@]}"; do
read -r kind target lanes refdes tsc ports note <<<"$row"
[ -n "$ONLY" ] && [ "$target" != "$ONLY" ] && continue
echo "== asic$a $kind $target refdes=$refdes TSC$tsc th6=$ports: $note"
rc=0
case $kind in
retimer)
"$DIR/retimer_lanes.sh" "$a" "$target" "$lanes" "$ACT" --method "$METHOD" $FORCE; rc=$?
if [ $TH6 = 1 ] && [ "$ports" != "-" ] && [ $rc != 2 ]; then
th6_ports "$a" "$ACT" "$ports" || rc=$?
fi ;;
th6) th6_ports "$a" "$ACT" "$ports"; rc=$? ;;
*) echo "unknown kind '$kind'"; rc=1 ;;
esac
case $rc in 0) ok=$((ok + 1)) ;; 2) refused=$((refused + 1)) ;; *) failed=$((failed + 1)) ;; esac
done
done
echo "== $ACT done: ok=$ok refused=$refused failed=$failed"
[ $failed = 0 ]