17 Commits
Author SHA1 Message Date
etwenandClaude Opus 5 419b680980 feat(publish): Add publish.sh and sync docs for V1.0.3
publish/publish.sh packages src/Script_ABC_Blanton into
publish/Script_ABC_Blanton_<Ver>/, with -z for a zip, -f to overwrite,
-n for a dry run.

The version is read from Script A's "; Version :" header rather than
passed in, so the folder name cannot disagree with what the tester sees
on opening the macro. The header is CRLF, so the carriage return is
stripped -- left in, it becomes part of the directory name. Captured
logs are excluded (they carry DUT serials) but the empty logs/ is kept
because logopen writes there. .gitignore now admits the tool while
still ignoring its output.

config.ttl: testcase ENV, margin scan off for this run.

Docs brought up to date with the 14 commits since 8aa5c90 -- SWB
channel/address map, the traffic and BMC tooling, line-ending policy,
and the exec-bit and tail -f traps.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014RetWKFZFG1ZHcitQAyhwM
2026-08-21 09:33:06 +08:00
etwenandClaude Opus 5 4ed283d0ca feat(ttl): Wait for thermal sensors before taking baseline data
New utils/wait_init.ttl polls `show platform temperature` every 10 s
until it stops reporting "Thermal Not detected", so Script A does not
record a baseline while pmon is still coming up. Script A calls it right
after `show boot` and bumps to V1.0.3.

The include pointed at utils/wait_thermal.ttl while the file is
wait_init.ttl -- as Script A's own V1.0.3 history line says. Corrected;
Tera Term aborts the macro when an include cannot be opened, so this
would have stopped Script A at line 59 on the first run.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014RetWKFZFG1ZHcitQAyhwM
2026-08-21 09:17:50 +08:00
etwenandClaude Opus 5 491e807cf8 feat(ttl): Log 100G port status, run traffic on both switch units
Script A and B: `show interfaces status Ethernet513,Ethernet514` before
the traffic stage, so the uplink state is on record next to the counters
it explains. In B this takes the slot the empty "eye measurement"
placeholder held.

Script B: drop -u 0 from the traffic calls. A and C already defaulted to
TL_UNITS="0 1", so B was pinned to unit 0 while C stopped both -- the
soak only ever loaded one switch board, and C's stop hit a unit that was
never initialised. All three scripts now cover both units.

Script B: post-clear settle 10 s -> 15 s, matching A.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014RetWKFZFG1ZHcitQAyhwM
2026-08-20 23:51:28 +08:00
etwenandClaude Opus 5 18ce7a1a73 fix(ttl): Dump BMC logs with cat instead of the blocking tail
`bmc_monitor.sh tail` runs `tail -n 50 -f`, which never returns, so
Script C stopped at the first call and never reached CHECK Stress
results, the traffic counters, or the closing messagebox. Stopping the
monitors first made it worse: with the file no longer growing, tail -f
just waits forever.

Read the two logs directly instead, so the whole BMC capture lands in
the master log and the macro carries on.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014RetWKFZFG1ZHcitQAyhwM
2026-08-20 11:14:34 +08:00
etwenandClaude Opus 5 d1b5cfea0b feat(ttl): Wire BMC monitors into Script B/C, log BMC version in A
Script A: dump the BMC banner via `bmc-manager run 'cat /etc/issue'`,
and add the missing `wait` before `show boot`.

Script B: start bmc_monitor_ddr.sh and bmc_monitor.sh alongside the
host stress load.

Script C: stop both and dump their logs into the master log.

bmc_monitor.sh: INTERVAL_SEC 5 -> 10.

Known blockers, filed here so they are not lost -- this wiring does not
work yet as written:

1. `bmc_monitor.sh tail` runs `tail -n 50 -f`, which never returns.
   Script C blocks on the first one and never reaches CHECK Stress
   results, the traffic counters, or the final messagebox. It needs a
   non-following dump (cat) instead.

2. Both scripts are mode 100644 and are the first .sh here meant to be
   executed rather than sourced. do_start re-execs "$SCRIPT_PATH"
   __daemon directly, so the execute bit is required even when invoked
   through bash -- and a Windows -> USB -> DUT copy cannot carry it.
   A chmod +x is needed before the start calls, as Script B already
   does for mlucas-avx2.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014RetWKFZFG1ZHcitQAyhwM
2026-08-20 11:12:03 +08:00
etwenandClaude Opus 5 5e63e3da6b fix(bmc): Give bmc_monitor_ddr its own log so the two can run together
Both scripts defaulted to LOG_NAME="bmc_poll.log", and PID_FILE is
derived from it, so the monitor and the DDR stress shared one log and
one pid file -- while the whole point of the split is to run them at
the same time.

The failure was quiet rather than loud: is_running greps the pid's
cmdline for its own SCRIPT_NAME, so the second script did not recognise
the first as running and started anyway. prepare_log_on_start then
truncated the log the first one was writing (START_LOG_MODE=new), both
raced on the pid file, and a later stop could only reap whichever wrote
it last, orphaning the other.

bmc_monitor_ddr.sh now uses bmc_ddr.log, which also moves its pid file
to bmc_ddr.pid. Both remain covered by the *.log ignore rule.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014RetWKFZFG1ZHcitQAyhwM
2026-08-20 11:03:44 +08:00
etwenandClaude Opus 5 855b7a6e30 feat(bmc): Split DDR stress out of bmc_monitor into bmc_monitor_ddr.sh
bmc_monitor.sh was doing two unrelated jobs: sampling `free -m` every
few seconds, and running `memtester 500M 1` as a stress load. They want
different timeouts -- a monitor should give up in 30 s, a memtester
pass legitimately runs for minutes -- and mixing them meant either
cutting memtester short or letting a hung sample stall the loop.

bmc_monitor.sh now polls `free -m` only, keeping CMD_TIMEOUT_SEC=30.
bmc_monitor_ddr.sh is the stress half: memtester only, with
CMD_TIMEOUT_SEC raised to 3600. The two are otherwise identical.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014RetWKFZFG1ZHcitQAyhwM
2026-08-20 11:00:04 +08:00
etwenandClaude Opus 5 310f897067 feat(bmc): Add bmc_monitor.sh, log build/uptime/reboot-cause in A and C
bmc_monitor.sh (V1.1.0): polls the BMC from the SONiC host via
`bmc-manager run`, appending to a rotating log. Runs detached, so no
interactive SSH session to the BMC is needed and the BMC's busybox
toolchain never comes into play. Subcommands start/stop/status/fg/tail/
clear; the polled command list, interval, per-command timeout and log
rotation are configured at the top of the file. START_LOG_MODE defaults
to "new", so one run means one log. Default COMMANDS are `free -m` and
`memtester 500M 1`, i.e. the BMC is memory-stressed alongside the host
soak.

Script A: `show boot` after the time sync to record the image the DUT
booted, and `show uptime` at the end of the traffic baseline.

Script C: `show reboot-cause` and `show uptime` after the traffic
report, so an unplanned reset during the soak is visible in the
closing snapshot rather than only in dmesg.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014RetWKFZFG1ZHcitQAyhwM
2026-08-20 10:47:35 +08:00
etwenandClaude Opus 5 fedf697977 docs(margin): Realign channel-pairing comments with bench wiring
The values were corrected in ababb2c but every comment describing the
pairing still said the old one, leaving four confs where the comment
contradicted the line it sat on:

    CB_I2C_CH=8           # CB F3 Ch6 = SWB0 CONN13/CONN14 ...

That reads as a typo waiting to be "fixed" back to 6, which would undo
a bench-verified value. Comments now state the real pairing:

    Ch6 = SWB0 CONN14/CONN16    Ch8 = SWB0 CONN13/CONN15
    Ch7 = SWB1 CONN14/CONN16    Ch9 = SWB1 CONN13/CONN15

Same map corrected in margin.sh's header (two places).

Ref/margin_command_trace.md gets a staleness banner rather than an
edit. Its 1223 expanded cb_pmbus_* commands were generated from the old
values - 487 of them use addresses that no longer exist (0x5D, 0x5F,
0x63, 0x65) - so patching the headings would leave the bodies wrong and
look authoritative. The banner carries the correct table and says the
SWB chapters need regenerating; chapters 1-4 are transport-independent
and remain valid.

Comments only: no CB_I2C_CH value and no CHIPS address was touched.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014RetWKFZFG1ZHcitQAyhwM
2026-08-19 12:19:44 +08:00
etwenandClaude Opus 5 ababb2c3d1 fix(margin): Correct SWB I2C channels and LTC2977 addresses from bench
Verified against the DUT. Two things were wrong in the SWB confs.

Channel assignment: CONN13/CONN15 and CONN14/CONN16 were paired the
wrong way round. The bench wiring is

    SWB0  Ch6 = CONN14/CONN16    Ch8 = CONN13/CONN15
    SWB1  Ch7 = CONN14/CONN16    Ch9 = CONN13/CONN15

LTC2977 addresses: the high half of each LTC2980 was recorded one step
above the low half (0x5C/0x5D, 0x62/0x63). These are 7-bit addresses
derived from 8-bit pairs, so they step by two: 0x5C/0x5E and 0x62/0x64.

This also clears the collision noted in v2.6.0, where SWB0_CONN13 chip2
and SWB0_CONN14 chip1 both landed on 0x5E on Ch6. Each channel now
carries two disjoint address pairs (0x5C/0x5E and 0x62/0x64), verified
collision-free across all eight SWB confs.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014RetWKFZFG1ZHcitQAyhwM
2026-08-19 12:10:39 +08:00
etwenandClaude Opus 5 a9c3dafc22 fix(traffic): Stop report saturating counters at INT32_MAX
Every value in the report printed as 2147483647 because grp() rendered
counters with sprintf("%d", v). The DUT's awk casts to a 32-bit int for
%d, so any counter above 2^31-1 saturates -- and a real line-speed run
is around 7e11, three orders of magnitude past that. Formatting with
%.0f keeps the value in awk's double domain, which is exact to 2^53.

The FAIL delta line had the same defect (%+d): a dead port makes the
delta as large as the counter itself, so the one number needed to
diagnose the failure would have been the one that saturated.

Verdicts were never affected. The cross-check compares the TX[]/RX[]
doubles directly and only the display path goes through grp(), so past
PASS results stand; only the printed numbers were wrong.

Verified against a captured run (secret/Counter.log, 108 pairs): values
now match the raw MIB_TPOK/MIB_RPOK lines exactly, and the loopback
identity holds (cd0.TX == cd32.RX, cd0.RX == cd32.TX).

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014RetWKFZFG1ZHcitQAyhwM
2026-08-19 09:21:16 +08:00
etwenandClaude Opus 5 4088a828c6 feat(ttl): Add traffic baseline to Script A, tune waits and unit args
Script A: the TRAFFIC-SETUP-STAGE placeholder is now a full loopback
round (ps, init, clear, show, start, stop, report) with 15 s settles, so
the run has a traffic baseline before the soak starts.

Script B: widen the post-clear settle from 3 s to 10 s.
Script C: drop -u 0 from stop/report so both switch units are covered.

Note: Script B still pins -u 0 while A and C now default to both units.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014RetWKFZFG1ZHcitQAyhwM
2026-08-19 09:02:41 +08:00
etwenandClaude Opus 5 afb7b1e283 fix(margin): Convert margin.sh and settings to LF, pin line endings
margin.sh and settings/*.conf carried CRLF into the repo, so sourcing
them on the SONiC DUT failed outright:

    -bash: $'\r': command not found
    margin.sh: line 126: syntax error near unexpected token `$'{\r''

settings/*.conf was the quieter half of the same bug: margin_init
sources them, and an unquoted assignment such as CB_I2C_CH=6 became
"6\r", so the SWB transport would have driven I2C with a malformed
channel number.

Content is unchanged - the diff is carriage returns only, and bash -n
passes on every converted file.

Add .gitattributes so this cannot come back: *.sh/*.conf/*.py/*.txt/*.md
are pinned to LF, *.ttl stays CRLF (Tera Term runs on Windows only) and
office formats are marked binary.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014RetWKFZFG1ZHcitQAyhwM
2026-08-18 16:34:55 +08:00
etwenandClaude Opus 5 fd35857d80 refactor(margin): Drop duplicated cb_i2c/pcimem copies
margin.sh v2.6.0 resolves the SWB backend via _swb_find_backend, which
searches the parent Blanton_Script/ dir, so the copies bundled inside
LTC2980_Margin_Script/ are dead weight and would silently drift from
the shared originals.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014RetWKFZFG1ZHcitQAyhwM
2026-08-18 10:38:59 +08:00
etwen cdf38a09ae feat(margin): Wire SWB transport to shared cb_i2c package, bump to v2.6.0
margin.sh (swb path only, CB/i2c path untouched):
- Search blanton_cb_i2c.sh / blanton_fpga_pcimem.sh in the parent
  Blanton_Script/ dir as well; the old code only looked next to margin.sh
  where those files do not exist, so TRANSPORT=swb always failed to load.
- _swb_setup: validate CB_I2C_CH, cb_i2c_init <ch> 0x88 0x00 once per
  session, then CB_I2C_AUTO_INIT=0 to drop 4 pcimem writes per transfer.
- Probe every LTC2977 in CHIPS with cb_pmbus_read <ch> <addr> 0x00 1.
- Check cb_pmbus_read/write return codes; a NACK is now reported instead
  of silently becoming Vout=0.0000.
- Fix DEBUG_MODE collision with blanton_fpga_pcimem.sh: pcimem tracing is
  gated by MARGIN_SWB_PCIMEM_DEBUG via _swb_call.
- Add margin_swb_info / probe / scan / reset / sem wrappers.

settings: fix CB_I2C_CH per bench wiring (CONN14/CONN15 were swapped)
  Ch6 = SWB0 CONN13/14, Ch8 = SWB0 CONN15/16,
  Ch7 = SWB1 CONN13/14, Ch9 = SWB1 CONN15/16

Ref/margin_command_trace.md: new command trace reference - every i2c /
  cb_pmbus command issued by margin_init, margin_status and
  margin_apply_profile combo_high3, expanded per channel for the CB conf
  and all 8 SWB confs, with LINEAR16 values and register meanings.

Known issue documented, not changed: SWB0_CONN13 chip2 (0x5E) collides
with SWB0_CONN14 chip1 (0x5E) now that both sit on Ch6; SWB1 pattern
suggests 0x5D, pending hardware confirmation.
2026-08-18 10:38:59 +08:00
etwenandClaude Opus 5 8aa5c90166 docs(arch): Sync ARCHITECTURE and CLAUDE with 20260817 changes
Bring both docs in line with the five commits pulled from Gitea.

- BDF: pcimem V1.5.0 auto-detects the CB FPGA BDF, so the "edit the four
  lines per DUT" guidance was inverted. Documented the detection order
  and fpga_rescan, and flagged that utils/show_pcie_error_reg_*.ttl
  still hard-codes its 7 BDFs -- the footgun is only half gone
- Traffic: documented blanton_traffic_linespeed.sh (bcmcmd SWB loopback,
  per-pair TX/RX cross-check) and tools/bcm_mibpair_report + the three
  loopback command tables; noted the route taken differs from the
  SONiC-CLI approach Phase 4 originally planned
- Stress: stress_hhmd and stress_pcie were removed on 08-17, so the job
  count is 8 not 10; soak loop now pulls full setup_pmon (9 items)
- Constraints: added the bcmcmd exit-0 and stdin-eating gotchas
- Phases: ticked 10 completed items, corrected Phase 3/4 acceptance
  criteria, and moved BDF auto-detect out of Future Extensions

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014RetWKFZFG1ZHcitQAyhwM
2026-08-17 21:51:17 +08:00
etwenandClaude 224ddbd21c feat(traffic): Report all pairs and add -tx/-length aliases
V0.4.0 -- report now lists ALL 108 pairs, PASS / FAIL / NA alike,
one line per port (two lines per pair). Missing counters render as
'-', so -a becomes the default (the flag is still accepted) and the
long "# NA (no counters): ..." summary line is dropped since every
NA pair is now a visible row. The table printer is generalised to a
column array with per-column alignment, keeping the pad-then-colour
order so ANSI codes do not break the column widths. This is the only
intentional difference from tools/bcm_mibpair_report_V1.1.0.py.

V0.3.3 -- accept -tx / -length as aliases for -c / -l so a tx burst
can be written the way the bcm command reads
("start -u 0 -tx 100 -length 512"); both are validated as positive
integers. Fix --dry-run printing nothing for init/start/stop/report
since V0.3.1, where the new bcm wrapper discarded the "[DRY ]" lines.
Offline report notes now go to stderr so the table on stdout stays
pipe/diff clean.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-08-17 21:20:36 +08:00
26 changed files with 4994 additions and 1808 deletions
+29
View File
@@ -0,0 +1,29 @@
# =============================================================================
# 換行符政策
# -----------------------------------------------------------------------------
# 這個 repo 同時被 WindowsTera Term 端編輯)與 SONiC/LinuxDUT 端執行)使用,
# 所以必須明確指定,不能靠各機器的 core.autocrlf。
#
# 踩過的坑:margin.sh 帶著 CRLF 進 reposcp 到 DUT 後 bash 直接掛:
# -bash: $'\r': command not found
# syntax error near unexpected token `$'{\r''
# settings/*.conf 同樣危險 —— margin_init 是用 source 讀它們的,未加引號的
# 賦值(例如 CB_I2C_CH=6)會變成 "6\r"SWB transport 會拿著壞掉的通道號去打 I2C。
# =============================================================================
# 在 DUT / Linux 上執行或被 source 的,一律 LF
*.sh text eol=lf
*.conf text eol=lf
*.py text eol=lf
*.txt text eol=lf
*.md text eol=lf
# Tera Term macro 只在 Windows 上跑,維持 CRLF;標成 -text 讓 git 原樣保存,
# 避免對既有檔案產生大量純換行符的假 diff
*.ttl -text
# 二進位,不要做任何轉換
*.xlsx binary
*.docx binary
*.pdf binary
*.h5 binary
+2 -1
View File
@@ -12,9 +12,10 @@ For_AI/
!**/logs/.gitkeep !**/logs/.gitkeep
!**/Logs/.gitkeep !**/Logs/.gitkeep
# 打包輸出 # 打包輸出:產出不進 git,打包工具本身要進
publish/* publish/*
!publish/.gitkeep !publish/.gitkeep
!publish/publish.sh
# Office 暫存檔 # Office 暫存檔
~$* ~$*
+201 -66
View File
@@ -11,8 +11,8 @@
| Script | 階段 | 做什麼 | | Script | 階段 | 做什麼 |
|--------|------|--------| |--------|------|--------|
| `1_Blanton_Script_A.ttl` | **Pre-test / Baseline** | 登入、對時、載入 shell 工具、清 PCIe AER、抓 PCIe tree / margin / PMON / dmesg 基線 | | `1_Blanton_Script_A.ttl` | **Pre-test / Baseline** | 登入、對時、載入 shell 工具、清 PCIe AER、抓 PCIe tree / margin / PMON / dmesg 基線 |
| `2_Blanton_Script_B.ttl` | **Stress / Soak** | 開 CPU / DDR / SSD / USB / PCIe / I2C 壓力程序,之後進無限迴圈每輪抓 fan / temp / margin | | `2_Blanton_Script_B.ttl` | **Stress / Soak** | 開 CPU / DDR / SSD / USB 壓力程序 + 啟動 SWB loopback 線速流量,之後進無限迴圈每輪抓完整 PMON / margin |
| `3_Blanton_Script_C.ttl` | **Post-test / Verdict** | 殺掉壓力程序、再抓一次 PMON / dmesg / PCIe AER,比對 stress log 判定 | | `3_Blanton_Script_C.ttl` | **Post-test / Verdict** | 殺掉壓力程序、停流量並出 per-pair TX/RX 報表、再抓一次 PMON / dmesg / PCIe AER,比對 stress log 判定 |
DUT 端的實際取數能力則來自一組 **bash 工具庫**`Blanton_Script/`),這一層才是真正碰硬體的地方: DUT 端的實際取數能力則來自一組 **bash 工具庫**`Blanton_Script/`),這一層才是真正碰硬體的地方:
CB FPGA 的 PCIe BAR`pcimem`)→ FPGA 內建 I2C / VI2C master → 板上 VRM、溫度感測器、 CB FPGA 的 PCIe BAR`pcimem`)→ FPGA 內建 I2C / VI2C master → 板上 VRM、溫度感測器、
@@ -36,8 +36,11 @@ LTC2980 margin 控制器。TTL 只負責「按順序 `sendln` 並把畫面收進
| I2C / PMBus 路徑 | CB FPGA F3 OpenCores I2C master、CB FPGA F3 VI2C proxy、CPU 原生 bus`i2cset`/`i2cget` | | I2C / PMBus 路徑 | CB FPGA F3 OpenCores I2C master、CB FPGA F3 VI2C proxy、CPU 原生 bus`i2cset`/`i2cget` |
| 電源 margin | LTC2980= 2 × LTC2977over PMBusLINEAR16 編碼 | | 電源 margin | LTC2980= 2 × LTC2977over PMBusLINEAR16 編碼 |
| 錯誤計數來源 | Linux PCIe AER sysfs`aer_dev_*`)、EDAC sysfs`dimm_ce/ue_count` | | 錯誤計數來源 | Linux PCIe AER sysfs`aer_dev_*`)、EDAC sysfs`dimm_ce/ue_count` |
| 平台監控 | SONiC `show platform *`pmon container | | 平台監控 | SONiC `show platform *`pmon container,含 `leak status` / `leak channels` |
| 壓力測試 | `mlucas-avx2`AMD CPU)、`~/hammer/tools/stress_*.py`DDR / SSD / USB / PCIe / I2C | | BMC 存取 | host 端 `bmc-manager run "<cmd>"`(不需對 BMC 開 SSH,繞開其 busybox 工具鏈 |
| 壓力測試 | `mlucas-avx2`AMD CPU)、`~/hammer/tools/stress_{mem,ssd,usb}.py` |
| 資料面流量 | `bcmcmd`Broadcom drivshellVLAN loopback + `tx` burstswitch unit 0 / 1 |
| 流量報表 | DUT 上 awk`blanton_traffic_linespeed report`);離線 `python3 tools/bcm_mibpair_report_V1.1.0.py` |
| 版本控制 | GitNAS + Gitea 私有 remote**不推 GitHub,含客戶 NDA 資料** | | 版本控制 | GitNAS + Gitea 私有 remote**不推 GitHub,含客戶 NDA 資料** |
> ⚠️ **無編譯步驟。** TTL 與 bash 都是直譯執行,「build」等同「把 `src/Script_ABC_Blanton/` 複製到 > ⚠️ **無編譯步驟。** TTL 與 bash 都是直譯執行,「build」等同「把 `src/Script_ABC_Blanton/` 複製到
@@ -70,16 +73,19 @@ LTC2980 margin 控制器。TTL 只負責「按順序 `sendln` 並把畫面收進
│ ├ blanton_icb_vi2c.sh vi2c_* (ICB/PDB proxy) │ │ ├ blanton_icb_vi2c.sh vi2c_* (ICB/PDB proxy) │
│ ├ blanton_temp_sensor.sh temp_cb / temp_icb / temp_all │ │ ├ blanton_temp_sensor.sh temp_cb / temp_icb / temp_all │
│ ├ blanton_pwr_data.sh pwr_data / pwr_data_pdb │ │ ├ blanton_pwr_data.sh pwr_data / pwr_data_pdb │
│ ├ blanton_traffic_linespeed.sh │
│ │ init/clear/show/start/stop/ps/report (bcmcmd) │
│ └ LTC2980_Margin_Script/ margin_init / margin_status ... │ │ └ LTC2980_Margin_Script/ margin_init / margin_status ... │
│ │ │ │
│ ~/hammer/tools/ 壓力程序(mlucas-avx2, stress_*.py │ ~/hammer/tools/ 壓力程序(mlucas-avx2, stress_*.py
SONiC CLI show platform fan/temperature/... bcmcmd -n 0|1 Broadcom drivshellVLAN loopback / tx
│ SONiC CLI show platform fan/temperature/leak/... │
│ Linux sysfs aer_dev_* / edac mc0/rank* │ │ Linux sysfs aer_dev_* / edac mc0/rank* │
└───────────────────────┬──────────────────────────────────────┘ └───────────────────────┬──────────────────────────────────────┘
│ pcimem /sys/bus/pci/devices/0000:03:00.<fn>/resource0 │ pcimem /sys/bus/pci/devices/0000:03:00.<fn>/resource0
┌──────────────────────────────────────────────────────────────┐ ┌──────────────────────────────────────────────────────────────┐
│ CB FPGA (PCIe EP4 個 Function) │ CB FPGA (PCIe EP4 個 FunctionBDF 於 source 時自動偵測)
│ F0 local regsRST_CAUSE_REG 0xD90 / SWB 0xD94,0xD98 │ │ F0 local regsRST_CAUSE_REG 0xD90 / SWB 0xD94,0xD98 │
│ F3 I2C master Ch0..Ch17 (base 0x300, stride 0x20) │ │ F3 I2C master Ch0..Ch17 (base 0x300, stride 0x20) │
│ F3 VI2C→ICB Ch0..Ch4 (base 0x780, stride 0x20) │ │ F3 VI2C→ICB Ch0..Ch4 (base 0x780, stride 0x20) │
@@ -101,16 +107,24 @@ Blanton_TTL_Script/
├── CLAUDE.md # 專案記憶 & 給 Claude 的指令 ├── CLAUDE.md # 專案記憶 & 給 Claude 的指令
├── ARCHITECTURE.md # 本架構文件 ├── ARCHITECTURE.md # 本架構文件
├── .gitignore # secret/ + For_AI/ + logs 規則 ├── .gitignore # secret/ + For_AI/ + logs + publish 產出
├── .gitattributes # 換行符政策:.sh/.conf 釘 LF、.ttl 保持 CRLF
├── docs/ # 規格與狀態文件(客戶提供 / 專案產出) ├── docs/ # 規格與狀態文件(客戶提供 / 專案產出)
│ ├── Blantons_FPGA_Registers.md # FPGA 暫存器規格(markdown 正本,查 offset 用) │ ├── Blantons_FPGA_Registers.md # FPGA 暫存器規格(markdown 正本,查 offset 用)
│ ├── Blantons_FPGA_Registers_draft.docx # 同上,客戶原始 docx │ ├── Blantons_FPGA_Registers_draft.docx # 同上,客戶原始 docx
│ ├── Blantons_FPGA_Registers_Map_draft.xlsx │ ├── Blantons_FPGA_Registers_Map_draft.xlsx
│ ├── Blantons_PCIe_AER_and_DDR_EDAC_checks.pdf # AER / EDAC 檢查點依據 │ ├── Blantons_PCIe_AER_and_DDR_EDAC_checks.pdf # AER / EDAC 檢查點依據
── TTL_Script_Blanton_Status_20260814.xlsx # ⭐ 三支腳本逐項 StatusOK / On-Going + Owner ── TTL_Script_Blanton_Status_20260814.xlsx
│ └── TTL_Script_Blanton_Status_20260817.xlsx # ⭐ 最新:三支腳本逐項 StatusOK / On-Going + Owner
├── tools/ # Host 端輔助腳本log 解析、報表產生) ├── tools/ # Host / 離線端輔助腳本
│ ├── bcm_mibpair_report_V1.1.0.py # 離線解析 drivshell log → per-pair TX/RX + PASS/FAIL
│ ├── traffic_loopback_vlan_setting.txt # bcmcmd VLAN 30..137 建立 + 成對 pbmcdN <-> cdN+32
│ ├── traffic_loopback_start.txt # bcmcmd tx 100 length=512 VLantag=<vid>
│ └── traffic_loopback_vlan_remove.txt # bcmcmd vlan remove <vid> pbm=...
│ # ↑ 三份為手動貼上用的原始指令表,
│ # blanton_traffic_linespeed.sh 是其 bash 版
├── secret/ # 🚫 gitignored — DUT 密碼、per-unit BDF、COM 設定 ├── secret/ # 🚫 gitignored — DUT 密碼、per-unit BDF、COM 設定
│ ├── README.md # ✅ committed — 用途索引 │ ├── README.md # ✅ committed — 用途索引
@@ -119,7 +133,9 @@ Blanton_TTL_Script/
├── For_AI/ # 🚫 gitignored — AI 協作素材(波形截圖、草稿) ├── For_AI/ # 🚫 gitignored — AI 協作素材(波形截圖、草稿)
├── publish/ # 可交付的打包輸出(Blanton_Script_ABC_v<ver>_<date>/ + zip ├── publish/
│ ├── publish.sh # ✅ committed — 打包工具(版號讀 Script A 檔頭)
│ └── Script_ABC_Blanton_<Ver>/ # 🚫 產出,gitignored-z 另出同名 .zip
└── src/ └── src/
└── Script_ABC_Blanton/ # ← Tera Term 的工作目錄(整包給測試員) └── Script_ABC_Blanton/ # ← Tera Term 的工作目錄(整包給測試員)
@@ -131,10 +147,12 @@ Blanton_TTL_Script/
├── utils/ # 可 include 的取數片段(TTL,無狀態、只 wait+sendln ├── utils/ # 可 include 的取數片段(TTL,無狀態、只 wait+sendln
│ ├── pcie_bus.ttl # lspci -tvvv / -vv │ ├── pcie_bus.ttl # lspci -tvvv / -vv
│ ├── setup_pmon.ttl # show platform summary/fan/temperature/psu/voltage/current/ssd │ ├── setup_pmon.ttl # show platform summary/fan/temperature/psustatus/voltage/
│ │ # current/ssdhealth/leak status/leak channels9 項)
│ ├── show_dmesg.ttl # date + dmesg grep error/fail/warning │ ├── show_dmesg.ttl # date + dmesg grep error/fail/warning
│ ├── show_margin_status.ttl # source margin_status_all.sh(掃 9 顆 LTC2980 │ ├── show_margin_status.ttl # source margin_status_all.sh(掃 9 顆 LTC2980
│ ├── kill_all_process.ttl # kill $(jobs -p) │ ├── kill_all_process.ttl # kill $(jobs -p)
│ ├── wait_init.ttl # 輪詢 show platform temperature 直到 pmon 起來
│ ├── show_pcie_error_reg.ttl # 舊的合併版(已被下列 per-device 版取代) │ ├── show_pcie_error_reg.ttl # 舊的合併版(已被下列 per-device 版取代)
│ ├── show_pcie_error_reg_TH6_SWB0.ttl # EP 01:00.0 + RP 00:01.1 │ ├── show_pcie_error_reg_TH6_SWB0.ttl # EP 01:00.0 + RP 00:01.1
│ ├── show_pcie_error_reg_TH6_SWB1.ttl # EP 02:00.0 + RP 00:01.2 │ ├── show_pcie_error_reg_TH6_SWB1.ttl # EP 02:00.0 + RP 00:01.2
@@ -152,12 +170,20 @@ Blanton_TTL_Script/
├── blanton_icb_vi2c.sh # CB F3 VI2C proxy → ICB/PDBCh0~Ch4 ├── blanton_icb_vi2c.sh # CB F3 VI2C proxy → ICB/PDBCh0~Ch4
├── blanton_temp_sensor.sh # TMP75 / TMP432 讀溫(CB Ch10 mux + ICB VI2C Ch2 ├── blanton_temp_sensor.sh # TMP75 / TMP432 讀溫(CB Ch10 mux + ICB VI2C Ch2
├── blanton_pwr_data.sh # SWB VRM + PDB brick 的 Vin/Vout/Iout/Temp 表 ├── blanton_pwr_data.sh # SWB VRM + PDB brick 的 Vin/Vout/Iout/Temp 表
├── blanton_traffic_linespeed.sh # SWB loopback 線速流量(bcmcmd unit 0/1
│ # init/clear/show/start/stop/ps/report/run
├── bmc_monitor.sh # BMC 監控:週期跑 free -m,寫輪替 log
├── bmc_monitor_ddr.sh # BMC DDR 壓力:memtester,逾時 3600s
│ # ↑ 兩支都要 chmod +x 才能跑(見 Key Constraints
└── LTC2980_Margin_Script/ # 電壓 margin 子專案(另有獨立 repo └── LTC2980_Margin_Script/ # 電壓 margin 子專案(另有獨立 repo
├── margin.sh # margin_init/status/set/apply_profile/save ├── margin.sh # margin_init/status/set/apply_profile/save
├── settings/*.conf # 一個 .conf = 一顆 LTC2980CB + SWB0/1 × CONN13~16 ├── settings/*.conf # 一個 .conf = 一顆 LTC2980CB + SWB0/1 × CONN13~16
├── profiles/*.conf # 16-ch 批次組合(comboA/B、high3/5、low3/5、normal、off ├── profiles/*.conf # 16-ch 批次組合(comboA/B、high3/5、low3/5、normal、off
├── script/*_all.sh # 跨 9 顆 LTC2980 的批次 status / apply / save ├── script/*_all.sh # 跨 9 顆 LTC2980 的批次 status / apply / save
├── Ref/margin_command_trace.md # 指令追蹤參考(SWB 章節已標過期,見文件內警告)
└── logs/ # margin 操作 log └── logs/ # margin 操作 log
# 註:v2.6.0 起不再內含 blanton_cb_i2c.sh / blanton_fpga_pcimem.sh 的複本,
# 改由 _swb_find_backend 往上層 Blanton_Script/ 找共用版
``` ```
--- ---
@@ -193,6 +219,18 @@ Log 檔名規則:`<mdir>\Logs\<project_name>_<strTestcase>_<YYYYmmdd-HHMMSS>.l
Channel 定址:`ch / 8``CHIPS[]` index`ch % 8` → LTC2977 PMBus PAGE0x00~0x07)。 Channel 定址:`ch / 8``CHIPS[]` index`ch % 8` → LTC2977 PMBus PAGE0x00~0x07)。
**SWB 通道與位址對應(2026-08-19 上機驗證,`ababb2c`):**
| FPGA I2C 通道 | 板 / 接頭 | LTC2977 位址(lo / hi |
|---|---|---|
| Ch6 | SWB0 CONN14 / CONN16 | CONN14 `0x5C`,`0x5E` ; CONN16 `0x62`,`0x64` |
| Ch8 | SWB0 CONN13 / CONN15 | CONN13 `0x5C`,`0x5E` ; CONN15 `0x62`,`0x64` |
| Ch7 | SWB1 CONN14 / CONN16 | CONN14 `0x5C`,`0x5E` ; CONN16 `0x62`,`0x64` |
| Ch9 | SWB1 CONN13 / CONN15 | CONN13 `0x5C`,`0x5E` ; CONN15 `0x62`,`0x64` |
> 同一顆 LTC2980 的兩個位址**相差 2**7-bit,由規格書 8-bit 配對除以 2 而來),不是 1。
> 兩張板共用一條通道,所以同通道上的位址必須互斥 —— 上表兩組(`0x5C/0x5E` 與 `0x62/0x64`)不重疊。
### 3. `profiles/*.conf` — 16-ch 批次 margin 組合 ### 3. `profiles/*.conf` — 16-ch 批次 margin 組合
```bash ```bash
@@ -235,9 +273,16 @@ PROFILE_CHANNELS=( "0:high:8" "1:low:8" ... ) # "channel:operation:change_perc
| I210 1G NIC | `0000:07:00.0` | `0000:00:02.4` | | I210 1G NIC | `0000:07:00.0` | `0000:00:02.4` |
| NVMe SSD | `0000:06:00.0` | `0000:00:02.1` | | NVMe SSD | `0000:06:00.0` | `0000:00:02.1` |
> ⚠️ **BDF 是 per-unit 的**。`blanton_fpga_pcimem.sh` 的 `FUNCT0..3_RES` 已經改過三次 > ⚠️ **BDF 是 per-unit 的,而且只解決了一半。**
> 04:00.x → 03:00.x → 05:00.x → 03:00.x)。換 DUT 一定要先 `lspci -Dnn | grep -i fpga` 重新確認, > `blanton_fpga_pcimem.sh`V1.5.0 起)**已改為 source 時自動偵測**,不再需要每台改檔:
> 同理 `show_pcie_error_reg_*.ttl` 裡的 BDF 也要跟著校。 > 掃 sysfs 找 `vendor == 0x1590`HPE)且 `.0`~`.3` 四個 function 都有 `resource0` 的裝置 →
> 退回 `lspci -Dnn` 名稱比對 → 再退回 `FPGA_BDF_FALLBACK``0000:03:00`)。
> 偵測結果會印一行 `[INFO] CB FPGA BDF = ...`,並存在 `FPGA_BDF_ACTIVE`。
> 換 DUT 後可用 `fpga_rescan` 重測,或 `FPGA_BDF=0000:05:00 fpga_rescan` 手動釘住。
>
> **但 `utils/show_pcie_error_reg_*.ttl` 裡的 BDF 仍然是寫死的**(上表那 7 組),
> 自動偵測完全沒有涵蓋到它們。換 DUT 時這些 TTL 還是要人工校對,否則 AER 會讀到錯誤的裝置
> ——甚至讀到不存在的路徑而整段 `No such file or directory`。
--- ---
@@ -245,15 +290,18 @@ PROFILE_CHANNELS=( "0:high:8" "1:low:8" ... ) # "channel:operation:change_perc
| 模組 | 進入點 | 功能 | | 模組 | 進入點 | 功能 |
|------|--------|------| |------|--------|------|
| **Script APre-test** | `1_Blanton_Script_A.ttl` | root 登入 → `mkdir ./logs``date -s` 對時 → source 個 bash 工具 → 清/讀 7 組 PCIe AER → `lspci -tvvv/-vv` → margin 全掃 → PMON 項 → dmesg 三 grep → `messagebox` 提示接 Script B | | **Script APre-test** | `1_Blanton_Script_A.ttl` V1.0.3 | root 登入 → `date -s` 對時`show boot`**等 pmon 起來**`wait_init.ttl`)→ BMC 版本 → source 個 bash 工具 → 清/讀 7 組 PCIe AER → `lspci` → margin 全掃 → PMON 項 → dmesg → 100G port 狀態 → **一輪流量基線**`show uptime` |
| **Script BStress** | `2_Blanton_Script_B.ttl` | `mlucas-avx2 -cpu 0:15` + 5 份 `stress_mem.py` + `stress_hhmd/ssd/pcie/usb.py` 全部背景執行 → `jobs` 確認 → `while 1` 每輪 `show platform fan` / `temperature` / margin | | **Script BStress** | `2_Blanton_Script_B.ttl` | `mlucas-avx2 -cpu 0:15` + 5 份 `stress_mem.py` + `stress_ssd.py` + `stress_usb.py`(**共 8 個 job**)→ 啟動兩支 BMC monitor → `jobs` → 100G port 狀態 → traffic `ps/init/clear/show/start`**兩個 unit**)→ `while 1` 每輪完整 PMON + margin |
| **Script CPost-test** | `3_Blanton_Script_C.ttl` | `kill $(jobs -p)` → PMON + dmesg 再抓一次 → 7 組 AER 再抓一次`cat` mlucas / stress_ssd log → 完成 messagebox | | **Script CPost-test** | `3_Blanton_Script_C.ttl` | `kill $(jobs -p)` → PMON + dmesg → 7 組 AER → 停兩支 BMC monitor 並 `cat` 其 log`cat` mlucas / stress_ssd log → traffic `stop` + `report`(兩個 unit)→ `show reboot-cause` / `show uptime` 完成 messagebox |
| **FPGA 暫存器存取** | `cb_fpga <fn> <addr> [data]` | pcimem 打 `resource0` + BAR 相對 offset32-bit word`pmc/icb/swb0/swb1_fpga` 走 VSPI 窗口 | | **SWB loopback 流量** | `blanton_traffic_linespeed <sub> [-u 0\|1\|all]` | `init`VLAN 30..137,成對 `cdN`/`cdN+32`)→ `clear``start``tx 100 length=512`)→ `stop``report`per-pair TX/RX 交叉比對 PASS/FAIL);另有 `ps`link/speed)與 `run`(一條龍) |
| **FPGA 暫存器存取** | `cb_fpga <fn> <addr> [data]``fpga_rescan` | pcimem 打 `resource0` + BAR 相對 offset32-bit wordBDF 於 source 時自動偵測;`pmc/icb/swb0/swb1_fpga` 走 VSPI 窗口 |
| **CB I2C / PMBus** | `cb_i2c_init/scan/read/write``cb_pmbus_read/write/linear16` | FPGA F3 OpenCores master**讀用 Repeated START**SMBus/PMBus 裝置必需) | | **CB I2C / PMBus** | `cb_i2c_init/scan/read/write``cb_pmbus_read/write/linear16` | FPGA F3 OpenCores master**讀用 Repeated START**SMBus/PMBus 裝置必需) |
| **ICB / PDB 存取** | `vi2c_init/scan/read/write``vi2c_scan_all` | CB F3 VI2C proxy~3.3 ms/reg,取代 VSPI 的 ~18 ms/reg(後者會踩 SMBus 2535 ms timeout | | **ICB / PDB 存取** | `vi2c_init/scan/read/write``vi2c_scan_all` | CB F3 VI2C proxy~3.3 ms/reg,取代 VSPI 的 ~18 ms/reg(後者會踩 SMBus 2535 ms timeout |
| **溫度** | `temp_cb` / `temp_icb` / `temp_all` | TMP7512-bit 左靠齊 ×0.0625)、TMP432(含 RANGE bit 的 64 偏移、remote1/2 | | **溫度** | `temp_cb` / `temp_icb` / `temp_all` | TMP7512-bit 左靠齊 ×0.0625)、TMP432(含 RANGE bit 的 64 偏移、remote1/2 |
| **電源資料** | `pwr_data` / `pwr_data_pdb` / `pwr_pdb_id` | SWB VRM 全軌 Vin/Vout/Iout/Temp 對齊表;MP29816 解析度**執行期從 `MFR_VOUT_SCALE_LOOP` 讀**,不寫死 | | **電源資料** | `pwr_data` / `pwr_data_pdb` / `pwr_pdb_id` | SWB VRM 全軌 Vin/Vout/Iout/Temp 對齊表;MP29816 解析度**執行期從 `MFR_VOUT_SCALE_LOOP` 讀**,不寫死 |
| **電壓 Margin** | `margin_init/status/set/apply_profile/save` + `*_all.sh` | 9 顆 LTC2980CB CONN13、SWB0/1 CONN13~16)批次 high/low margin 與狀態掃描 | | **電壓 Margin** | `margin_init/status/set/apply_profile/save` + `*_all.sh` | 9 顆 LTC2980CB CONN13、SWB0/1 CONN13~16)批次 high/low margin 與狀態掃描。v2.6.0 起 SWB 路徑真正接上共用 cb_i2c 後端,並有 `margin_swb_info/probe/scan/reset/sem` 除錯包裝 |
| **BMC 監控 / 壓力** | `bmc_monitor.sh` / `bmc_monitor_ddr.sh` `{start\|stop\|status\|fg\|tail\|clear}` | 由 host 端 `bmc-manager run` 週期性打 BMC,背景 detached、log 輪替。前者跑 `free -m`(逾時 30s),後者跑 `memtester`(逾時 3600s),各自獨立 log 與 pid |
| **打包交付** | `publish/publish.sh [-v Ver] [-f] [-z] [-n]` | 版號讀 Script A 檔頭,複製成 `publish/Script_ABC_Blanton_<Ver>/`,排除 `*.log`,可另出 zip |
--- ---
@@ -269,30 +317,41 @@ Script A ── logopen Logs/Blanton_Margin_<ts>.log ──┐ (之後所有畫
│ │ │ │
├─ 登入 admin → sudo -i → root@sonic:~# │ ├─ 登入 admin → sudo -i → root@sonic:~# │
├─ date -s "<host 時間>" ← 讓 DUT 時戳可對照 │ ├─ date -s "<host 時間>" ← 讓 DUT 時戳可對照 │
├─ source 5 個 bash 工具(函數進 shell ├─ source 6 個 bash 工具(函數進 shell
│ └ 此時 blanton_fpga_pcimem.sh 印出 [INFO] CB FPGA BDF = ...(自動偵測結果)
├─ AER 基線:7 組 EP+RP 的 correctable/nonfatal/fatal ├─ AER 基線:7 組 EP+RP 的 correctable/nonfatal/fatal
├─ lspci -tvvv / -vv ← 拓樸 + link speed/width ├─ lspci -tvvv / -vv ← 拓樸 + link speed/width
├─ margin_status_all ← 9 顆 LTC2980 × 16ch 的 Vout 與偏差% ├─ margin_status_all ← 9 顆 LTC2980 × 16ch 的 Vout 與偏差%
├─ show platform × 7 ← fan/temp/psu/voltage/current/ssdhealth ├─ show platform × 9 ← fan/temp/psu/voltage/current/ssdhealth/leak status/leak channels
─ dmesg | grep error/fail/warning ─ dmesg | grep error/fail/warning
├─ show interfaces status Ethernet513,Ethernet514 ← 100G uplink 狀態
├─ 一輪流量基線:ps → init → clear → show → start → stop → report
└─ show uptime
(最前面還有 wait_init.ttl:輪詢 show platform temperature 直到不再回報
"Thermal Not detected",避免 pmon 還沒起來就抓基線)
[messagebox "Start Script B"] ← 人工確認後手動跑 B [messagebox "Start Script B"] ← 人工確認後手動跑 B
Script B ── 同一個 log 檔續寫(logwrite 分隔線) Script B ── 同一個 log 檔續寫(logwrite 分隔線)
├─ 背景程序:mlucas-avx2(CPU) ×1, stress_mem ×5, ├─ 背景程序:mlucas-avx2(CPU) ×1, stress_mem ×5, stress_ssd, stress_usb → 共 8 個 job
stress_hhmd(I2C/LSB/PCIe), stress_ssd, stress_pcie, stress_usb stress_hhmd / stress_pcie 已於 2026-08-17 移除,見 Status 表 ScriptB #5/#7 "KC Remove"
├─ BMC: bmc_monitor.sh start + bmc_monitor_ddr.sh start
├─ jobs ← 確認全部起來了 ├─ jobs ← 確認全部起來了
while 1: fan → temperature → margin_status_all soak 期間持續取樣 traffic: ps → init → clear → (pause 15) → show → start unit 0 與 1VLAN 30..137 loopback
└─ while 1: setup_pmon9 項)→ margin_status_all soak 期間持續取樣)
[測試員 Ctrl-C / 停止 macro,接著跑 C] [測試員 Ctrl-C / 停止 macro,接著跑 C]
Script C Script C
├─ kill $(jobs -p) ← 收掉所有背景壓力 ├─ kill $(jobs -p) ← 收掉所有背景壓力
├─ show platform × 7 + dmesg ← 收工快照 ├─ show platform × 9 + dmesg ← 收工快照
├─ AER 再抓一次 ← 與 Script A 基線相減 = 本輪新增錯誤 ├─ AER 再抓一次 ← 與 Script A 基線相減 = 本輪新增錯誤
cat mlucas_amm_log / log.stress_ssd ← 壓力程式自身的 pass/fail BMC: 兩支 monitor stop → cat log/bmc_poll.log + log/bmc_ddr.log
├─ cat mlucas_amm_log / log.stress_ssd ← 壓力程式自身的 pass/fail
├─ traffic: stop → report ← per-pair TX/RX 交叉比對,PASS/FAIL/NAexit 2 = 有 FAIL/NA
└─ show reboot-cause / show uptime ← soak 期間有沒有意外重開,一眼看得到
[messagebox "GOOD JOB! Test Case DONE"] → 人工檢查 log [messagebox "GOOD JOB! Test Case DONE"] → 人工檢查 log
``` ```
@@ -321,8 +380,9 @@ pwr_data
跑 B/C 前若關掉了 Tera Termlog 就斷了。 跑 B/C 前若關掉了 Tera Termlog 就斷了。
3. **PCIe AER 的判定是「差值」不是「絕對值」**Script A 抓基線、Script C 抓結果, 3. **PCIe AER 的判定是「差值」不是「絕對值」**Script A 抓基線、Script C 抓結果,
兩者相減才是本輪產生的錯誤。EP 與上游 Root Port **兩邊都要看**(錯誤可能只記在 RP)。 兩者相減才是本輪產生的錯誤。EP 與上游 Root Port **兩邊都要看**(錯誤可能只記在 RP)。
4. **BDF 是 per-unit 的**,換 DUT 必須重新確認並改 `blanton_fpga_pcimem.sh` `FUNCT0..3_RES` 4. **BDF 是 per-unit 的,bash 端已自動偵測、TTL 端仍寫死**`blanton_fpga_pcimem.sh` V1.5.0 起
`utils/show_pcie_error_reg_*.ttl`(見 Data Models §5 註)。 於 source 時自掃(vendor `0x1590``lspci` → fallback),但 `utils/show_pcie_error_reg_*.ttl`
的 7 組 BDF 還是硬編碼,換 DUT 仍需人工校對(見 Data Models §5 註)。
5. **ICB / PDB 一律走 VI2C,不走 VSPI**VSPI ~18 ms/reg 會讓 I2C byte 相之間拉到 ~50 ms 5. **ICB / PDB 一律走 VI2C,不走 VSPI**VSPI ~18 ms/reg 會讓 I2C byte 相之間拉到 ~50 ms
踩中 SMBus 2535 ms timeout,裝置會 NACK register pointer。實測全 5 通道掃描 9.4 s vs 34.0 s。 踩中 SMBus 2535 ms timeout,裝置會 NACK register pointer。實測全 5 通道掃描 9.4 s vs 34.0 s。
6. **I2C 讀一律用 Repeated START**(不是 STOP + START):PMBus / SMBus 裝置(VRM、EFUSE 6. **I2C 讀一律用 Repeated START**(不是 STOP + START):PMBus / SMBus 裝置(VRM、EFUSE
@@ -334,9 +394,27 @@ pwr_data
9. **`ARB_LOST` 不等於裝置存在**`vi2c_scan` 只看 `RX_ACK`,仲裁失敗會被誤報成「有裝置」。 9. **`ARB_LOST` 不等於裝置存在**`vi2c_scan` 只看 `RX_ACK`,仲裁失敗會被誤報成「有裝置」。
`blanton_pwr_data.sh` 因此加了 ARB_LOST-aware retry 與 plausibility window `blanton_pwr_data.sh` 因此加了 ARB_LOST-aware retry 與 plausibility window
`0xFFFF` 或超出範圍一律回 `NA`)——**寧可 NA 也不要印出一個看起來像數據的錯值**。 `0xFFFF` 或超出範圍一律回 `NA`)——**寧可 NA 也不要印出一個看起來像數據的錯值**。
10. **DUT 密碼不得進 git**`1_Blanton_Script_A.ttl` 目前的 `sendln "YourPaSsWoRd"` 是 placeholder 10. **`bcmcmd` 成功退出不代表指令成功**:BCM shell 拒絕指令(`VLAN: ERROR: Entry exists` /
`Failed to execute the diagnostic command`)時 `bcmcmd` 仍 exit 0。
`blanton_traffic_linespeed.sh` 因此改用輸出比對(`$TL_ERR_PAT`),並把
`vlan create``Entry exists`(重跑 init)與 `vlan remove``not found`init 前先 stop
當良性情況分開計數。另外 `bcmcmd` 一律以 `</dev/null` 執行 ——
否則它會吃掉 while 迴圈的 stdin,導致**只有第一個 VLAN 被設定**V0.3.1 修的 bug)。
11. **DUT 密碼不得進 git**`1_Blanton_Script_A.ttl` 目前的 `sendln "YourPaSsWoRd"` 是 placeholder
實際帳密必須放 `secret/config_secret.ttl`gitignored)並由 `config.ttl` include。 實際帳密必須放 `secret/config_secret.ttl`gitignored)並由 `config.ttl` include。
11. **客戶 NDA**`docs/` 內含客戶 FPGA 規格與 schematic 衍生資訊,**本 repo 只推 NAS + Gitea 12. **`.sh` / `.conf` 必須是 LF**CRLF 的 shell script 在 SONiC 上直接掛
`-bash: $'\r': command not found` + `syntax error near unexpected token`),
`settings/*.conf` 則因為是被 `source` 的,未加引號的賦值會變成 `"6\r"`(安靜地壞掉)。
`.gitattributes` 已把 `*.sh` `*.conf` `*.py` `*.txt` `*.md` 釘成 LF、`*.ttl` 維持 CRLF。
⚠️ 但 `.gitattributes` **不會回頭重寫既有工作區檔案** —— 早於它 clone 出來的目錄,
`git rm --cached -r . && git reset --hard` 或刪掉目錄重新 checkout 才會生效,
而且 `git status` 乾淨**不代表**磁碟上是 LF`--renormalize` 只在進 index 的路上轉換)。
13. **`bmc_monitor*.sh` 需要執行權**:它們是本專案第一組「被執行」而非被 `source` 的腳本,
`do_start` 內部是 `setsid "$SCRIPT_PATH" __daemon`**即使用 `bash x.sh start` 呼叫
也仍需 exec bit**。git 記錄為 `100644`,且 Windows → 隨身碟 → DUT 的路徑無法攜帶權限位元,
所以佈署後要先 `chmod +x ~/Blanton_Script/bmc_monitor*.sh`,否則 Script B 的 start 會
`Permission denied`、Script C 的 `cat` 撲空(BMC 段全空但不會報錯)。
14. **客戶 NDA**`docs/` 內含客戶 FPGA 規格與 schematic 衍生資訊,**本 repo 只推 NAS + Gitea
絕不推 GitHub**。 絕不推 GitHub**。
--- ---
@@ -371,22 +449,29 @@ cp secret/config_secret.ttl.example secret/config_secret.ttl
# ── 2. DUT 端:部署 bash 工具庫 ───────────────────────────── # ── 2. DUT 端:部署 bash 工具庫 ─────────────────────────────
scp -r src/Script_ABC_Blanton/Blanton_Script admin@<DUT_IP>:~/ scp -r src/Script_ABC_Blanton/Blanton_Script admin@<DUT_IP>:~/
# 佈署後務必補執行權(Windows/隨身碟路徑帶不動 exec bit):
ssh admin@<DUT_IP> 'chmod +x ~/Blanton_Script/bmc_monitor*.sh'
# 若曾在舊工作區編輯過,先確認沒有 CRLFgrep -rlU $'\r' ~/Blanton_Script
ssh admin@<DUT_IP> 'sudo -i' ssh admin@<DUT_IP> 'sudo -i'
# (現場無網路時改用 USB 隨身碟複製到 ~/Blanton_Script # (現場無網路時改用 USB 隨身碟複製到 ~/Blanton_Script
# ── 3. DUT 端:確認 FPGA BDF(每台不同!)──────────────────── # ── 3. DUT 端:煙霧測試(跑 Script A 之前先手動確認)──────────
lspci -Dnn | grep -i fpga # 例:0000:03:00.0
ls -d /sys/bus/pci/devices/0000:*:00.3
# 若不是 0000:03:00.x,改 ~/Blanton_Script/blanton_fpga_pcimem.sh 的 FUNCT0..3_RES
# 以及 src/Script_ABC_Blanton/utils/show_pcie_error_reg_*.ttl 內的 BDF
# ── 4. DUT 端:煙霧測試(跑 Script A 之前先手動確認)──────────
source ~/Blanton_Script/blanton_fpga_pcimem.sh source ~/Blanton_Script/blanton_fpga_pcimem.sh
# → 會印 [INFO] CB FPGA BDF = 0000:0X:00.0-.3 (resource0) ← BDF 自動偵測,不用改檔
# 偵測錯或多顆候選時:FPGA_BDF=0000:05:00 fpga_rescan
cb_fpga 0 0xD90 # RST_CAUSE_REG,讀得到值 = BAR 通了 cb_fpga 0 0xD90 # RST_CAUSE_REG,讀得到值 = BAR 通了
source ~/Blanton_Script/blanton_cb_i2c.sh source ~/Blanton_Script/blanton_cb_i2c.sh
cb_i2c_init 11 && cb_i2c_scan 11 # Ch11 風扇控制器,應看到 0x20 cb_i2c_init 11 && cb_i2c_scan 11 # Ch11 風扇控制器,應看到 0x20
source ~/Blanton_Script/LTC2980_Margin_Script/margin.sh source ~/Blanton_Script/LTC2980_Margin_Script/margin.sh
margin_init Blanton_CB_CONN13 && margin_status margin_init Blanton_CB_CONN13 && margin_status
source ~/Blanton_Script/blanton_traffic_linespeed.sh
blanton_traffic_linespeed ps -u 0 # 對照線的 link/speed
blanton_traffic_linespeed init -u 0 -n # -n = dry-run,先看要下哪些 bcmcmd
# ── 4. DUT 端:校對 AER 的 BDF(自動偵測沒涵蓋這裡!)─────────
lspci -Dnn # 對照 ARCHITECTURE.md 的 PCIe 拓樸表
# 與 src/Script_ABC_Blanton/utils/show_pcie_error_reg_*.ttl 內寫死的 7 組 BDF 比對,
# 不符就改 TTL——否則 AER 會讀到別的裝置或不存在的路徑
``` ```
**HostWindows)端:** **HostWindows)端:**
@@ -402,6 +487,19 @@ margin_init Blanton_CB_CONN13 && margin_status
8. 檢查 Logs\Blanton_Margin_<ts>.log 8. 檢查 Logs\Blanton_Margin_<ts>.log
``` ```
**打包交付:**
```bash
./publish/publish.sh # 版號讀 Script A 檔頭 → publish/Script_ABC_Blanton_V1.0.3/
./publish/publish.sh -z # 另外產出同名 .zip
./publish/publish.sh -n # dry run,只印會做什麼
./publish/publish.sh -f # 覆蓋既有輸出資料夾
```
版號**不是參數而是從 Script A 的 `; Version : Vx.y.z` 讀出來的**,所以資料夾名不可能跟
測試員打開 macro 看到的版本不一致。要出新版就先改 Script A 檔頭再跑。
輸出會排除 `*.log`(測試 log 含 DUT 序號,不隨交付外流),但保留空的 `logs/``logopen` 寫入。
> 註:本專案非 .NET / Blazor / EC2 架構,`aws-ec2-deploy` 與 `vbox-run-deploy` 兩份部署指南不適用,已跳過。 > 註:本專案非 .NET / Blazor / EC2 架構,`aws-ec2-deploy` 與 `vbox-run-deploy` 兩份部署指南不適用,已跳過。
--- ---
@@ -416,12 +514,13 @@ margin_init Blanton_CB_CONN13 && margin_status
**目標:** 專案進 git(NAS + Gitea),帳密不再躺在腳本裡,任何人 clone 下來照文件就能跑。 **目標:** 專案進 git(NAS + Gitea),帳密不再躺在腳本裡,任何人 clone 下來照文件就能跑。
**包含:** **包含:**
- [ ] `git init` + `.gitignore``secret/*``For_AI/``logs/*.log``~$*``*.DSN` - [x] `git init` + `.gitignore``secret/*``For_AI/``logs/*.log``~$*``*.DSN`
- [ ]`secret/README.md` + `secret/config_secret.ttl.example``__CHANGE_ME__` placeholder - [x]`secret/README.md` + `secret/config_secret.ttl.example``__CHANGE_ME__` placeholder
- [ ] `config.ttl` 改為 `include "..\..\secret\config_secret.ttl"`(或現場放同層), - [ ] `config.ttl` 改為 `include "..\..\secret\config_secret.ttl"`(或現場放同層),
`1_Blanton_Script_A.ttl``sendln "YourPaSsWoRd"` 換成 `sendln dut_password` `1_Blanton_Script_A.ttl``sendln "YourPaSsWoRd"` 換成 `sendln dut_password`
- [ ] `src/Script_ABC_Blanton/logs/.gitkeep`,既有 `Blanton_Margin_20260814-164122.log` 移出 repo **← 唯一未完成項;密碼目前仍寫在腳本裡**
- [ ] `CLAUDE.md` + `ARCHITECTURE.md` 進 repopush `nas` + `gitea`**不推 GitHub** - [x] `src/Script_ABC_Blanton/logs/.gitkeep`,既有 `Blanton_Margin_20260814-164122.log` 移出 repo
- [x] `CLAUDE.md` + `ARCHITECTURE.md` 進 repopush `nas` + `gitea`**不推 GitHub**
**驗收條件:** 在另一台機器 `git clone` → 填 `secret/config_secret.ttl` → Script A 能完整登入並產生 log **驗收條件:** 在另一台機器 `git clone` → 填 `secret/config_secret.ttl` → Script A 能完整登入並產生 log
`git log -p` 中搜不到任何實際密碼。 `git log -p` 中搜不到任何實際密碼。
@@ -442,7 +541,8 @@ margin_init Blanton_CB_CONN13 && margin_status
- [ ] 新增 `utils/show_pwr_data.ttl``pwr_data` + `pwr_data_pdb`)與 - [ ] 新增 `utils/show_pwr_data.ttl``pwr_data` + `pwr_data_pdb`)與
`utils/show_temp_sensor.ttl``temp_all`),把 `blanton_pwr_data.sh` / `utils/show_temp_sensor.ttl``temp_all`),把 `blanton_pwr_data.sh` /
`blanton_temp_sensor.sh` 真正接進流程(目前 Script A 只 source 沒用) `blanton_temp_sensor.sh` 真正接進流程(目前 Script A 只 source 沒用)
- [ ] Script A 開頭加 BDF 自檢`lspci -Dnn | grep -i fpga`log 裡看得到本台的實際 BDF - [x] Script A 開頭加 BDF 自檢 —— 由 `blanton_fpga_pcimem.sh` V1.5.0 的 source 時自動偵測達成,
log 內會出現 `[INFO] CB FPGA BDF = ...`(比原本規劃的 `lspci` 一行更完整)
**驗收條件:** 跑一次 Script Alog 內同時含 reset cause 三個暫存器值、9 顆 LTC2980 × 16ch 的 **驗收條件:** 跑一次 Script Alog 內同時含 reset cause 三個暫存器值、9 顆 LTC2980 × 16ch 的
margin status、全部 VRM 軌的 Vin/Vout/Iout/Temp、CB + ICB 溫感讀值,且無 `ERR` / `NA` 以外的異常。 margin status、全部 VRM 軌的 Vin/Vout/Iout/Temp、CB + ICB 溫感讀值,且無 `ERR` / `NA` 以外的異常。
@@ -458,14 +558,21 @@ margin status、全部 VRM 軌的 Vin/Vout/Iout/Temp、CB + ICB 溫感讀值,
`ls -l ~/hammer/tools/` + `ls ~/hammer/tools/amd/mlucas-avx2`),缺檔就別往下跑 `ls -l ~/hammer/tools/` + `ls ~/hammer/tools/amd/mlucas-avx2`),缺檔就別往下跑
- [ ] 壓力程序 log 加時戳,避免多輪覆蓋: - [ ] 壓力程序 log 加時戳,避免多輪覆蓋:
`mlucas_amm_log``~/logs/mlucas_amm_log_$(date +%Y%m%d-%H%M%S).log`Status 表 ScriptB #2 的既定寫法) `mlucas_amm_log``~/logs/mlucas_amm_log_$(date +%Y%m%d-%H%M%S).log`Status 表 ScriptB #2 的既定寫法)
- [ ] `jobs` 之後加驗證:期望 10 個背景 job1 mlucas + 5 mem + hhmd/ssd/pcie/usb), - [ ] `jobs` 之後加驗證:期望 **8** 個背景 job1 mlucas + 5 mem + ssd + usb),
數量不符時 `messagebox` 提示 數量不符時 `messagebox` 提示
`stress_hhmd` / `stress_pcie` 已於 2026-08-17 由 KC 移除,原本的 10 改為 8)
- [ ] soak 迴圈改為「每輪先 `date`、跑完 `pause <間隔>`」, - [ ] soak 迴圈改為「每輪先 `date`、跑完 `pause <間隔>`」,
`EN_LoopInterval`(預設 600 秒 = 10 min)放進 `config.ttl`,兌現 Status 表的「Get data every 10mins」 `EN_LoopInterval`(預設 600 秒 = 10 min)放進 `config.ttl`,兌現 Status 表的「Get data every 10mins」
- [ ] soak 每輪補 `show platform psustatus` / `voltage` / `current`(目前只有 fan + temperature **← 目前 `while 1` 仍無 `pause`,實際間隔取決於指令執行時間,不是 10 分鐘**
- [x] soak 每輪補 `show platform psustatus` / `voltage` / `current`
—— 迴圈已改為 `include "utils/setup_pmon.ttl"`,九項全收
- [ ]`; ========== CLEAR EVENT ==========`Status 表 ScriptB #1Owner: Alan - [ ]`; ========== CLEAR EVENT ==========`Status 表 ScriptB #1Owner: Alan
- [x] BMC 納入 soak`bmc_monitor.sh``free -m` 取樣)與 `bmc_monitor_ddr.sh``memtester`
由 Script B 啟動、Script C 停止並把 log 併進 master log
- [x] Script A 加開機資訊與就緒等待:`show boot`、BMC `/etc/issue``wait_init.ttl`
- [x] Script C 加 `show reboot-cause` / `show uptime`soak 期間意外重開看得見
**驗收條件:** Script B 啟動後 `jobs` 顯示 10 個 job;連續跑 3 輪,log 內三段各自帶正確時戳且間隔 ≈10 min; **驗收條件:** Script B 啟動後 `jobs` 顯示 8 個 job;連續跑 3 輪,log 內三段各自帶正確時戳且間隔 ≈10 min;
故意把 `~/hammer` 改名,Script B 會在前置檢查就停下並提示。 故意把 `~/hammer` 改名,Script B 會在前置檢查就停下並提示。
--- ---
@@ -474,19 +581,32 @@ margin status、全部 VRM 軌的 Vin/Vout/Iout/Temp、CB + ICB 溫感讀值,
**目標:** 補齊 Status 表中 Owner=Alan 的三個 traffic 項目,讓測試涵蓋交換晶片的資料面。 **目標:** 補齊 Status 表中 Owner=Alan 的三個 traffic 項目,讓測試涵蓋交換晶片的資料面。
**包含:** > 📌 **實作路線與原規劃不同**:原本設想用 SONiC CLI`sonic-clear counters` / `show interface counters`),
- [ ] `utils/traffic_setup.ttl`Blanton/SONiC 版的 port 起始設定與 counter 清零 > 實際落地的是**更底層的 `bcmcmd` drivshell 路線** —— SWB 實體 loopback 線 + VLAN 成對配置 + `tx` burst
(對照 Carlsbad 的 `clear int stat global`,改用 SONiC `sonic-clear counters` > 直接讀 ASIC 的 `MIB_TPOK`/`MIB_RPOK`。好處是能做 per-pair 雙向交叉比對(`cdA.TX == cdB.RX`),
- [ ] `utils/show_traffic_counters.ttl``show interface counters` > 比 SONiC CLI 的單向 counter 強得多;代價是綁定 Broadcom SDK 與那條 loopback 線。
`show interface counters errors``show interface status`
- [ ] `utils/show_interface_detail.ttl``show interface transceiver eeprom/dom`
`show interface link-training`(對照 Carlsbad `sho int transceiver detail`
- [ ] `utils/show_tech.ttl``show techsupport`Script C 收工快照,Status 表 ScriptC #4
- [ ] Script A 的 TRAFFIC-SETUP-STAGE 與 Script C 的 CHECK Traffic counters 接上這些片段
- [ ] `config.ttl``EN_Traffic` 旗標,未接線的站點可整段跳過
**驗收條件** `EN_Traffic=1` 時,Script A log 有清零後的 counter 基線、Script C log 有結束時的 **包含**
counter 與 error-counter,兩者可直接相減;`EN_Traffic=0` 時整段不執行且不報錯。 - [x] `blanton_traffic_linespeed.sh`V0.4.0):`init` / `clear` / `show` / `start` / `stop` /
`ps` / `report` / `run`unit 0 與 1VLAN 30..137 對應 `cdN``cdN+32`
(取代原規劃的 `utils/traffic_setup.ttl`
- [x] per-pair TX/RX 報表與 PASS/FAIL 判定:DUT 上 awk`report`+ 離線
`tools/bcm_mibpair_report_V1.1.0.py`,同版面同判定規則
(取代原規劃的 `utils/show_traffic_counters.ttl`
- [x] Script B 接上 `ps → init → clear → show → start`、Script C 接上 `stop → report`
- [x] 原始 bcmcmd 指令表留檔:`tools/traffic_loopback_{vlan_setting,start,vlan_remove}.txt`
- [x] **Script A 的流量基線** —— 原本的空區塊已改成完整一輪
`ps → init → clear → show → start → stop → report`,含 15 秒沉澱)
- [ ] `utils/show_interface_detail.ttl``show interface transceiver eeprom/dom`
`show interface link-training`Status 表 ScriptC #4,仍 On-Going
- [ ] `utils/show_tech.ttl``show techsupport`Script C 收工快照,Status 表 ScriptC #4
- [ ] `config.ttl``EN_Traffic` 旗標,未接 loopback 線的站點可整段跳過
(現在 B/C 是無條件執行,沒接線會整批 bcmcmd 失敗)
- [x] `-u 0` 已從三支腳本移除,全部沿用工具預設 `TL_UNITS="0 1"`,兩塊 switch board 都測到
- [x] 100G uplink 狀態:`show interfaces status Ethernet513,Ethernet514`A 與 B 各一次)
**驗收條件:** `EN_Traffic=1` 時,Script C 的 `report` 印出 108 對的 TX/RX 表且全 PASS
拔掉一條 loopback 線重跑,該對顯示 FAIL 或 NA 且 exit code 為 2`EN_Traffic=0` 時整段不執行且不報錯。
--- ---
@@ -502,6 +622,8 @@ counter 與 error-counter,兩者可直接相減;`EN_Traffic=0` 時整段不
- [ ] Margin 表解析:把 9 顆 × 16ch 的 Vout / 偏差% 收成 CSV,標出超出 ±(profile 設定值 + 容差) 的軌 - [ ] Margin 表解析:把 9 顆 × 16ch 的 Vout / 偏差% 收成 CSV,標出超出 ±(profile 設定值 + 容差) 的軌
- [ ] 溫度 / 風扇 / 電源趨勢:soak 期間逐輪取樣 → CSV(可直接畫圖) - [ ] 溫度 / 風扇 / 電源趨勢:soak 期間逐輪取樣 → CSV(可直接畫圖)
- [ ] Stress 判定:`mlucas_amm_log``log.stress_ssd` 的錯誤關鍵字掃描 - [ ] Stress 判定:`mlucas_amm_log``log.stress_ssd` 的錯誤關鍵字掃描
- [ ] Traffic 判定:**直接複用 `tools/bcm_mibpair_report_V1.1.0.py`**(它已能吃 console log 的
`show c` 段落並輸出 PASS/FAIL),不要重寫一套解析
- [ ] 產出 `Logs/<run>_summary.md`:一頁 PASS/FAIL + 每個子系統一行結論 - [ ] 產出 `Logs/<run>_summary.md`:一頁 PASS/FAIL + 每個子系統一行結論
**驗收條件:** 拿一份既有 log(如 `Blanton_Margin_20260814-164122.log`)跑解析器, **驗收條件:** 拿一份既有 log(如 `Blanton_Margin_20260814-164122.log`)跑解析器,
@@ -514,9 +636,9 @@ counter 與 error-counter,兩者可直接相減;`EN_Traffic=0` 時整段不
**目標:** 產出可直接交給現場測試員(或客戶)的一包,含操作手冊,不需要看原始碼。 **目標:** 產出可直接交給現場測試員(或客戶)的一包,含操作手冊,不需要看原始碼。
**包含:** **包含:**
- [ ] `tools/publish.sh`:把 `src/Script_ABC_Blanton/` + 空 `Logs/` + `secret/*.example` - [x] `publish/publish.sh`:把 `src/Script_ABC_Blanton/` 打包成
打包成 `publish/Blanton_Script_ABC_v<ver>_<YYYYMMDD>/` 與同名 zip(比照 `publish/Script_ABC_Blanton_<Ver>/``-z` 另出 zip)。版號讀 Script A 檔頭,
既有的 `Blanton_Script_ABC_v1.0_20251229` 命名) 排除 `*.log` 但保留空的 `logs/`。工具本身進 git,產出被 gitignore
- [ ] `README.md`(現場用):Tera Term 設定、A→B→C 操作順序、常見錯誤(BDF 不符、 - [ ] `README.md`(現場用):Tera Term 設定、A→B→C 操作順序、常見錯誤(BDF 不符、
margin 掃不到、hammer 工具缺檔)與對應處理 margin 掃不到、hammer 工具缺檔)與對應處理
- [ ] `CHANGELOG.md`:延續三支腳本頂端的 Version History,集中成一份 - [ ] `CHANGELOG.md`:延續三支腳本頂端的 Version History,集中成一份
@@ -537,12 +659,25 @@ counter 與 error-counter,兩者可直接相減;`EN_Traffic=0` 時整段不
COM36 才拿到部分結果;`READ_VIN` 已知不是真的輸入電壓(跟隨 `READ_VOUT`)。待拿到 brick 的 COM36 才拿到部分結果;`READ_VIN` 已知不是真的輸入電壓(跟隨 `READ_VOUT`)。待拿到 brick 的
`MFR_ID`/`MFR_MODEL` 後對照 datasheet 定案。 `MFR_ID`/`MFR_MODEL` 後對照 datasheet 定案。
- **`show_pcie_error_reg.ttl` 淘汰**:舊的合併版已被 7 支 per-device 版取代,確認無人引用後刪除。 - **`show_pcie_error_reg.ttl` 淘汰**:舊的合併版已被 7 支 per-device 版取代,確認無人引用後刪除。
- **BDF 自動偵測**:把 `blanton_fpga_pcimem.sh` 註解中的 auto-detect 片段轉正, - **BDF 自動偵測** —— 已於 `blanton_fpga_pcimem.sh` V1.5.02026-08-17)完成。
`lspci -Dnn | grep -i fpga` 動態填 `FUNCT0..3_RES`,讓換 DUT 不用改檔。 下一步是把同樣的偵測結果餵給 `utils/show_pcie_error_reg_*.ttl`
讓 AER 那 7 組 BDF 也不用手改(目前仍是硬編碼)。
- **`stress_hhmd` / `stress_pcie` 的替代**:兩者已於 2026-08-17 移除,I2CPCIe 的持續讀取壓力
目前沒有替代品。若仍需覆蓋這塊,可考慮用 `blanton_cb_i2c.sh` / `cb_fpga` 寫一支輕量 loop。
- **SSH / 網路模式**:目前全走 COM console。若 DUT 管理網路可用,可改用 `plink`/`ssh` 執行同一組 - **SSH / 網路模式**:目前全走 COM console。若 DUT 管理網路可用,可改用 `plink`/`ssh` 執行同一組
bash 工具,速度與可靠度都比 serial 好,TTL 只保留開機階段。 bash 工具,速度與可靠度都比 serial 好,TTL 只保留開機階段。
- **多 DUT 併行**:多台 DUT 同時 soak 時,Tera Term 每台開一個 macro window - **多 DUT 併行**:多台 DUT 同時 soak 時,Tera Term 每台開一個 macro window
可考慮改成 Python`pyserial`)統一排程與集中收 log。 可考慮改成 Python`pyserial`)統一排程與集中收 log。
- **與 `LTC2980_Margin_Script` 獨立 repo 的同步機制**:目前 `Blanton_Script/LTC2980_Margin_Script/` - **margin 子專案的重複檔** —— v2.6.0 已移除內含的 `blanton_cb_i2c.sh` /
是該 repo 的複本(少了 `ARCHITECTURE.md`/`CLAUDE.md`/`Reference/`,多了兩支 `blanton_*` 後端) `blanton_fpga_pcimem.sh` 複本,改由 `_swb_find_backend` 往上層找共用版,漂移風險解除
待評估改用 git submodule 或明確的 vendored 同步腳本。 與獨立 repo `LTC2980_Margin_Script` 的同步機制(submodule 或 vendored 腳本)仍待評估
- **`Ref/margin_command_trace.md` 需重新產生**:SWB 章節的 1223 條 `cb_pmbus_*` 指令是依
修正前的通道/位址展開的(其中 487 條用到已不存在的 `0x5D`/`0x5F`/`0x63`/`0x65`),
文件已加過期警告但內文未動。第 1~4 章不受影響。
- **`wait_init.ttl` 沒有逾時出口**:若 pmon 一直起不來,`result` 既非 1 也非 2,迴圈會無限重試,
Script A 既不往下走也不報錯。可加最大重試次數 + `messagebox` 提示。
- **`bmc_monitor*.sh` 的執行權**:目前需人工 `chmod +x`。可在 Script B 起它們之前補一行
`chmod +x`(比照既有的 `chmod +x ~/hammer/tools/amd/mlucas-avx2`),或在 publish 時處理。
- **`bmc_monitor_ddr.sh` 的壓力強度**`memtester 500M 1``INTERVAL_SEC=5` 等於整個 soak
近乎不間斷壓 BMC 記憶體,而 BMC 同時還要服務 host 的 `show platform` 查詢。
若 PMON 取數變慢,先調大 `INTERVAL_SEC` 或縮小 memtester 的量。
+94 -15
View File
@@ -9,6 +9,10 @@ DUT 端的取數能力由一組 **bash 工具庫** 提供(CB FPGA `pcimem` →
完整架構見 [ARCHITECTURE.md](ARCHITECTURE.md)。 完整架構見 [ARCHITECTURE.md](ARCHITECTURE.md)。
**目前狀態(2026-08-21**Script A/B/C 為 **V1.0.3**,涵蓋 PCIe AER / DDR EDAC / PMON /
電壓 margin9 顆 LTC2980/ SWB loopback 線速流量(兩個 switch unit/ BMC 監控與 DDR 壓力。
SWB 的 I2C 通道與位址已於 2026-08-19 上機驗證。交付用 `./publish/publish.sh` 打包。
## 技術棧 ## 技術棧
- **Host**Tera Term 5.x TTL macro`.ttl`),Windows - **Host**Tera Term 5.x TTL macro`.ttl`),Windows
@@ -17,6 +21,9 @@ DUT 端的取數能力由一組 **bash 工具庫** 提供(CB FPGA `pcimem` →
- **I2C/PMBus**CB FPGA F3 I2C masterCh0~Ch17base 0x300 stride 0x20)、 - **I2C/PMBus**CB FPGA F3 I2C masterCh0~Ch17base 0x300 stride 0x20)、
CB F3 VI2C proxy → ICB/PDBCh0~Ch4base 0x780)、CPU 原生 bus`i2cset`/`i2cget` CB F3 VI2C proxy → ICB/PDBCh0~Ch4base 0x780)、CPU 原生 bus`i2cset`/`i2cget`
- **Margin**LTC2980= 2 × LTC2977over PMBusLINEAR16 - **Margin**LTC2980= 2 × LTC2977over PMBusLINEAR16
- **資料面流量**`bcmcmd`Broadcom drivshellSWB loopbackVLAN 30..137 成對 `cdN`/`cdN+32`
`tx 100 length=512`;判定讀 ASIC `MIB_TPOK`/`MIB_RPOK` 做 per-pair 雙向交叉比對
- **BMC**host 端 `bmc-manager run "<cmd>"`,不需對 BMC 開 SSH(繞開其 busybox 工具鏈)
- **判定資料源**PCIe AER sysfs`aer_dev_*`)、EDAC sysfs`dimm_{ce,ue}_count`)、SONiC `show platform *` - **判定資料源**PCIe AER sysfs`aer_dev_*`)、EDAC sysfs`dimm_{ce,ue}_count`)、SONiC `show platform *`
- **無編譯步驟**:TTL 與 bash 都直譯執行 - **無編譯步驟**:TTL 與 bash 都直譯執行
@@ -26,17 +33,19 @@ DUT 端的取數能力由一組 **bash 工具庫** 提供(CB FPGA `pcimem` →
# ── 部署 bash 工具庫到 DUT ────────────────────────── # ── 部署 bash 工具庫到 DUT ──────────────────────────
scp -r src/Script_ABC_Blanton/Blanton_Script admin@<DUT_IP>:~/ scp -r src/Script_ABC_Blanton/Blanton_Script admin@<DUT_IP>:~/
# ── DUT 上先確認 FPGA BDF(每台不同!)─────────────── # ── DUT 上載入工具(Script A 也是做這六件事)─────────
lspci -Dnn | grep -i fpga source ~/Blanton_Script/blanton_fpga_pcimem.sh # ← 會自己印出偵測到的 CB FPGA BDF
ls -d /sys/bus/pci/devices/0000:*:00.3
# ── DUT 上載入工具(Script A 也是做這五件事)─────────
source ~/Blanton_Script/blanton_fpga_pcimem.sh
source ~/Blanton_Script/blanton_cb_i2c.sh source ~/Blanton_Script/blanton_cb_i2c.sh
source ~/Blanton_Script/blanton_icb_vi2c.sh source ~/Blanton_Script/blanton_icb_vi2c.sh
source ~/Blanton_Script/blanton_pwr_data.sh source ~/Blanton_Script/blanton_pwr_data.sh
source ~/Blanton_Script/blanton_traffic_linespeed.sh
source ~/Blanton_Script/LTC2980_Margin_Script/margin.sh source ~/Blanton_Script/LTC2980_Margin_Script/margin.sh
# ── FPGA BDF:自動偵測,不用改檔;必要時手動釘 ───────
fpga_rescan # 換 DUT / 重新列舉後重測
FPGA_BDF=0000:05:00 fpga_rescan # 偵測到多顆或選錯時,指定 domain:bus:dev(不含 .fn
echo $FPGA_BDF_ACTIVE # 目前生效的 BDF
# ── 煙霧測試 ──────────────────────────────────────── # ── 煙霧測試 ────────────────────────────────────────
cb_fpga 0 0xD90 # RST_CAUSE_REG,讀得到 = BAR 通了 cb_fpga 0 0xD90 # RST_CAUSE_REG,讀得到 = BAR 通了
cb_i2c_init 11 && cb_i2c_scan 11 # Ch11 風扇控制器,應看到 0x20 cb_i2c_init 11 && cb_i2c_scan 11 # Ch11 風扇控制器,應看到 0x20
@@ -45,11 +54,45 @@ temp_all # CB + ICB 溫感
pwr_data # SWB VRM 全軌 pwr_data # SWB VRM 全軌
margin_init Blanton_CB_CONN13 && margin_status margin_init Blanton_CB_CONN13 && margin_status
# ── SWB loopback 流量(bcmcmdunit 0/1)─────────────
# 不帶 -u 就是預設 TL_UNITS="0 1",兩塊 switch board 都做(TTL 三支腳本即如此)
blanton_traffic_linespeed ps # 對照線的 link/speed
blanton_traffic_linespeed init # VLAN 30..137cdN <-> cdN+32
blanton_traffic_linespeed clear # clear c
blanton_traffic_linespeed start -tx 100 -length 512
blanton_traffic_linespeed stop # 移除 VLAN 成員(--destroy 連 VLAN 一起砍)
blanton_traffic_linespeed report # per-pair TX/RX + PASS/FAILexit 2 = 有 FAIL/NA
blanton_traffic_linespeed run # ps → init → clear → start → report 一條龍
blanton_traffic_linespeed init -u 0 # 只做單一 unit 時才需要 -u
blanton_traffic_linespeed init -n # -n dry-run:只印要下的 bcmcmd,不碰 DUT
blanton_traffic_linespeed report -f <log> # 離線重解一份存下來的 console log
# 離線版報表(開發機上跑,選項更多)
python3 tools/bcm_mibpair_report_V1.1.0.py <drivshell.log> -a
# ── BMC 監控 / 壓力(需先 chmod +x!)────────────────
chmod +x ~/Blanton_Script/bmc_monitor*.sh # 佈署後必做一次
~/Blanton_Script/bmc_monitor.sh start # free -m 取樣,log/bmc_poll.log
~/Blanton_Script/bmc_monitor_ddr.sh start # memtesterlog/bmc_ddr.log
~/Blanton_Script/bmc_monitor.sh status # pid / log 路徑 / log 大小
~/Blanton_Script/bmc_monitor.sh stop
# ⚠️ tail 子命令是 tail -f,會卡住不返回;要倒 log 請直接 cat log/*.log
# ── 各工具的 help ─────────────────────────────────── # ── 各工具的 help ───────────────────────────────────
blanton_fpga_help ; blanton_cb_i2c_help ; vi2c_help blanton_fpga_help ; blanton_cb_i2c_help ; vi2c_help
temp_sensor_help ; pwr_data_help temp_sensor_help ; pwr_data_help ; blanton_traffic_linespeed_help
``` ```
**打包交付(在開發機上):**
```bash
./publish/publish.sh # 版號讀 Script A 檔頭 → publish/Script_ABC_Blanton_V1.0.3/
./publish/publish.sh -z # 另出 zip
./publish/publish.sh -n # dry run
```
要出新版**先改 Script A 檔頭的 `; Version :`**,publish 才會用新版號建資料夾。
**Host 端跑一輪測試:** **Host 端跑一輪測試:**
``` ```
@@ -69,10 +112,17 @@ Tera Term 連 COM port (115200-8-N-1)
- **新增取數項目** = 新增一支 `utils/show_xxx.ttl`,再在主腳本 `include`,不要往主腳本塞指令 - **新增取數項目** = 新增一支 `utils/show_xxx.ttl`,再在主腳本 `include`,不要往主腳本塞指令
- **bash 命名**:公開函數 `cb_i2c_* / vi2c_* / temp_* / pwr_* / margin_*`,內部 helper 一律底線開頭 - **bash 命名**:公開函數 `cb_i2c_* / vi2c_* / temp_* / pwr_* / margin_*`,內部 helper 一律底線開頭
`_cbi2c_* / _vr / _ts_* / _pwr_*` `_cbi2c_* / _vr / _ts_* / _pwr_*`
- **版本紀錄**每支 `.ttl``.sh` 檔頭都有 `Version History` 區塊,改動時**在檔頭補一行** - **版本紀錄**`.sh` 每支檔頭都有 `Version History` 區塊,改動時**在檔頭補一行**
`V1.0.2 YYYY-MM-DD <做了什麼>`),不要只改版號 `V1.0.2 YYYY-MM-DD <做了什麼>`),不要只改版號
TTL 這邊 **2026-08-17 起改為集中制**`1_Blanton_Script_A.ttl` 的 History 記錄 A/B/C 三支的
所有變更,B 與 C 的檔頭版本區塊已移除。⚠️ 這是待確認的慣例變更 ——
若要回到「每支各自記錄」,B/C 的 History 需要補回去
- **Commit**Conventional Commits**英文**。scope 用 `ttl` / `fpga` / `i2c` / `vi2c` / - **Commit**Conventional Commits**英文**。scope 用 `ttl` / `fpga` / `i2c` / `vi2c` /
`margin` / `pwr` / `temp` / `docs` `margin` / `pwr` / `temp` / `docs`
- **換行符**:由 `.gitattributes` 管理 —— `*.sh` `*.conf` `*.py` `*.txt` `*.md`**LF**
(要在 SONiC 上跑/被 source),`*.ttl` 維持 **CRLF**Tera Term 只在 Windows 跑)。
編輯 `.ttl` 時要保留 CRLF,別讓整檔變成 LF 產生假 diff
- **打包**`./publish/publish.sh`,版號**讀 Script A 檔頭**不是參數 —— 先改檔頭再打包
- **Status 表**`docs/TTL_Script_Blanton_Status_*.xlsx` 是逐項進度的正本(OK / On-Going + Owner), - **Status 表**`docs/TTL_Script_Blanton_Status_*.xlsx` 是逐項進度的正本(OK / On-Going + Owner),
完成一項就同步更新,別讓文件跟腳本脫節 完成一項就同步更新,別讓文件跟腳本脫節
@@ -81,9 +131,12 @@ Tera Term 連 COM port (115200-8-N-1)
- 🔒 **NDA:本 repo 只推 `nas` + `gitea`,絕不推 GitHub。** `docs/` 內含客戶 FPGA 規格。 - 🔒 **NDA:本 repo 只推 `nas` + `gitea`,絕不推 GitHub。** `docs/` 內含客戶 FPGA 規格。
- 🔒 **DUT 帳密不進 git**`1_Blanton_Script_A.ttl``sendln "YourPaSsWoRd"` 是 placeholder - 🔒 **DUT 帳密不進 git**`1_Blanton_Script_A.ttl``sendln "YourPaSsWoRd"` 是 placeholder
實際值放 `secret/config_secret.ttl`gitignored)。**任何 commit 前先 grep 一次確認沒帶到真密碼。** 實際值放 `secret/config_secret.ttl`gitignored)。**任何 commit 前先 grep 一次確認沒帶到真密碼。**
- ⚠️ **PCIe BDF 是 per-unit 的**`blanton_fpga_pcimem.sh``FUNCT0..3_RES` 已經改過三次 - ⚠️ **PCIe BDF 是 per-unit 的 —— bash 端已自動化,TTL 端還沒**
04:00.x → 03:00.x → 05:00.x → 03:00.x)。換 DUT 一定先 `lspci -Dnn | grep -i fpga` `blanton_fpga_pcimem.sh` V1.5.02026-08-17)起 source 時自動偵測(sysfs vendor `0x1590`
`utils/show_pcie_error_reg_*.ttl` 內的 BDF 也要一起校。 `lspci``FPGA_BDF_FALLBACK`),**不用再每台改檔**;印一行 `[INFO] CB FPGA BDF = ...`
可用 `fpga_rescan` 重測、`FPGA_BDF=... fpga_rescan` 手動釘住。
**但 `utils/show_pcie_error_reg_*.ttl` 裡的 7 組 BDF 仍是寫死的**,換 DUT 還是要人工校對 ——
不校就會讀到別的裝置,或整段 `No such file or directory`
- ⚠️ **AER 判定看差值不看絕對值**Script A 抓基線、Script C 抓結果,相減才是本輪錯誤。 - ⚠️ **AER 判定看差值不看絕對值**Script A 抓基線、Script C 抓結果,相減才是本輪錯誤。
**Endpoint 與上游 Root Port 兩邊都要讀**,錯誤可能只記在 RP。 **Endpoint 與上游 Root Port 兩邊都要讀**,錯誤可能只記在 RP。
- ⚠️ **ICB/PDB 一律走 VI2C`blanton_icb_vi2c.sh`),不要走 VSPI**VSPI ~18 ms/reg 會讓 I2C - ⚠️ **ICB/PDB 一律走 VI2C`blanton_icb_vi2c.sh`),不要走 VSPI**VSPI ~18 ms/reg 會讓 I2C
@@ -99,12 +152,38 @@ Tera Term 連 COM port (115200-8-N-1)
shelllog 也只有 A 會 `logopen`,B/C 是續寫。中途關掉 window 就得手動收程序、log 也斷了。 shelllog 也只有 A 會 `logopen`,B/C 是續寫。中途關掉 window 就得手動收程序、log 也斷了。
- ⚠️ **MP29816 的 Vout 解析度不是固定值**,由 `MFR_VOUT_SCALE_LOOP(0x29)` bit[12:10] 決定, - ⚠️ **MP29816 的 Vout 解析度不是固定值**,由 `MFR_VOUT_SCALE_LOOP(0x29)` bit[12:10] 決定,
`blanton_pwr_data.sh` 執行期從晶片讀;xlsx 上寫的 `/5mV` 與 MP2985B 的 `+49` offset **都是錯的** `blanton_pwr_data.sh` 執行期從晶片讀;xlsx 上寫的 `/5mV` 與 MP2985B 的 `+49` offset **都是錯的**
- ⚠️ **`bcmcmd` 就算失敗也 exit 0**`VLAN: ERROR: Entry exists` /
`Failed to execute the diagnostic command` 都照樣回 0),所以 `blanton_traffic_linespeed.sh`
是**比對輸出**而非看 exit code。另外 `bcmcmd` 一律要 `</dev/null` ——
否則它會吃掉 while 迴圈的 stdin**只有第一個 VLAN 被設定**(V0.3.1 修過的 bug,很難看出來)。
- ⚠️ **`stress_hhmd.py` / `stress_pcie.py` 已於 2026-08-17 由 KC 移除**Status 表 ScriptB #5/#7)。
Script B 現在是 **8 個背景 job**1 mlucas + 5 mem + ssd + usb),不是 10 個。
I2C/PCIe 的持續讀取壓力目前沒有替代品。
- ⚠️ **soak 迴圈沒有 `pause`**:取樣間隔不是 Status 表寫的 10 分鐘,而是取決於指令執行時間。
`-u 0` 已於 `491e807` 移除,A/B/C 現在都跑兩個 switch unit。)
- 🔒 **`.sh` / `.conf` 一定要 LF**。CRLF 的 shell script 在 SONiC 上直接掛(`$'\r': command not found`);
`settings/*.conf` 更陰險 —— 它們是被 `source` 的,`CB_I2C_CH=6` 會變成 `"6\r"`,安靜地壞掉。
`.gitattributes` 已擋住 git 這條路徑,但**不會回頭重寫既有工作區檔案**:早於它 clone 的目錄要
`git rm --cached -r . && git reset --hard` 才會生效,而且 **`git status` 乾淨不代表磁碟上是 LF**
`--renormalize` 只在進 index 的路上轉換)。驗證要看 byte:`grep -rlU $'\r' <dir>`
- ⚠️ **`bmc_monitor*.sh` 需要 exec bit**,且**不是改用 `bash x.sh start` 就能繞過** ——
`do_start` 內部是 `setsid "$SCRIPT_PATH" __daemon`,直接執行該路徑。git 記錄是 `100644`
Windows → 隨身碟 → DUT 也帶不動權限位元,所以佈署後要 `chmod +x ~/Blanton_Script/bmc_monitor*.sh`
沒做的話 Script B 的 start 會 Permission denied、Script C 的 `cat` 撲空,而且**不會報錯**
只會得到一份 BMC 段全空的 log
- ⚠️ **`bmc_monitor*.sh``tail` 子命令是 `tail -n 50 -f`,永遠不返回**。TTL 裡絕對不要呼叫它
Script C 曾因此卡死,`18ce7a1` 改成直接 `cat log/*.log`
- ⚠️ **DUT 的 awk 對 `%d` 會夾到 INT32**(2147483647)。線速流量的計數器是 ~10¹² 量級,
所以 awk 裡輸出大數一律用 `%.0f`double 域,精確到 2⁵³)。`blanton_traffic_linespeed.sh`
V0.4.1 已修;寫新的 awk 報表時要記得同一件事
## 資料夾說明 ## 資料夾說明
- `src/Script_ABC_Blanton/` — 可執行內容(Tera Term 工作目錄;內含要 scp 到 DUT 的 `Blanton_Script/` - `src/Script_ABC_Blanton/` — 可執行內容(Tera Term 工作目錄;內含要 scp 到 DUT 的 `Blanton_Script/`
- `docs/` — 客戶 FPGA 規格、AER/EDAC 檢查依據、逐項 Status 表 - `docs/` — 客戶 FPGA 規格、AER/EDAC 檢查依據、逐項 Status 表(最新為 `*_20260817.xlsx`
- `tools/` — Host 端輔助腳本(log 解析、報表產生、打包) - `tools/` — Host / 離線端:`bcm_mibpair_report_V1.1.0.py`drivshell log → per-pair 報表)、
- `publish/` — 交付用打包輸出(`Blanton_Script_ABC_v<ver>_<date>/` + zip `traffic_loopback_*.txt`bcmcmd 原始指令表,`blanton_traffic_linespeed.sh` 是其 bash 版,
**改 loopback 線路時兩邊要同步**
- `publish/``publish.sh`(打包工具,**進 git**+ `Script_ABC_Blanton_<Ver>/` 產出(**gitignored**
- `secret/` — 🚫 gitignored(除 `README.md``*.example`):DUT 帳密、per-unit BDF 覆寫、COM 設定 - `secret/` — 🚫 gitignored(除 `README.md``*.example`):DUT 帳密、per-unit BDF 覆寫、COM 設定
- `For_AI/` — 🚫 gitignored:AI 協作素材(波形截圖、草稿筆記) - `For_AI/` — 🚫 gitignored:AI 協作素材(波形截圖、草稿筆記)
+108
View File
@@ -0,0 +1,108 @@
#!/usr/bin/env bash
# =============================================================================
# publish.sh -- package src/Script_ABC_Blanton for delivery
# =============================================================================
# Version History:
# V1.0.0 2026-08-21 Initial. Copies src/Script_ABC_Blanton to
# publish/Script_ABC_Blanton_<Version>, taking <Version>
# from the "; Version : Vx.y.z" header of
# 1_Blanton_Script_A.ttl.
# =============================================================================
#
# The version is READ FROM Script A rather than passed in, so the folder name
# can never disagree with the header the tester sees when they open the macro.
# Bump Script A's header first, then run this.
#
# Usage:
# ./publish/publish.sh # publish at Script A's version
# ./publish/publish.sh -v V1.0.4 # override the version
# ./publish/publish.sh -f # overwrite an existing output folder
# ./publish/publish.sh -z # also produce a .zip next to it
# ./publish/publish.sh -n # dry run: print what would happen
# =============================================================================
set -u
SCRIPT_DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)"
REPO_ROOT="$(cd -- "${SCRIPT_DIR}/.." && pwd)"
SRC_DIR="${REPO_ROOT}/src/Script_ABC_Blanton"
OUT_ROOT="${SCRIPT_DIR}"
NAME="Script_ABC_Blanton"
VER=""; FORCE=0; ZIP=0; DRY=0
info() { printf '[\033[34mINFO\033[0m] %s\n' "$*"; }
ok() { printf '[\033[32m OK \033[0m] %s\n' "$*"; }
die() { printf '[\033[31mERR \033[0m] %s\n' "$*" >&2; exit 1; }
usage() { sed -n '2,26p' "${BASH_SOURCE[0]}" | sed 's/^# \{0,1\}//'; exit 0; }
while [ $# -gt 0 ]; do
case "$1" in
-v|--version) VER="${2:-}"; shift 2 ;;
-f|--force) FORCE=1; shift ;;
-z|--zip) ZIP=1; shift ;;
-n|--dry-run) DRY=1; shift ;;
-h|--help) usage ;;
*) die "unknown option '$1' (try -h)" ;;
esac
done
[ -d "${SRC_DIR}" ] || die "source not found: ${SRC_DIR}"
# --- version from Script A's header ------------------------------------------
# The .ttl files are CRLF, so strip the carriage return or it lands in the
# folder name and produces a directory no one can cd into.
if [ -z "${VER}" ]; then
A_TTL="${SRC_DIR}/1_Blanton_Script_A.ttl"
[ -f "${A_TTL}" ] || die "cannot read version: ${A_TTL} missing"
VER=$(grep -m1 -E '^;[[:space:]]*Version[[:space:]]*:' "${A_TTL}" \
| tr -d '\r' | sed -E 's/.*:[[:space:]]*//; s/[[:space:]]+$//')
[ -n "${VER}" ] || die "no '; Version : Vx.y.z' header in $(basename "${A_TTL}")"
info "version from $(basename "${A_TTL}") : ${VER}"
else
info "version (overridden) : ${VER}"
fi
case "${VER}" in
V[0-9]*) ;;
*) die "version '${VER}' does not look like Vx.y.z" ;;
esac
DEST="${OUT_ROOT}/${NAME}_${VER}"
info "source : ${SRC_DIR#${REPO_ROOT}/}"
info "destination : ${DEST#${REPO_ROOT}/}"
if [ -e "${DEST}" ]; then
[ "${FORCE}" = "1" ] || die "${DEST##*/} already exists (use -f to overwrite)"
[ "${DRY}" = "1" ] || rm -rf -- "${DEST}"
info "existing folder removed (-f)"
fi
if [ "${DRY}" = "1" ]; then
info "dry run: nothing copied"
exit 0
fi
# --- copy ---------------------------------------------------------------------
# Captured console logs are per-run and often carry DUT serials, so they are
# never shipped; the empty logs/ directory itself is kept because Script A's
# logopen writes into it.
mkdir -p -- "${DEST}" || die "cannot create ${DEST}"
if command -v rsync >/dev/null 2>&1; then
rsync -a --exclude='*.log' "${SRC_DIR}/" "${DEST}/" || die "rsync failed"
else
cp -a -- "${SRC_DIR}/." "${DEST}/" || die "cp failed"
find "${DEST}" -type f -name '*.log' -delete
fi
# Tera Term's logopen target, in case the source tree never had one
mkdir -p -- "${DEST}/logs"
n_files=$(find "${DEST}" -type f | wc -l)
ok "${n_files} files -> ${DEST##*/}"
if [ "${ZIP}" = "1" ]; then
command -v zip >/dev/null 2>&1 || die "zip not installed"
( cd -- "${OUT_ROOT}" && rm -f -- "${NAME}_${VER}.zip" \
&& zip -qr "${NAME}_${VER}.zip" "${NAME}_${VER}" ) || die "zip failed"
ok "$(du -h "${OUT_ROOT}/${NAME}_${VER}.zip" | cut -f1) -> ${NAME}_${VER}.zip"
fi
+41 -6
View File
@@ -1,7 +1,7 @@
; ============================================================================= ; =============================================================================
; Script A for Blanton ; Script A for Blanton
; Version : V1.0.2 ; Version : V1.0.3
; Date : 2026-08-17 ; Date : 2026-08-21
; Author : ETWen ; Author : ETWen
; ============================================================================= ; =============================================================================
; Version History: ; Version History:
@@ -14,7 +14,8 @@
; ScriptB Remove stress_hhmd, stress_pcie ; ScriptB Remove stress_hhmd, stress_pcie
; Blanton_Script Add blanton_traffic_linespeed.sh ; Blanton_Script Add blanton_traffic_linespeed.sh
; ScriptB/C Add Traffic Test ; ScriptB/C Add Traffic Test
; ; V1.0.3 2026-08-21 utils/wait_init.ttl Created
; ScriptA Add Wait DUT Ready
; ============================================================================= ; =============================================================================
include "config.ttl" include "config.ttl"
@@ -49,6 +50,18 @@ gettime timestr
sprintf2 cmd 'sudo date -s "%s %s"' datestr timestr sprintf2 cmd 'sudo date -s "%s %s"' datestr timestr
sendln cmd sendln cmd
; ========== Show Build Name ==========
wait prompt_sonic_root
sendln "show boot"
; ========== Wait DUT Init ==========
wait prompt_sonic_root
include "utils/wait_init.ttl"
; ========== BMC version ==========
wait prompt_sonic_root
sendln "bmc-manager run 'cat /etc/issue'"
; ========== Load Shell Script ========== ; ========== Load Shell Script ==========
wait prompt_sonic_root wait prompt_sonic_root
sendln "source ~/Blanton_Script/blanton_fpga_pcimem.sh" sendln "source ~/Blanton_Script/blanton_fpga_pcimem.sh"
@@ -94,10 +107,32 @@ wait prompt_sonic_root
;wait prompt_sonic_root ;wait prompt_sonic_root
;flushrecv ;flushrecv
; ========== TRAFFIC-SETUP-STAGE ========== ; ========== 100G Port Status ==========
;sendln "command" wait prompt_sonic_root
sendln "show interfaces status Ethernet513,Ethernet514"
; ========== First Traffic Test ==========
wait prompt_sonic_root
sendln "blanton_traffic_linespeed ps"
wait prompt_sonic_root
sendln "blanton_traffic_linespeed init"
wait prompt_sonic_root
sendln "blanton_traffic_linespeed clear"
wait prompt_sonic_root
pause 15
sendln "blanton_traffic_linespeed show"
wait prompt_sonic_root
sendln "blanton_traffic_linespeed start"
wait prompt_sonic_root
pause 15
sendln "blanton_traffic_linespeed stop"
wait prompt_sonic_root
sendln "blanton_traffic_linespeed report"
; ========== Show Power on uptime ==========
wait prompt_sonic_root
sendln "show uptime"
sendln ""
wait prompt_sonic_root wait prompt_sonic_root
messagebox 'Start Script B' 'Script A DONE' messagebox 'Start Script B' 'Script A DONE'
+18 -7
View File
@@ -20,6 +20,13 @@ sendln 'chmod +x ~/hammer/tools/amd/mlucas-avx2'
wait prompt_sonic_root wait prompt_sonic_root
sendln "~/hammer/tools/amd/mlucas-avx2 -s l -cpu 0:15 > ~/hammer/tools/amd/mlucas_amm_log 2>&1 &" sendln "~/hammer/tools/amd/mlucas-avx2 -s l -cpu 0:15 > ~/hammer/tools/amd/mlucas_amm_log 2>&1 &"
; ========== BMC Stress Test ==========
wait prompt_sonic_root
sendln "./Blanton_Script/bmc_monitor_ddr.sh start"
wait prompt_sonic_root
sendln "./Blanton_Script/bmc_monitor.sh start"
; ========== DDR MEMORY STRESS TEST ========== ; ========== DDR MEMORY STRESS TEST ==========
wait prompt_sonic_root wait prompt_sonic_root
sendln "python ~/hammer/tools/stress_mem.py &" sendln "python ~/hammer/tools/stress_mem.py &"
@@ -44,20 +51,24 @@ sendln "python ~/hammer/tools/stress_usb.py &"
wait prompt_sonic_root wait prompt_sonic_root
sendln "jobs" sendln "jobs"
; ========== Take one round of eye measurement on uplink ports ==========
; ========== 100G Port Status ==========
wait prompt_sonic_root
sendln "show interfaces status Ethernet513,Ethernet514"
; ========== Traffic START ========== ; ========== Traffic START ==========
wait prompt_sonic_root wait prompt_sonic_root
sendln "blanton_traffic_linespeed ps -u 0" sendln "blanton_traffic_linespeed ps"
wait prompt_sonic_root wait prompt_sonic_root
sendln "blanton_traffic_linespeed init -u 0" sendln "blanton_traffic_linespeed init"
wait prompt_sonic_root wait prompt_sonic_root
sendln "blanton_traffic_linespeed clear -u 0" sendln "blanton_traffic_linespeed clear"
wait prompt_sonic_root wait prompt_sonic_root
pause 3 pause 15
sendln "blanton_traffic_linespeed show -u 0" sendln "blanton_traffic_linespeed show"
wait prompt_sonic_root wait prompt_sonic_root
sendln "blanton_traffic_linespeed start -u 0" sendln "blanton_traffic_linespeed start"
+19 -2
View File
@@ -25,6 +25,17 @@ include "utils/show_pcie_error_reg_DDR.ttl"
include "utils/show_pcie_error_reg_I210.ttl" include "utils/show_pcie_error_reg_I210.ttl"
include "utils/show_pcie_error_reg_SSD.ttl" include "utils/show_pcie_error_reg_SSD.ttl"
; ========== BMC TAKE DATA ==========
wait prompt_sonic_root
sendln "./Blanton_Script/bmc_monitor_ddr.sh stop"
wait prompt_sonic_root
sendln "./Blanton_Script/bmc_monitor.sh stop"
wait prompt_sonic_root
sendln "cat ./Blanton_Script/log/bmc_poll.log"
wait prompt_sonic_root
sendln "cat ./Blanton_Script/log/bmc_ddr.log"
; ========== CHECK Stress results ========== ; ========== CHECK Stress results ==========
wait prompt_sonic_root wait prompt_sonic_root
sendln "cat ~/hammer/tools/amd/mlucas_amm_log" sendln "cat ~/hammer/tools/amd/mlucas_amm_log"
@@ -33,9 +44,15 @@ sendln "cat ~/hammer/tools/log.stress_ssd"
; ========== CHECK Traffic counters ========== ; ========== CHECK Traffic counters ==========
wait prompt_sonic_root wait prompt_sonic_root
sendln "blanton_traffic_linespeed stop -u 0" sendln "blanton_traffic_linespeed stop"
wait prompt_sonic_root wait prompt_sonic_root
sendln "blanton_traffic_linespeed report -u 0" sendln "blanton_traffic_linespeed report"
wait prompt_sonic_root
sendln "show reboot-cause"
; ========== Show Power on uptime ==========
wait prompt_sonic_root
sendln "show uptime"
messagebox 'GOOD JOB! Test Case DONE' 'teraterm' messagebox 'GOOD JOB! Test Case DONE' 'teraterm'
@@ -1,444 +0,0 @@
#!/usr/bin/env bash
# =============================================================================
# blanton_cb_i2c.sh -- CB FPGA (F3) I2C / PMBus over the OpenCores I2C master
# =============================================================================
# Version History:
# V0.1.0 2026-06-16 CB sibling of blanton_icb_i2c.sh. Same OpenCores I2C
# handshake, but the I2C block lives in CB FPGA *Function 3*
# register space (base 0x300, stride 0x20, Ch0..Ch17), and
# is reached via cb_fpga 3 <off> (direct PCIe BAR -> .3/resource0).
# =============================================================================
#
# IMPORTANT (vs the hand-written cb_fpga.md pcimem commands):
# * The CB I2C channels are on PCI **Function 3**, so the sysfs file is
# .../0000:02:00.3/resource0 (NOT .0). cb_fpga 3 uses FUNCT3_RES for this.
# * This tool uses **Repeated START** for reads (write reg pointer with WR only,
# no STOP, then Sr + slave|R) so it works for the SMBus/PMBus parts (VRM,
# EFUSE) as well as plain register parts. STOP+START only works for the latter.
# * Slave addresses are 7-bit here. The spec lists 8-bit pairs (e.g. MAX31790
# "0x40/0x41" -> 7-bit 0x20; TCA9546 "0xE8/0xE9" -> 7-bit 0x74). Divide by 2.
#
# CB FPGA F3 I2C channels (base 0x300, stride 0x20) -- 7-bit addrs in (...):
# Ch0 0x300 SWB_0: TCA9546 mux (0x74), M0.C0 temp sensor, ...
# Ch1 0x320 SWB_1: TCA9546 mux (0x74), ...
# Ch2 0x340 SWB_0: SWB_FPGA (0x40), IO Exp PCA9555 (0x24), ...
# Ch3 0x360 SWB_1: SWB_FPGA (0x40), PCA9555 (0x24), ...
# Ch4 0x380 SWB_0: M1 TCA9546 mux (0x74), Clk Gen SI5..., ...
# Ch5 0x3A0 SWB_1: M1 TCA9546 mux (0x74), ...
# Ch6 0x3C0 SWB_0: VRM M29816 (0x20), VRM M2985B (0x21), ... [PMBus]
# Ch7 0x3E0 SWB_1: VRM M29816 (0x20), VRM M2985B (0x21), ... [PMBus]
# Ch8 0x400 SWB_0: VRM M2985B (0x25), M2985B (0x26), ... [PMBus]
# Ch9 0x420 SWB_1: VRM M2985B (0x25), M2985B (0x26), ... [PMBus]
# Ch10 0x440 M0 TCA9543 mux (0x70), M0.C0 Clk Buf 9DBL0452, ...
# Ch11 0x460 Fan Ctrl MAX31790 (0x20)
# Ch12 0x480 ADC leakage ADC128D818 (0x1F)
# Ch13 0x4A0 NFC SM-MFAD4-C02 (0x28)
# Ch14 0x4C0 M1 TCA9543 mux (0x70), M1.C0 QSFP28.P1 xcvr, ...
# Ch15 0x4E0 Fan1/2 EFUSE STEF48H28 (0x10/0x11), ... [PMBus-ish]
# Ch16 0x500 1G PHY I210 Ethernet ctrlr (0x49)
# Ch17 0x520 Chassis EEPROM (0x28..0x29), Diag EEPROM (0x2B), ...
# (slave 7-bit addrs derived from the 8-bit pairs in the register map; verify
# against the device datasheet / your board before driving writes.)
#
# Per-channel register layout (offset from channel base):
# +0x00 I2C_PRSCL_LO +0x04 I2C_PRSCL_HI +0x08 I2C_CTRL (bit7 MOD_EN)
# +0x0C I2C_TX(W)/I2C_RX(R) +0x10 I2C_CMD(W)/I2C_STAT(R)
# +0x14 I2C_MUX_SEL +0x18 I2C_RST (0xD) +0x1C SEM
# CMD : STA=0x80 STO=0x40 RD=0x20 WR=0x10 ACK=0x08(1=NACK) IACK=0x01
# STAT: RX_ACK=0x80(1=NACK) BUSY=0x40 ARB_LOST=0x20 TIP=0x02 INT_FLAG=0x01
# Prescale = (in_clk/(5*scl))-1 @75MHz: 0x88=100kHz, 0x24=400kHz
#
# Usage:
# source blanton_fpga_pcimem.sh # provides cb_fpga / fpga_debug
# source blanton_cb_i2c.sh
# cb_i2c_init 11 # Ch11 (Fan Ctrl) @100kHz
# cb_i2c_scan 11
# cb_i2c_read 11 0x20 0x01 1 # MAX31790 (7-bit 0x20) reg 0x01
# cb_pmbus_read 6 0x20 0x8B 2 # Ch6 VRM READ_VOUT
# =============================================================================
# --- Pull in the register-access backend (cb_fpga) if not present -----------
if ! declare -F cb_fpga >/dev/null 2>&1; then
_CBI2C_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" 2>/dev/null && pwd)"
if [ -n "$_CBI2C_DIR" ] && [ -f "$_CBI2C_DIR/blanton_fpga_pcimem.sh" ]; then
# shellcheck source=/dev/null
source "$_CBI2C_DIR/blanton_fpga_pcimem.sh"
else
echo -e "[\033[33mWARN\033[0m] blanton_cb_i2c.sh: cb_fpga() not found." >&2
echo " source blanton_fpga_pcimem.sh first (provides cb_fpga)." >&2
fi
fi
# === CB I2C geometry =========================================================
CBI2C_FN=3 # CB PCI Function hosting the I2C block
CBI2C_BASE=0x300 # channel 0 base offset (relative to F3 resource0)
CBI2C_CH_STRIDE=0x20
CBI2C_CH_MAX=17
# Per-channel register offsets
_CBI2C_PRSCL_LO=0x00
_CBI2C_PRSCL_HI=0x04
_CBI2C_CTRL=0x08
_CBI2C_TX=0x0C
_CBI2C_RX=0x0C
_CBI2C_CMD=0x10
_CBI2C_STAT=0x10
_CBI2C_MUX_SEL=0x14
_CBI2C_RST=0x18
_CBI2C_SEM=0x1C
# I2C_CTRL_REG / CMD / STAT bits
_CBI2C_EN=0x80
_CBI2C_STA=0x80
_CBI2C_STO=0x40
_CBI2C_RD=0x20
_CBI2C_WR=0x10
_CBI2C_NACK=0x08
_CBI2C_RXACK=0x80
_CBI2C_TIP=0x02
_CBI2C_RST_VAL=0xD
# Defaults
CB_I2C_PRESCALE_LO="${CB_I2C_PRESCALE_LO:-0x88}" # 100 kHz @ 75 MHz
CB_I2C_PRESCALE_HI="${CB_I2C_PRESCALE_HI:-0x00}"
CB_I2C_AUTO_INIT="${CB_I2C_AUTO_INIT:-1}"
CB_I2C_TIP_POLL_MAX="${CB_I2C_TIP_POLL_MAX:-100}"
# --- External I2C-mux RESET control (verified on COM34, 2026-06-16) ----------
# The TCA9543 muxes on CH10 (M0) and CH14 (M1) have an FPGA-driven, active-low
# RESET line. On power-up / FPGA reload the FPGA holds them in reset, so 0x70
# does NOT ACK on the root bus and all downstream legs are unreachable.
#
# The control is a GLOBAL register in CB FPGA *Function 0*, offset 0x7D4
# (I2C_MUX_RST_REG) -- NOT the per-channel I2C_MUX_SEL_REG (+0x14) which is a
# device-select and does nothing to the RESET pin. Per bit: 1 = release,
# 0 = hold-in-reset. Observed default 0xDD (bit1=CH10 mux, bit5=CH14 mux held).
# Writing 0xFF releases all external muxes (normal operating state). This value
# is volatile and is lost on the next power cycle / FPGA reload.
CBI2C_MUX_RST_FN="${CBI2C_MUX_RST_FN:-0}"
CBI2C_MUX_RST_OFF="${CBI2C_MUX_RST_OFF:-0x7D4}"
# Channels whose root bus carries an external mux that hangs off the RESET reg.
# CH10/CH14 = TCA9543; CH0/1/4/5 = TCA9546 (SWB/M1) per the channel map above.
CBI2C_MUX_CHANNELS="${CBI2C_MUX_CHANNELS:-0 1 4 5 10 14}"
# Auto-release external mux RESET before scanning / mux-writing a mux channel.
CB_I2C_AUTO_MUX_RST="${CB_I2C_AUTO_MUX_RST:-1}"
# --- Single FPGA register read/write via cb_fpga <fn> <off> [data] ----------
# _cbi2c_reg <abs_offset> [data] data present = write, absent = read (echo hex)
_cbi2c_reg() {
local off="$1" data="${2:-}"
if [ -n "$data" ]; then cb_fpga "$CBI2C_FN" "$off" "$data"; else cb_fpga "$CBI2C_FN" "$off"; fi
}
# Channel base absolute offset
_cbi2c_chan_base() {
local ch="$1"
if (( ch < 0 || ch > CBI2C_CH_MAX )); then
echo "blanton_cb_i2c: ch must be 0..$CBI2C_CH_MAX" >&2; return 1
fi
printf '0x%X' $(( CBI2C_BASE + ch * CBI2C_CH_STRIDE ))
}
# Normalize a register read-back to a number string ("0x...." -> 0x....)
_cbi2c_num() { local v="$1"; [[ "$v" =~ (0[xX][0-9a-fA-F]+) ]] && echo "${BASH_REMATCH[1]}" || echo "0"; }
# Write CMD then poll STAT (same offset) until TIP clears; echo final status hex
_cbi2c_cmd_wait() {
local cmd_off="$1" cmd_val="$2" i status
_cbi2c_reg "$cmd_off" "$cmd_val" >/dev/null
for (( i=0; i<CB_I2C_TIP_POLL_MAX; i++ )); do
status=$(_cbi2c_num "$(_cbi2c_reg "$cmd_off")")
if (( (status & _CBI2C_TIP) == 0 )); then
printf '0x%X' "$status"; return 0
fi
done
echo -e "[\033[31mERR\033[0m] CB I2C TIP timeout (status=$status)" >&2
printf '0x%X' "${status:-0}"; return 1
}
# Check RX_ACK in status (bit7). 0 = ACK, 1 = NACK -> return 1
_cbi2c_check_ack() {
local status; status=$(_cbi2c_num "$1")
(( (status & _CBI2C_RXACK) != 0 )) && return 1 || return 0
}
# === Public: init / reset / semaphore / mux-sel =============================
# cb_i2c_init <ch> [prescale_lo] [prescale_hi]
cb_i2c_init() {
[[ $# -lt 1 ]] && { echo "Usage: cb_i2c_init <ch> [prescale_lo] [prescale_hi]"; return 1; }
local ch="$1" lo="${2:-$CB_I2C_PRESCALE_LO}" hi="${3:-$CB_I2C_PRESCALE_HI}" base
base=$(_cbi2c_chan_base "$ch") || return 1
_cbi2c_reg "$(printf '0x%X' $((base + _CBI2C_CTRL)))" 0x00 >/dev/null # disable
_cbi2c_reg "$(printf '0x%X' $((base + _CBI2C_PRSCL_LO)))" "$lo" >/dev/null
_cbi2c_reg "$(printf '0x%X' $((base + _CBI2C_PRSCL_HI)))" "$hi" >/dev/null
_cbi2c_reg "$(printf '0x%X' $((base + _CBI2C_CTRL)))" "$_CBI2C_EN" >/dev/null # enable
return 0
}
# cb_i2c_reset <ch>
cb_i2c_reset() {
[[ $# -lt 1 ]] && { echo "Usage: cb_i2c_reset <ch>"; return 1; }
local ch="$1" base; base=$(_cbi2c_chan_base "$ch") || return 1
_cbi2c_reg "$(printf '0x%X' $((base + _CBI2C_RST)))" "$_CBI2C_RST_VAL" >/dev/null
echo " [RST] ch=$ch local I2C controller reset (0xD)"
}
# cb_i2c_sem <ch> [status|acquire [val]|release]
cb_i2c_sem() {
[[ $# -lt 1 ]] && { echo "Usage: cb_i2c_sem <ch> [status|acquire [val]|release]"; return 1; }
local ch="$1" act="${2:-status}" base off cur
base=$(_cbi2c_chan_base "$ch") || return 1
off=$(printf '0x%X' $((base + _CBI2C_SEM)))
case "$act" in
status)
cur=$(_cbi2c_num "$(_cbi2c_reg "$off")")
(( (cur & 0xFF) == 0 )) && echo " [SEM] ch=$ch free (0x00)" \
|| printf ' [SEM] ch=%s held (0x%02X)\n' "$ch" $((cur & 0xFF)) ;;
acquire)
local val="${3:-0x01}"
cur=$(_cbi2c_num "$(_cbi2c_reg "$off")")
if (( (cur & 0xFF) != 0 )); then
printf ' [SEM] ch=%s already held (0x%02X) -- not acquired\n' "$ch" $((cur & 0xFF)); return 1
fi
_cbi2c_reg "$off" "$val" >/dev/null
cur=$(_cbi2c_num "$(_cbi2c_reg "$off")")
(( (cur & 0xFF) != 0 )) && printf ' [SEM] ch=%s acquired (0x%02X)\n' "$ch" $((cur & 0xFF)) \
|| { echo " [SEM] ch=$ch acquire failed"; return 1; } ;;
release)
_cbi2c_reg "$off" 0x00 >/dev/null; echo " [SEM] ch=$ch released (0x00)" ;;
*) echo "Usage: cb_i2c_sem <ch> [status|acquire [val]|release]"; return 1 ;;
esac
}
# cb_i2c_muxsel <ch> <dev_sel> -- write I2C_MUX_SEL_REG (FPGA drives ext mux pins)
cb_i2c_muxsel() {
[[ $# -lt 2 ]] && { echo "Usage: cb_i2c_muxsel <ch> <dev_sel>"; return 1; }
local ch="$1" sel="$2" base; base=$(_cbi2c_chan_base "$ch") || return 1
_cbi2c_reg "$(printf '0x%X' $((base + _CBI2C_MUX_SEL)))" "$sel" >/dev/null
printf ' [MUXSEL] ch=%s DEV_SEL <= 0x%02X\n' "$ch" $((sel & 0xFF))
}
# cb_i2c_mux_reset_release -- de-assert ALL external mux RESET lines (F0 0x7D4 <= 0xFF)
# Global, not per-channel. Volatile: lost on power cycle / FPGA reload.
cb_i2c_mux_reset_release() {
cb_fpga "$CBI2C_MUX_RST_FN" "$CBI2C_MUX_RST_OFF" 0xFF >/dev/null
local rb; rb=$(_cbi2c_num "$(cb_fpga "$CBI2C_MUX_RST_FN" "$CBI2C_MUX_RST_OFF")")
printf ' [MUX-RST] I2C_MUX_RST_REG (F%s %s) <= 0xFF (all external muxes released, readback %s)\n' \
"$CBI2C_MUX_RST_FN" "$CBI2C_MUX_RST_OFF" "$rb"
}
# _cbi2c_is_mux_channel <ch> -- 0 (true) if ch is in CBI2C_MUX_CHANNELS
_cbi2c_is_mux_channel() {
local ch="$1" c
for c in $CBI2C_MUX_CHANNELS; do [[ "$c" == "$ch" ]] && return 0; done
return 1
}
# _cbi2c_auto_mux_rst <ch> -- release external mux RESET if ch is a mux channel
_cbi2c_auto_mux_rst() {
[[ "$CB_I2C_AUTO_MUX_RST" == "1" ]] || return 0
_cbi2c_is_mux_channel "$1" || return 0
cb_i2c_mux_reset_release
}
# === Core transfers (Repeated START, SMBus/PMBus compatible) =================
# _cbi2c_xfer_read <ch> <slave7> <reg> <nbytes> echoes space-separated hex bytes
_cbi2c_xfer_read() {
local ch="$1" slave="$2" reg="$3" n="$4" base tx cmd rx status i last cmdv val out=""
base=$(_cbi2c_chan_base "$ch") || return 1
tx=$(printf '0x%X' $((base + _CBI2C_TX)))
cmd=$(printf '0x%X' $((base + _CBI2C_CMD)))
rx=$(printf '0x%X' $((base + _CBI2C_RX)))
[[ "$CB_I2C_AUTO_INIT" == "1" ]] && cb_i2c_init "$ch" >/dev/null
_cbi2c_reg "$tx" "$(printf '0x%X' $(( (slave << 1) & 0xFF )))" >/dev/null
status=$(_cbi2c_cmd_wait "$cmd" "$(printf '0x%X' $((_CBI2C_STA | _CBI2C_WR)))")
_cbi2c_check_ack "$status" || { echo "ERR: NACK on slave addr (W) 0x$(printf %02X "$slave")" >&2; return 1; }
_cbi2c_reg "$tx" "$(printf '0x%X' $((reg & 0xFF)))" >/dev/null
status=$(_cbi2c_cmd_wait "$cmd" "$(printf '0x%X' $((_CBI2C_WR)))") # WR only, no STOP
_cbi2c_check_ack "$status" || { echo "ERR: NACK on reg 0x$(printf %02X "$reg")" >&2; return 1; }
_cbi2c_reg "$tx" "$(printf '0x%X' $(( ((slave << 1) | 1) & 0xFF )))" >/dev/null
status=$(_cbi2c_cmd_wait "$cmd" "$(printf '0x%X' $((_CBI2C_STA | _CBI2C_WR)))") # Repeated START
_cbi2c_check_ack "$status" || { echo "ERR: NACK on slave addr (R) 0x$(printf %02X "$slave")" >&2; return 1; }
for (( i=0; i<n; i++ )); do
last=$(( i == n-1 ))
if (( last )); then cmdv=$(( _CBI2C_RD | _CBI2C_NACK | _CBI2C_STO )); else cmdv=$(( _CBI2C_RD )); fi
_cbi2c_cmd_wait "$cmd" "$(printf '0x%X' "$cmdv")" >/dev/null
val=$(_cbi2c_num "$(_cbi2c_reg "$rx")")
out+="$(printf '0x%02X ' $((val & 0xFF)))"
done
echo "${out% }"
}
# _cbi2c_xfer_write <ch> <slave7> <reg> <byte> [byte...]
_cbi2c_xfer_write() {
local ch="$1" slave="$2" reg="$3"; shift 3
local base tx cmd status b last cmdv n=$# idx=0
[[ $n -lt 1 ]] && { echo "ERR: no data bytes" >&2; return 1; }
base=$(_cbi2c_chan_base "$ch") || return 1
tx=$(printf '0x%X' $((base + _CBI2C_TX)))
cmd=$(printf '0x%X' $((base + _CBI2C_CMD)))
[[ "$CB_I2C_AUTO_INIT" == "1" ]] && cb_i2c_init "$ch" >/dev/null
_cbi2c_reg "$tx" "$(printf '0x%X' $(( (slave << 1) & 0xFF )))" >/dev/null
status=$(_cbi2c_cmd_wait "$cmd" "$(printf '0x%X' $((_CBI2C_STA | _CBI2C_WR)))")
_cbi2c_check_ack "$status" || { echo "ERR: NACK on slave addr (W) 0x$(printf %02X "$slave")" >&2; return 1; }
_cbi2c_reg "$tx" "$(printf '0x%X' $((reg & 0xFF)))" >/dev/null
status=$(_cbi2c_cmd_wait "$cmd" "$(printf '0x%X' $((_CBI2C_WR)))")
_cbi2c_check_ack "$status" || { echo "ERR: NACK on reg 0x$(printf %02X "$reg")" >&2; return 1; }
for b in "$@"; do
idx=$((idx+1)); last=$(( idx == n ))
_cbi2c_reg "$tx" "$(printf '0x%X' $(( $((b)) & 0xFF )))" >/dev/null
if (( last )); then cmdv=$(( _CBI2C_WR | _CBI2C_STO )); else cmdv=$(( _CBI2C_WR )); fi
status=$(_cbi2c_cmd_wait "$cmd" "$(printf '0x%X' "$cmdv")")
_cbi2c_check_ack "$status" || { echo "ERR: NACK on data byte #$idx" >&2; return 1; }
done
return 0
}
# _cbi2c_xfer_cmd_only <ch> <slave7> <cmd> -- START+W, cmd byte, STOP (no data)
_cbi2c_xfer_cmd_only() {
local ch="$1" slave="$2" cc="$3" base tx cmd status
base=$(_cbi2c_chan_base "$ch") || return 1
tx=$(printf '0x%X' $((base + _CBI2C_TX)))
cmd=$(printf '0x%X' $((base + _CBI2C_CMD)))
[[ "$CB_I2C_AUTO_INIT" == "1" ]] && cb_i2c_init "$ch" >/dev/null
_cbi2c_reg "$tx" "$(printf '0x%X' $(( (slave << 1) & 0xFF )))" >/dev/null
status=$(_cbi2c_cmd_wait "$cmd" "$(printf '0x%X' $((_CBI2C_STA | _CBI2C_WR)))")
_cbi2c_check_ack "$status" || { echo "ERR: NACK on slave addr (W) 0x$(printf %02X "$slave")" >&2; return 1; }
_cbi2c_reg "$tx" "$(printf '0x%X' $((cc & 0xFF)))" >/dev/null
status=$(_cbi2c_cmd_wait "$cmd" "$(printf '0x%X' $((_CBI2C_WR | _CBI2C_STO)))")
_cbi2c_check_ack "$status" || { echo "ERR: NACK on cmd 0x$(printf %02X "$cc")" >&2; return 1; }
return 0
}
# === Public: generic I2C =====================================================
# cb_i2c_read <ch> <slave> <reg> [nbytes]
cb_i2c_read() {
[[ $# -lt 3 ]] && { echo "Usage: cb_i2c_read <ch> <slave> <reg> [nbytes]"; return 1; }
local ch="$1" slave=$(( $2 )) reg=$(( $3 )) n="${4:-1}" bytes
bytes=$(_cbi2c_xfer_read "$ch" "$slave" "$reg" "$n") || return 1
printf ' [I2C-R] ch=%s slave=0x%02X reg=0x%02X => [%s]\n' "$ch" "$slave" "$reg" "$bytes"
}
# cb_i2c_write <ch> <slave> <reg> <byte> [byte...]
cb_i2c_write() {
[[ $# -lt 4 ]] && { echo "Usage: cb_i2c_write <ch> <slave> <reg> <byte> [byte...]"; return 1; }
local ch="$1" slave=$(( $2 )) reg=$(( $3 )); shift 3
_cbi2c_xfer_write "$ch" "$slave" "$reg" "$@" || return 1
printf ' [I2C-W] ch=%s slave=0x%02X reg=0x%02X <= [%s]\n' "$ch" "$slave" "$reg" "$*"
}
# cb_i2c_muxwrite <ch> <mux_slave> <ctrl_byte> -- raw 1-byte write, no reg (TCA954x)
cb_i2c_muxwrite() {
[[ $# -lt 3 ]] && { echo "Usage: cb_i2c_muxwrite <ch> <mux_slave> <ctrl_byte>"; return 1; }
local ch="$1" slave=$(( $2 )) val=$(( $3 )) base tx cmd status
base=$(_cbi2c_chan_base "$ch") || return 1
tx=$(printf '0x%X' $((base + _CBI2C_TX)))
cmd=$(printf '0x%X' $((base + _CBI2C_CMD)))
[[ "$CB_I2C_AUTO_INIT" == "1" ]] && cb_i2c_init "$ch" >/dev/null
_cbi2c_auto_mux_rst "$ch"
_cbi2c_reg "$tx" "$(printf '0x%X' $(( (slave << 1) & 0xFF )))" >/dev/null
status=$(_cbi2c_cmd_wait "$cmd" "$(printf '0x%X' $((_CBI2C_STA | _CBI2C_WR)))")
_cbi2c_check_ack "$status" || { echo "ERR: NACK on mux addr 0x$(printf %02X "$slave")" >&2; return 1; }
_cbi2c_reg "$tx" "$(printf '0x%X' $((val & 0xFF)))" >/dev/null
status=$(_cbi2c_cmd_wait "$cmd" "$(printf '0x%X' $((_CBI2C_WR | _CBI2C_STO)))")
_cbi2c_check_ack "$status" || { echo "ERR: NACK on mux ctrl byte" >&2; return 1; }
printf ' [MUX-W] ch=%s mux=0x%02X <= 0x%02X\n' "$ch" "$slave" "$val"
}
# cb_i2c_scan <ch> -- probe 0x08..0x77, report slaves that ACK
cb_i2c_scan() {
[[ $# -lt 1 ]] && { echo "Usage: cb_i2c_scan <ch>"; return 1; }
local ch="$1" base tx cmd a status found=""
base=$(_cbi2c_chan_base "$ch") || return 1
tx=$(printf '0x%X' $((base + _CBI2C_TX)))
cmd=$(printf '0x%X' $((base + _CBI2C_CMD)))
cb_i2c_init "$ch" >/dev/null
_cbi2c_auto_mux_rst "$ch"
for (( a=0x08; a<=0x77; a++ )); do
_cbi2c_reg "$tx" "$(printf '0x%X' $(( (a << 1) & 0xFF )))" >/dev/null
status=$(_cbi2c_cmd_wait "$cmd" "$(printf '0x%X' $((_CBI2C_STA | _CBI2C_WR | _CBI2C_STO)))")
_cbi2c_check_ack "$status" && found+="$(printf '0x%02X ' "$a")"
done
[[ -n "$found" ]] && echo " [SCAN] ch=$ch found: ${found% }" || echo " [SCAN] ch=$ch no devices"
}
# === Public: PMBus (== SMBus; command code + Repeated START read) ============
# cb_pmbus_read <ch> <slave> <cmd> [nbytes] (default 2 bytes, LSB first)
cb_pmbus_read() {
[[ $# -lt 3 ]] && { echo "Usage: cb_pmbus_read <ch> <slave> <cmd> [nbytes]"; return 1; }
local ch="$1" slave=$(( $2 )) cc=$(( $3 )) n="${4:-2}" bytes
bytes=$(_cbi2c_xfer_read "$ch" "$slave" "$cc" "$n") || return 1
if [[ "$n" == "2" ]]; then
local b0 b1 word; b0=$(( $(echo "$bytes" | awk '{print $1}') )); b1=$(( $(echo "$bytes" | awk '{print $2}') ))
word=$(( b0 | (b1 << 8) ))
printf ' [PMB-R] ch=%s slave=0x%02X cmd=0x%02X => 0x%04X raw=[%s] linear16=%s\n' \
"$ch" "$slave" "$cc" "$word" "$bytes" "$(cb_pmbus_linear16 "$word")"
else
printf ' [PMB-R] ch=%s slave=0x%02X cmd=0x%02X => [%s]\n' "$ch" "$slave" "$cc" "$bytes"
fi
}
# cb_pmbus_write <ch> <slave> <cmd> [data byte...] (no byte = send-byte)
cb_pmbus_write() {
[[ $# -lt 3 ]] && { echo "Usage: cb_pmbus_write <ch> <slave> <cmd> [data byte...]"; return 1; }
local ch="$1" slave=$(( $2 )) cc=$(( $3 )); shift 3
if [[ $# -eq 0 ]]; then
_cbi2c_xfer_cmd_only "$ch" "$slave" "$cc" || return 1
printf ' [PMB-W] ch=%s slave=0x%02X cmd=0x%02X <= (send-byte)\n' "$ch" "$slave" "$cc"; return 0
fi
_cbi2c_xfer_write "$ch" "$slave" "$cc" "$@" || return 1
printf ' [PMB-W] ch=%s slave=0x%02X cmd=0x%02X <= [%s]\n' "$ch" "$slave" "$cc" "$*"
}
# cb_pmbus_linear16 <word>
cb_pmbus_linear16() {
local word=$(( $1 & 0xFFFF ))
awk -v w="$word" 'BEGIN{
e=int(w/2048)%32; if(e>15)e-=32;
m=w%2048; if(m>1023)m-=2048;
printf "%.4f", m * (2.0 ^ e);
}'
}
# === Help ====================================================================
blanton_cb_i2c_help() {
echo -e "\033[1mblanton_cb_i2c.sh - CB FPGA (F3) I2C / PMBus over OpenCores I2C master\033[0m"
echo ""
echo -e " Backend: \033[36mcb_fpga $CBI2C_FN\033[0m (direct PCIe BAR -> .3/resource0), base 0x300, Ch0..$CBI2C_CH_MAX"
echo ""
echo -e "\033[1mSetup\033[0m"
echo -e " \033[33mcb_i2c_init\033[0m <ch> [pre_lo] [pre_hi] prescale + enable (def 0x88/0x00 = 100kHz)"
echo -e " \033[33mcb_i2c_reset\033[0m <ch> LCL_RST=0xD"
echo -e " \033[33mcb_i2c_sem\033[0m <ch> [status|acquire [v]|release]"
echo -e " \033[33mcb_i2c_muxsel\033[0m <ch> <dev_sel> write I2C_MUX_SEL_REG"
echo -e " \033[33mcb_i2c_mux_reset_release\033[0m de-assert ALL ext mux RESET (F0 0x7D4<=0xFF)"
echo ""
echo -e "\033[1mGeneric I2C\033[0m"
echo -e " \033[33mcb_i2c_scan\033[0m <ch>"
echo -e " \033[33mcb_i2c_read\033[0m <ch> <slave> <reg> [nbytes]"
echo -e " \033[33mcb_i2c_write\033[0m <ch> <slave> <reg> <byte> [byte...]"
echo -e " \033[33mcb_i2c_muxwrite\033[0m <ch> <mux_slave> <ctrl_byte> raw 1-byte (TCA954x)"
echo ""
echo -e "\033[1mPMBus / SMBus\033[0m"
echo -e " \033[33mcb_pmbus_read\033[0m <ch> <slave> <cmd> [nbytes] (def 2, LE word + linear16)"
echo -e " \033[33mcb_pmbus_write\033[0m <ch> <slave> <cmd> [byte...]"
echo -e " \033[33mcb_pmbus_linear16\033[0m <word>"
echo ""
echo -e " Slave = 7-bit (tool shifts). Spec lists 8-bit pairs -> divide by 2."
echo -e " \033[33mfpga_debug on\033[0m to see every underlying pcimem command."
}
blanton_cb_i2c_help
@@ -1,270 +0,0 @@
#!/usr/bin/env bash
# =============================================================================
# blanton_fpga_pcimem.sh -- pcimem backend, sysfs resource file + relative offset
# =============================================================================
# Version History:
# V1.3.0 2026-06-12 pcimem variant (SONiC hardware). memtool variant: blanton_fpga_memtool.sh
# Version number kept in sync with blanton_fpga_memtool.sh
# V1.4.0 2026-06-15 Fix: VSPI window bases pointed at the VSPI-Flash block
# (0x340/0x380/0x3C0/0x400, config-flash channel) so register
# reads returned 0; corrected to the VSPI register block
# (PMC 0x640 / ICB 0x680 / SWB0 0x6C0 / SWB1 0x700)
# Add: _vspi_fnwarn guard - warns when PMC/ICB/SWB are called with
# cb_fpga-style "<fn> <addr>" (a read turning into a write)
# Change: suppress pcimem write stdout noise (mmap/Written lines)
# Docs: comments/help translated to English (ASCII-only for SONiC console)
# V1.4.1 2026-06-23 Fix: BDF in example auto-detect was wrong (02:00 vs 04:00); corrected to match the lspci example.
# =============================================================================
#
# SONiC hardware has no memtool / devmem, but it has pcimem. pcimem mmaps the PCI
# sysfs resource file directly and takes "resource file + offset relative to BAR
# start", not an absolute physical address.
#
# pcimem { sysfile } { offset } [ type*count [ data ] ]
# sysfile : sysfs file for the pci resource (e.g. .../resource0)
# offset : offset into pci memory region
# type : [b]yte, [h]alfword, [w]ord, [d]ouble-word
# *count : number of items (w*100 dump 100 words)
# data : data to be written
#
# This tool always accesses as word (w, 32-bit).
#
# After sourcing this file the following functions are available:
# cb_fpga <fn:0-3> <addr> [data]
# pmc_fpga <addr> [data]
# icb_fpga <addr> [data]
# swb0_fpga <addr> [data]
# swb1_fpga <addr> [data]
#
# data present = write, absent = read.
# Usage: source blanton_fpga_pcimem.sh
# === CB FPGA PCIe resource file (sysfs) ===
# Use lspci -D to find the BDF (domain:bus:dev.fn); resource0 is the sysfs mapping of BAR0.
# e.g. lspci -Dnn | grep -i fpga -> 0000:02:00.0 ...
# memtool variant fills in the BAR value; pcimem variant fills in the resource file
# path instead (offset becomes relative to BAR).
FUNCT0_RES=/sys/bus/pci/devices/0000:04:00.0/resource0
FUNCT1_RES=/sys/bus/pci/devices/0000:04:00.1/resource0
FUNCT2_RES=/sys/bus/pci/devices/0000:04:00.2/resource0
FUNCT3_RES=/sys/bus/pci/devices/0000:04:00.3/resource0
# Auto-detect example (uncomment to use; needs the FPGA's BDF prefix):
# _BDF=0000:02:00
# FUNCT0_RES=/sys/bus/pci/devices/${_BDF}.0/resource0
# FUNCT1_RES=/sys/bus/pci/devices/${_BDF}.1/resource0
# FUNCT2_RES=/sys/bus/pci/devices/${_BDF}.2/resource0
# FUNCT3_RES=/sys/bus/pci/devices/${_BDF}.3/resource0
# === VSPI window base offset (relative to CB Function 2 resource0 start) ===
# NOTE: CB.F2 has TWO near-identical VSPI register blocks (see spec register map):
# - VSPI-Flash-* @ 0x340/0x380/0x3C0/0x400 -> accesses the remote FPGA's CONFIG FLASH
# - VSPI-* @ 0x640/0x680/0x6C0/0x700 -> accesses the remote FPGA's REGISTERS
# This tool reads/writes remote registers, so it uses the VSPI-* block (0x640+).
# The VSPI-Flash bases are kept below (commented) in case flash access is needed later.
VSPI_PMC_BASE=0x640
VSPI_ICB_BASE=0x680
VSPI_SWB0_BASE=0x6C0
VSPI_SWB1_BASE=0x700
# VSPI-Flash window bases (config-flash access, not register access):
# VSPI_FLASH_PMC_BASE=0x340
# VSPI_FLASH_ICB_BASE=0x380
# VSPI_FLASH_SWB0_BASE=0x3C0
# VSPI_FLASH_SWB1_BASE=0x400
# === VSPI protocol constants ===
_VSPI_CMD_STAT=0x00
_VSPI_ADDR=0x04
_VSPI_WR_DATA=0x08
_VSPI_RD_DATA=0x0C
_VSPI_NEW_CMD=0x01
_VSPI_READ_W=0x02
_VSPI_WRITE_W=0x04
_VSPI_READY_MASK=0x100
_VSPI_POLL_MAX=200
# === pcimem command ===
PCIMEM_CMD="${PCIMEM_CMD:-pcimem}"
# === Debug mode (1=print commands AND execute, 0=execute only) ===
DEBUG_MODE=${DEBUG_MODE:-0}
fpga_debug() {
if [ "${1:-}" = "on" ]; then DEBUG_MODE=1
elif [ "${1:-}" = "off" ]; then DEBUG_MODE=0
else [ "$DEBUG_MODE" = "0" ] && DEBUG_MODE=1 || DEBUG_MODE=0; fi
echo -e "[\033[34mINFO\033[0m] DEBUG_MODE=$DEBUG_MODE"
}
# Wrapper: debug mode prints command (to stderr) AND executes, normal mode executes only.
# On read, returns the parsed hex value (stripping pcimem's mmap noise).
# Usage: _pcimem <sysfile> <offset> [data] (data present = write, absent = read)
_pcimem() {
local sysfile="$1" offset="$2" data="${3:-}"
if [[ -n "$data" ]]; then
[ "$DEBUG_MODE" = "1" ] && \
echo -e "\033[90m[DEBG] $PCIMEM_CMD $sysfile $offset w $data\033[0m" >&2
# Write output (opened / Target offset / mmap / Written...readback) is never
# parsed -> drop stdout noise; keep stderr so real errors still surface.
$PCIMEM_CMD "$sysfile" "$offset" w "$data" >/dev/null
else
[ "$DEBUG_MODE" = "1" ] && \
echo -e "\033[90m[DEBG] $PCIMEM_CMD $sysfile $offset w\033[0m" >&2
# pcimem read prints mmap info + "Value at offset ...: 0xXXXX";
# take the last 0x... token as the actual read-back value.
$PCIMEM_CMD "$sysfile" "$offset" w | grep -oiE '0x[0-9a-f]+' | tail -1
fi
}
# --- Internal VSPI helpers (all hang off CB Function 2's resource0) ---
_vspi_read() {
local win_off="$1" remote_offset="$2"
local cmd_off add_off rd_off addr_val status i
cmd_off=$(printf '0x%X' $(( win_off + _VSPI_CMD_STAT )))
add_off=$(printf '0x%X' $(( win_off + _VSPI_ADDR )))
rd_off=$(printf '0x%X' $(( win_off + _VSPI_RD_DATA )))
addr_val=$(printf '0x%08X' $(( (0x0 << 20) | (remote_offset & 0xFFFFF) )))
_pcimem "$FUNCT2_RES" "$add_off" "$addr_val"
_pcimem "$FUNCT2_RES" "$cmd_off" $(printf '0x%X' $(( _VSPI_NEW_CMD | _VSPI_READ_W )))
for (( i=0; i<_VSPI_POLL_MAX; i++ )); do
status=$(_pcimem "$FUNCT2_RES" "$cmd_off")
if (( ( ${status:-0} & _VSPI_READY_MASK) != 0 )); then
_pcimem "$FUNCT2_RES" "$rd_off"
return 0
fi
done
echo "ERROR: VSPI read timeout @ offset $remote_offset" >&2
return 1
}
_vspi_write() {
local win_off="$1" remote_offset="$2" data="$3"
local cmd_off add_off wr_off addr_val status i
cmd_off=$(printf '0x%X' $(( win_off + _VSPI_CMD_STAT )))
add_off=$(printf '0x%X' $(( win_off + _VSPI_ADDR )))
wr_off=$(printf '0x%X' $(( win_off + _VSPI_WR_DATA )))
addr_val=$(printf '0x%08X' $(( (0x0 << 20) | (remote_offset & 0xFFFFF) )))
_pcimem "$FUNCT2_RES" "$wr_off" "$data"
_pcimem "$FUNCT2_RES" "$add_off" "$addr_val"
_pcimem "$FUNCT2_RES" "$cmd_off" $(printf '0x%X' $(( _VSPI_NEW_CMD | _VSPI_WRITE_W )))
for (( i=0; i<_VSPI_POLL_MAX; i++ )); do
status=$(_pcimem "$FUNCT2_RES" "$cmd_off")
if (( ( ${status:-0} & _VSPI_READY_MASK) != 0 )); then
return 0
fi
done
echo "ERROR: VSPI write timeout @ offset $remote_offset" >&2
return 1
}
# Guard: PMC/ICB/SWB are single-function; signature is <addr> [data], NO fn arg.
# Common pitfall: using cb_fpga's "<fn> <addr>" form -> 2nd arg becomes data, a read
# turns into a write. If addr is 0~3 (looks like fn) AND data is given, warn and
# confirm; on a non-tty (script) just warn, don't block.
# Usage: _vspi_fnwarn <name> <addr> <data> non-zero return = cancel
_vspi_fnwarn() {
local name="$1" addr="$2" data="$3"
[[ -z "$data" ]] && return 0 # read, nothing to warn
case "$addr" in
0|1|2|3|0x0|0x1|0x2|0x3|0x00|0x01|0x02|0x03) ;; # looks like fn, warn below
*) return 0 ;; # normal offset, allow
esac
echo -e "[\033[33mWARN\033[0m] $name has no fn arg; signature is <addr> [data] (NOT cb_fpga's <fn> <addr>)." >&2
echo -e " This will WRITE $data to PMC/ICB/SWB offset $addr." >&2
echo -e " To READ offset $addr, pass a single arg: $name $addr" >&2
if [ -t 0 ]; then
local ans
read -r -p " Proceed with write anyway? [y/N] " ans
case "$ans" in
y|Y|yes|YES) return 0 ;;
*) echo " Cancelled." >&2; return 1 ;;
esac
fi
return 0
}
# === Public functions ===
cb_fpga() {
if [[ $# -lt 2 ]]; then
echo "Usage: cb_fpga <fn:0-3> <addr> [data]"; return 1
fi
local fn="$1" addr="$2" data="${3:-}" res
case "$fn" in
0) res=$FUNCT0_RES ;; 1) res=$FUNCT1_RES ;;
2) res=$FUNCT2_RES ;; 3) res=$FUNCT3_RES ;;
*) echo "cb_fpga: fn must be 0-3" >&2; return 1 ;;
esac
# pcimem offset is relative to BAR start; do not add BAR.
if [[ -z "$data" ]]; then
_pcimem "$res" "$addr"
else
_pcimem "$res" "$addr" "$data"
fi
}
pmc_fpga() {
if [[ $# -lt 1 ]]; then
echo "Usage: pmc_fpga <addr> [data]"; return 1
fi
local addr="$1" data="${2:-}"
_vspi_fnwarn pmc_fpga "$addr" "$data" || return 1
if [[ -z "$data" ]]; then _vspi_read "$VSPI_PMC_BASE" "$addr"
else _vspi_write "$VSPI_PMC_BASE" "$addr" "$data"; fi
}
icb_fpga() {
if [[ $# -lt 1 ]]; then
echo "Usage: icb_fpga <addr> [data]"; return 1
fi
local addr="$1" data="${2:-}"
_vspi_fnwarn icb_fpga "$addr" "$data" || return 1
if [[ -z "$data" ]]; then _vspi_read "$VSPI_ICB_BASE" "$addr"
else _vspi_write "$VSPI_ICB_BASE" "$addr" "$data"; fi
}
swb0_fpga() {
if [[ $# -lt 1 ]]; then
echo "Usage: swb0_fpga <addr> [data]"; return 1
fi
local addr="$1" data="${2:-}"
_vspi_fnwarn swb0_fpga "$addr" "$data" || return 1
if [[ -z "$data" ]]; then _vspi_read "$VSPI_SWB0_BASE" "$addr"
else _vspi_write "$VSPI_SWB0_BASE" "$addr" "$data"; fi
}
swb1_fpga() {
if [[ $# -lt 1 ]]; then
echo "Usage: swb1_fpga <addr> [data]"; return 1
fi
local addr="$1" data="${2:-}"
_vspi_fnwarn swb1_fpga "$addr" "$data" || return 1
if [[ -z "$data" ]]; then _vspi_read "$VSPI_SWB1_BASE" "$addr"
else _vspi_write "$VSPI_SWB1_BASE" "$addr" "$data"; fi
}
blanton_fpga_help() {
echo -e "\033[1mblanton_fpga (pcimem) - FPGA Register Access Tool\033[0m"
echo ""
echo -e "\033[1mUsage:\033[0m"
echo -e " \033[33mcb_fpga\033[0m <fn:0-3> <addr> [data] CB FPGA direct (sysfs resource0)"
echo -e " \033[33mpmc_fpga\033[0m <addr> [data] PMC FPGA via VSPI"
echo -e " \033[33micb_fpga\033[0m <addr> [data] ICB FPGA via VSPI"
echo -e " \033[33mswb0_fpga\033[0m <addr> [data] SWB0 FPGA via VSPI"
echo -e " \033[33mswb1_fpga\033[0m <addr> [data] SWB1 FPGA via VSPI"
echo -e " \033[33mfpga_debug\033[0m on|off Toggle debug mode (print commands and execute)"
echo ""
echo -e " Omit [data] to \033[32mread\033[0m, provide [data] to \033[31mwrite\033[0m."
echo -e " Backend: \033[36mpcimem\033[0m (sysfs resource file + relative offset)."
echo -e " Run \033[33mblanton_fpga_help\033[0m to show this message again."
}
blanton_fpga_help
@@ -5,6 +5,20 @@
# ============================================================================= # =============================================================================
# Version Control # Version Control
# ----------------------------------------------------------------------------- # -----------------------------------------------------------------------------
# v2.6.0 SWB path actually wired to the shared CB I2C package. blanton_cb_i2c.sh
# and blanton_fpga_pcimem.sh live in the PARENT dir (Blanton_Script/), not
# next to margin.sh, so the backend is now searched in both places.
# Adds, for TRANSPORT=swb only (CB/i2c path untouched):
# * cb_i2c_init <ch> once in margin_init (prescale 0x88 = 100kHz) and
# CB_I2C_AUTO_INIT=0 afterwards -> 4 fewer pcimem writes per transfer
# * cb_pmbus_read presence probe of every LTC2977 in CHIPS
# * return-code checking on every cb_pmbus_read/write (NACK is reported
# instead of silently reading 0x0000)
# * DEBUG_MODE collision fix: blanton_fpga_pcimem.sh uses the same var
# name, so pcimem tracing is gated by MARGIN_SWB_PCIMEM_DEBUG
# * margin_swb_* wrappers (info/probe/scan/reset/sem) for debugging
# Physical map: CB F3 Ch6=SWB0 CONN14/16, Ch8=SWB0 CONN13/15,
# Ch7=SWB1 CONN14/16, Ch9=SWB1 CONN13/15
# v2.5.0 SWB margin support via a per-conf TRANSPORT layer. The SWB margin # v2.5.0 SWB margin support via a per-conf TRANSPORT layer. The SWB margin
# boards are ALSO LTC2980 (2x LTC2977) -- same chip as CB -- just reached # boards are ALSO LTC2980 (2x LTC2977) -- same chip as CB -- just reached
# through the CB FPGA F3 I2C master instead of a native bus. So the whole # through the CB FPGA F3 I2C master instead of a native bus. So the whole
@@ -55,6 +69,37 @@ LOG_MODE=1
TRANSPORT="i2c" TRANSPORT="i2c"
CB_I2C_CH="" CB_I2C_CH=""
# --- SWB transport backend (blanton_cb_i2c.sh + blanton_fpga_pcimem.sh) -------
# The SWB margin boards' LTC2980s hang off the CB FPGA Function 3 I2C master.
# Physical mapping (per bench wiring):
# CB F3 Ch6 -> SWB0 CONN14 / CONN16
# CB F3 Ch8 -> SWB0 CONN13 / CONN15
# CB F3 Ch7 -> SWB1 CONN14 / CONN16
# CB F3 Ch9 -> SWB1 CONN13 / CONN15
# Two CONNs share one FPGA I2C channel, so the two boards on a channel MUST use
# different LTC2977 slave addresses (the CHIPS array in each .conf).
MARGIN_SWB_CHANNELS="${MARGIN_SWB_CHANNELS:-6 7 8 9}"
# SCL prescale for cb_i2c_init: 0x88 = 100kHz, 0x24 = 400kHz (@75MHz core clk).
MARGIN_SWB_PRESCALE_LO="${MARGIN_SWB_PRESCALE_LO:-0x88}"
MARGIN_SWB_PRESCALE_HI="${MARGIN_SWB_PRESCALE_HI:-0x00}"
# 0 = cb_i2c_init once in margin_init, then CB_I2C_AUTO_INIT=0 so each
# cb_pmbus_* transfer skips the 4-register prescale/enable dance (much faster:
# margin_status all = 32 transfers). Set 1 to keep the package default (re-init
# on every transfer) if something else on the box reprograms the channel.
MARGIN_SWB_AUTO_INIT="${MARGIN_SWB_AUTO_INIT:-0}"
# 1 = grab the per-channel FPGA semaphore (cb_i2c_sem acquire) in margin_init and
# hold it for the whole session; margin_swb_sem_release frees it. Default off so
# margin never fails just because someone else holds it.
MARGIN_SWB_USE_SEM="${MARGIN_SWB_USE_SEM:-0}"
# 1 = probe every LTC2977 in CHIPS (PMBus PAGE read) at margin_init.
MARGIN_SWB_PROBE="${MARGIN_SWB_PROBE:-1}"
# 1 = also let blanton_fpga_pcimem.sh print every underlying pcimem command.
# Needed because that script's debug flag is ALSO called DEBUG_MODE; margin_debug
# would otherwise flood the console with pcimem lines.
MARGIN_SWB_PCIMEM_DEBUG="${MARGIN_SWB_PCIMEM_DEBUG:-0}"
_SWB_READY=0
_SWB_SEM_HELD=0
# Channel enable for margining. Rails the LTC2980 does not sequence (externally # Channel enable for margining. Rails the LTC2980 does not sequence (externally
# enabled, e.g. an always-on MPQ8625 buck) sit with STATUS_WORD OFF=1 and won't # enabled, e.g. an always-on MPQ8625 buck) sit with STATUS_WORD OFF=1 and won't
# margin, because the servo DAC never soft-connects. Writing ON_OFF_CONFIG (0x02) # margin, because the servo DAC never soft-connects. Writing ON_OFF_CONFIG (0x02)
@@ -131,6 +176,233 @@ margin_debug() {
echo -e "[\033[34mINFO\033[0m] DEBUG_MODE=$DEBUG_MODE" echo -e "[\033[34mINFO\033[0m] DEBUG_MODE=$DEBUG_MODE"
} }
# =============================================================================
# SWB transport backend (TRANSPORT=swb only -- the CB/i2c path never gets here)
# -----------------------------------------------------------------------------
# Wire ops come from the shared CB I2C package:
# blanton_fpga_pcimem.sh -> cb_fpga <fn> <off> [data] (PCIe BAR via pcimem)
# blanton_cb_i2c.sh -> cb_i2c_init / cb_i2c_reset / cb_i2c_sem /
# cb_i2c_scan / cb_pmbus_read / cb_pmbus_write
# Both live in the parent Blanton_Script/ directory (a copy next to margin.sh is
# also honoured if you ever bundle one).
# =============================================================================
# Locate a backend script: next to margin.sh first, then the parent dir.
_swb_find_backend() {
local name="$1" p
for p in "$SCRIPT_DIR/$name" "$SCRIPT_DIR/../$name"; do
[ -f "$p" ] && { echo "$p"; return 0; }
done
return 1
}
# Source blanton_fpga_pcimem.sh (cb_fpga) + blanton_cb_i2c.sh (cb_pmbus_*).
_swb_backend_load() {
local f
if ! declare -F cb_fpga >/dev/null 2>&1; then
if f=$(_swb_find_backend "blanton_fpga_pcimem.sh"); then
# shellcheck source=/dev/null
source "$f"
echo -e "[\033[34mINFO\033[0m] SWB backend: sourced $f"
else
echo -e "[\033[31mERRO\033[0m] blanton_fpga_pcimem.sh not found in $SCRIPT_DIR or its parent"
return 1
fi
fi
if ! declare -F cb_pmbus_read >/dev/null 2>&1; then
if f=$(_swb_find_backend "blanton_cb_i2c.sh"); then
# shellcheck source=/dev/null
source "$f" >/dev/null
echo -e "[\033[34mINFO\033[0m] SWB backend: sourced $f"
else
echo -e "[\033[31mERRO\033[0m] blanton_cb_i2c.sh not found in $SCRIPT_DIR or its parent"
return 1
fi
fi
declare -F cb_pmbus_read >/dev/null 2>&1 || {
echo -e "[\033[31mERRO\033[0m] cb_pmbus_read still undefined after sourcing the backend"; return 1; }
return 0
}
# Run a cb_* command with pcimem tracing suppressed (shared DEBUG_MODE var).
# Echoes the command's stdout, preserves its return code.
_swb_call() {
local _saved="$DEBUG_MODE" _rc
[ "$MARGIN_SWB_PCIMEM_DEBUG" = "1" ] || DEBUG_MODE=0
"$@"
_rc=$?
DEBUG_MODE="$_saved"
return $_rc
}
# Probe one LTC2977: 1-byte PMBus read of PAGE (0x00). Returns cb_pmbus_read's rc.
_swb_probe_addr() {
_swb_call cb_pmbus_read "$CB_I2C_CH" "$1" 0x00 1 >/dev/null 2>&1
}
# --- SWB wire ops used by the transport switch below ------------------------
# Both use _ADDR (LTC2977 7-bit slave) set by _get_chip and CB_I2C_CH from the
# .conf. Unlike i2cset/i2cget these report failures: a NACK on the FPGA I2C
# master used to silently turn into Vout=0.0000.
# _swb_write <reg> <byte> [byte...] -> cb_pmbus_write (LSB first for LINEAR16)
_swb_write() {
local reg=$1; shift
[ "$DEBUG_MODE" = "1" ] && echo -e "\033[90m[DEBG] cb_pmbus_write $CB_I2C_CH $_ADDR $reg $*\033[0m"
if ! _swb_call cb_pmbus_write "$CB_I2C_CH" "$_ADDR" "$reg" "$@" >/dev/null 2>&1; then
echo -e "[\033[31mERRO\033[0m] cb_pmbus_write ch=$CB_I2C_CH $_ADDR reg=$reg failed (NACK/timeout)" >&2
_log_detail "ERRO cb_pmbus_write ch=$CB_I2C_CH $_ADDR $reg $*"
return 1
fi
return 0
}
# _swb_read_word <reg> -> echoes "0xXXXX" parsed out of cb_pmbus_read's "=> 0xXXXX"
_swb_read_word() {
local reg=$1 out word
[ "$DEBUG_MODE" = "1" ] && echo -e "\033[90m[DEBG] cb_pmbus_read $CB_I2C_CH $_ADDR $reg 2\033[0m" >&2
if ! out=$(_swb_call cb_pmbus_read "$CB_I2C_CH" "$_ADDR" "$reg" 2 2>/dev/null); then
echo -e "[\033[31mERRO\033[0m] cb_pmbus_read ch=$CB_I2C_CH $_ADDR reg=$reg failed (NACK/timeout)" >&2
_log_detail "ERRO cb_pmbus_read ch=$CB_I2C_CH $_ADDR $reg"
echo "0x0000"
return 1
fi
# cb_pmbus_read prints " [PMB-R] ... => 0xXXXX raw=[...] linear16=..."
word=$(printf '%s' "$out" | awk -F'=> ' 'NF>1{print $2}' | awk '{print $1}')
[[ "$word" =~ ^0[xX][0-9a-fA-F]+$ ]] || word="0x0000"
echo "$word"
}
# Bring up the FPGA I2C channel for this .conf: validate CB_I2C_CH, prescale +
# enable it once (cb_i2c_init), optionally take the semaphore, then probe CHIPS.
_swb_setup() {
_SWB_READY=0
if [ -z "$CB_I2C_CH" ]; then
echo -e "[\033[31mERRO\033[0m] TRANSPORT=swb but CB_I2C_CH is empty in the .conf"
return 1
fi
if ! [[ "$CB_I2C_CH" =~ ^[0-9]+$ ]] || [ "$CB_I2C_CH" -gt 17 ]; then
echo -e "[\033[31mERRO\033[0m] CB_I2C_CH='$CB_I2C_CH' invalid (CB F3 I2C channels are 0..17)"
return 1
fi
case " $MARGIN_SWB_CHANNELS " in
*" $CB_I2C_CH "*) ;;
*) echo -e "[\033[33mWARN\033[0m] CB_I2C_CH=$CB_I2C_CH is not a known SWB VRM channel ($MARGIN_SWB_CHANNELS)" ;;
esac
# Prescale + MOD_EN on this channel.
if ! _swb_call cb_i2c_init "$CB_I2C_CH" "$MARGIN_SWB_PRESCALE_LO" "$MARGIN_SWB_PRESCALE_HI"; then
echo -e "[\033[31mERRO\033[0m] cb_i2c_init $CB_I2C_CH failed (FPGA/pcimem access?)"
return 1
fi
CB_I2C_AUTO_INIT="$MARGIN_SWB_AUTO_INIT"
local _khz="?"; [ "$MARGIN_SWB_PRESCALE_LO" = "0x88" ] && _khz="100"
[ "$MARGIN_SWB_PRESCALE_LO" = "0x24" ] && _khz="400"
echo -e "[\033[34mINFO\033[0m] cb_i2c_init ch=$CB_I2C_CH prescale=$MARGIN_SWB_PRESCALE_LO/$MARGIN_SWB_PRESCALE_HI (~${_khz}kHz), CB_I2C_AUTO_INIT=$CB_I2C_AUTO_INIT"
_log "swb: cb_i2c_init ch=$CB_I2C_CH prescale=$MARGIN_SWB_PRESCALE_LO auto_init=$CB_I2C_AUTO_INIT"
if [ "$MARGIN_SWB_USE_SEM" = "1" ]; then
if _swb_call cb_i2c_sem "$CB_I2C_CH" acquire; then
_SWB_SEM_HELD=1
else
echo -e "[\033[33mWARN\033[0m] semaphore on ch=$CB_I2C_CH not acquired; continuing without it"
fi
fi
_SWB_READY=1
if [ "$MARGIN_SWB_PROBE" = "1" ]; then
local idx=0 chip addr bad=0
for chip in "${CHIPS[@]}"; do
IFS=':' read -r _ addr <<< "$chip"
if _swb_probe_addr "$addr"; then
echo -e "[\033[34mINFO\033[0m] probe ch=$CB_I2C_CH LTC2977[$idx] $addr : \033[32mACK\033[0m"
else
echo -e "[\033[33mWARN\033[0m] probe ch=$CB_I2C_CH LTC2977[$idx] $addr : \033[31mNACK\033[0m (wrong CB_I2C_CH / addr, board absent, or channel stuck -> margin_swb_scan / margin_swb_reset)"
bad=1
fi
idx=$((idx+1))
done
_log "swb: probe ch=$CB_I2C_CH chips=${CHIPS[*]} $( [ "$bad" = "0" ] && echo all-ACK || echo has-NACK )"
fi
return 0
}
# --- SWB debug / maintenance wrappers (thin shells over blanton_cb_i2c.sh) ---
_swb_guard() {
if [ "$TRANSPORT" != "swb" ]; then
echo -e "[\033[33mWARN\033[0m] current setting is TRANSPORT=$TRANSPORT (not swb); nothing to do"
return 1
fi
declare -F cb_pmbus_read >/dev/null 2>&1 || { echo -e "[\033[31mERRO\033[0m] SWB backend not loaded; run margin_init first"; return 1; }
return 0
}
# margin_swb_info -- show the transport wiring of the loaded setting
margin_swb_info() {
echo "-------------"
echo "[$PART_NUMBER] transport"
echo "-------------"
echo " TRANSPORT : $TRANSPORT"
if [ "$TRANSPORT" = "swb" ]; then
echo " CB_I2C_CH : $CB_I2C_CH (CB FPGA F3, base $(printf '0x%X' $(( 0x300 + CB_I2C_CH * 0x20 ))))"
echo " LTC2977 : ${CHIPS[*]} (bus field unused on swb)"
echo " prescale : $MARGIN_SWB_PRESCALE_LO/$MARGIN_SWB_PRESCALE_HI auto_init=$CB_I2C_AUTO_INIT sem_held=$_SWB_SEM_HELD"
echo " wire ops : cb_pmbus_write $CB_I2C_CH <addr> 0x00 <page> / cb_pmbus_read $CB_I2C_CH <addr> 0x8b 2"
else
echo " bus:addr : ${CHIPS[*]} (i2cset/i2cget)"
fi
}
# margin_swb_probe -- re-run the PAGE-read presence check on every LTC2977
margin_swb_probe() {
_swb_guard || return 1
local idx=0 chip addr
for chip in "${CHIPS[@]}"; do
IFS=':' read -r _ addr <<< "$chip"
if _swb_probe_addr "$addr"; then
echo -e " ch=$CB_I2C_CH $addr : \033[32mACK\033[0m"
else
echo -e " ch=$CB_I2C_CH $addr : \033[31mNACK\033[0m"
fi
idx=$((idx+1))
done
}
# margin_swb_scan [ch] -- cb_i2c_scan on this conf's channel (0x08..0x77)
margin_swb_scan() {
_swb_guard || return 1
_swb_call cb_i2c_scan "${1:-$CB_I2C_CH}"
}
# margin_swb_reset -- local I2C controller reset (0xD) + re-init, for a stuck bus
margin_swb_reset() {
_swb_guard || return 1
_swb_call cb_i2c_reset "$CB_I2C_CH"
_swb_call cb_i2c_init "$CB_I2C_CH" "$MARGIN_SWB_PRESCALE_LO" "$MARGIN_SWB_PRESCALE_HI"
echo -e "[\033[34mINFO\033[0m] ch=$CB_I2C_CH reset + re-init done"
_log "swb: cb_i2c_reset + cb_i2c_init ch=$CB_I2C_CH"
}
# margin_swb_sem [status|acquire|release]
margin_swb_sem() {
_swb_guard || return 1
local act="${1:-status}"
_swb_call cb_i2c_sem "$CB_I2C_CH" "$act"
case "$act" in
acquire) _SWB_SEM_HELD=1 ;;
release) _SWB_SEM_HELD=0 ;;
esac
}
# margin_swb_sem_release -- release the session semaphore if margin_init took it
margin_swb_sem_release() {
[ "$_SWB_SEM_HELD" = "1" ] || { echo -e "[\033[34mINFO\033[0m] no semaphore held by this session"; return 0; }
_swb_call cb_i2c_sem "$CB_I2C_CH" release
_SWB_SEM_HELD=0
}
# --- Init: load setting by name --- # --- Init: load setting by name ---
margin_init() { margin_init() {
if [ -z "$1" ]; then if [ -z "$1" ]; then
@@ -150,15 +422,13 @@ margin_init() {
TRANSPORT="${TRANSPORT:-i2c}" # a blank TRANSPORT="" in a .conf means i2c TRANSPORT="${TRANSPORT:-i2c}" # a blank TRANSPORT="" in a .conf means i2c
echo -e "[\033[34mINFO\033[0m] Loaded setting: $setting_file (transport=$TRANSPORT)" echo -e "[\033[34mINFO\033[0m] Loaded setting: $setting_file (transport=$TRANSPORT)"
# SWB rails need the cb_pmbus_* backend; source it on demand. # SWB rails need the cb_pmbus_* backend (blanton_cb_i2c.sh + blanton_fpga_pcimem.sh,
if [ "$TRANSPORT" = "swb" ] && ! declare -F cb_pmbus_read >/dev/null 2>&1; then # normally in the parent Blanton_Script/ dir); load it and bring the FPGA I2C
if [ -f "$SCRIPT_DIR/blanton_cb_i2c.sh" ]; then # channel up once. CB/i2c settings skip this entirely.
# shellcheck source=/dev/null _SWB_READY=0; _SWB_SEM_HELD=0
source "$SCRIPT_DIR/blanton_cb_i2c.sh" if [ "$TRANSPORT" = "swb" ]; then
else _swb_backend_load || return 1
echo -e "[\033[31mERRO\033[0m] TRANSPORT=swb but blanton_cb_i2c.sh not found in $SCRIPT_DIR" _swb_setup || return 1
return 1
fi
fi fi
_log_start_session _log_start_session
} }
@@ -221,8 +491,7 @@ _get_page() {
# PAGE is written the same way on both (PMBus command 0x00). # PAGE is written the same way on both (PMBus command 0x00).
_open_page() { _open_page() {
if [ "$TRANSPORT" = "swb" ]; then if [ "$TRANSPORT" = "swb" ]; then
[ "$DEBUG_MODE" = "1" ] && echo -e "\033[90m[DEBG] cb_pmbus_write $CB_I2C_CH $_ADDR 0x00 $1\033[0m" _swb_write 0x00 "$1"
cb_pmbus_write "$CB_I2C_CH" "$_ADDR" 0x00 "$1" >/dev/null 2>&1
return return
fi fi
[ "$DEBUG_MODE" = "1" ] && echo -e "\033[90m[DEBG] i2cset -y $_BUS $_ADDR 0x00 $1 b\033[0m" [ "$DEBUG_MODE" = "1" ] && echo -e "\033[90m[DEBG] i2cset -y $_BUS $_ADDR 0x00 $1 b\033[0m"
@@ -233,8 +502,7 @@ _open_page() {
_i2c_write_word() { _i2c_write_word() {
local reg=$1 lo=$2 hi=$3 local reg=$1 lo=$2 hi=$3
if [ "$TRANSPORT" = "swb" ]; then if [ "$TRANSPORT" = "swb" ]; then
[ "$DEBUG_MODE" = "1" ] && echo -e "\033[90m[DEBG] cb_pmbus_write $CB_I2C_CH $_ADDR $reg $lo $hi\033[0m" _swb_write "$reg" "$lo" "$hi"
cb_pmbus_write "$CB_I2C_CH" "$_ADDR" "$reg" "$lo" "$hi" >/dev/null 2>&1
return return
fi fi
local word=$(( (hi << 8) | lo )) local word=$(( (hi << 8) | lo ))
@@ -245,8 +513,7 @@ _i2c_write_word() {
_i2c_write_byte() { _i2c_write_byte() {
if [ "$TRANSPORT" = "swb" ]; then if [ "$TRANSPORT" = "swb" ]; then
[ "$DEBUG_MODE" = "1" ] && echo -e "\033[90m[DEBG] cb_pmbus_write $CB_I2C_CH $_ADDR $1 $2\033[0m" _swb_write "$1" "$2"
cb_pmbus_write "$CB_I2C_CH" "$_ADDR" "$1" "$2" >/dev/null 2>&1
return return
fi fi
[ "$DEBUG_MODE" = "1" ] && echo -e "\033[90m[DEBG] i2cset -y $_BUS $_ADDR $1 $2 b\033[0m" [ "$DEBUG_MODE" = "1" ] && echo -e "\033[90m[DEBG] i2cset -y $_BUS $_ADDR $1 $2 b\033[0m"
@@ -256,13 +523,7 @@ _i2c_write_byte() {
# Read a 16-bit word; echoes "0xXXXX". For swb, parse cb_pmbus_read's "=> 0xXXXX". # Read a 16-bit word; echoes "0xXXXX". For swb, parse cb_pmbus_read's "=> 0xXXXX".
_i2c_read_word() { _i2c_read_word() {
if [ "$TRANSPORT" = "swb" ]; then if [ "$TRANSPORT" = "swb" ]; then
[ "$DEBUG_MODE" = "1" ] && echo -e "\033[90m[DEBG] cb_pmbus_read $CB_I2C_CH $_ADDR $1 2\033[0m" >&2 _swb_read_word "$1"
local out word
out=$(cb_pmbus_read "$CB_I2C_CH" "$_ADDR" "$1" 2 2>/dev/null)
# cb_pmbus_read prints "... => 0xXXXX raw=[...] ..."; take the word after "=>".
word=$(printf '%s' "$out" | awk -F'=> ' 'NF>1{print $2}' | awk '{print $1}')
[[ "$word" =~ ^0[xX][0-9a-fA-F]+$ ]] || word="0x0000"
echo "$word"
return return
fi fi
[ "$DEBUG_MODE" = "1" ] && echo -e "\033[90m[DEBG] i2cget -y $_BUS $_ADDR $1 w\033[0m" >&2 [ "$DEBUG_MODE" = "1" ] && echo -e "\033[90m[DEBG] i2cget -y $_BUS $_ADDR $1 w\033[0m" >&2
@@ -9,7 +9,7 @@
# --- SWB transport (added): this LTC2980 sits behind the CB FPGA F3 I2C; reach # --- SWB transport (added): this LTC2980 sits behind the CB FPGA F3 I2C; reach
# it with cb_pmbus, not i2cset. CB_I2C_CH = FPGA I2C channel for this CONN. --- # it with cb_pmbus, not i2cset. CB_I2C_CH = FPGA I2C channel for this CONN. ---
TRANSPORT="swb" TRANSPORT="swb"
CB_I2C_CH=6 CB_I2C_CH=8 # CB F3 Ch8 = SWB0 CONN13/CONN15 (shared with CONN15, addr must differ)
# --- I2C Configuration --- # --- I2C Configuration ---
@@ -9,15 +9,15 @@
# --- SWB transport (added): this LTC2980 sits behind the CB FPGA F3 I2C; reach # --- SWB transport (added): this LTC2980 sits behind the CB FPGA F3 I2C; reach
# it with cb_pmbus, not i2cset. CB_I2C_CH = FPGA I2C channel for this CONN. --- # it with cb_pmbus, not i2cset. CB_I2C_CH = FPGA I2C channel for this CONN. ---
TRANSPORT="swb" TRANSPORT="swb"
CB_I2C_CH=8 CB_I2C_CH=6 # CB F3 Ch6 = SWB0 CONN14/CONN16 (shared with CONN16, addr must differ)
# --- I2C Configuration --- # --- I2C Configuration ---
# Format: "bus:address" (8 channels per entry / per LTC2977) # Format: "bus:address" (8 channels per entry / per LTC2977)
CHIPS=( CHIPS=(
# chip1: SWB0 (CONN14) # chip1: SWB0 (CONN14)
"0:0x5E" # CH0 ~ CH7 "0:0x5C" # CH0 ~ CH7
"0:0x5F" # CH8 ~ CH15 "0:0x5E" # CH8 ~ CH15
) )
# Pre-commands to run before accessing each chip (empty = none) # Pre-commands to run before accessing each chip (empty = none)
@@ -9,7 +9,7 @@
# --- SWB transport (added): this LTC2980 sits behind the CB FPGA F3 I2C; reach # --- SWB transport (added): this LTC2980 sits behind the CB FPGA F3 I2C; reach
# it with cb_pmbus, not i2cset. CB_I2C_CH = FPGA I2C channel for this CONN. --- # it with cb_pmbus, not i2cset. CB_I2C_CH = FPGA I2C channel for this CONN. ---
TRANSPORT="swb" TRANSPORT="swb"
CB_I2C_CH=6 CB_I2C_CH=8 # CB F3 Ch8 = SWB0 CONN13/CONN15 (shared with CONN13, addr must differ)
# --- I2C Configuration --- # --- I2C Configuration ---
@@ -17,7 +17,7 @@ CB_I2C_CH=6
CHIPS=( CHIPS=(
# chip1: SWB0 (CONN15) # chip1: SWB0 (CONN15)
"0:0x62" # CH0 ~ CH7 "0:0x62" # CH0 ~ CH7
"0:0x63" # CH8 ~ CH15 "0:0x64" # CH8 ~ CH15
) )
# Pre-commands to run before accessing each chip (empty = none) # Pre-commands to run before accessing each chip (empty = none)
@@ -9,15 +9,15 @@
# --- SWB transport (added): this LTC2980 sits behind the CB FPGA F3 I2C; reach # --- SWB transport (added): this LTC2980 sits behind the CB FPGA F3 I2C; reach
# it with cb_pmbus, not i2cset. CB_I2C_CH = FPGA I2C channel for this CONN. --- # it with cb_pmbus, not i2cset. CB_I2C_CH = FPGA I2C channel for this CONN. ---
TRANSPORT="swb" TRANSPORT="swb"
CB_I2C_CH=8 CB_I2C_CH=6 # CB F3 Ch6 = SWB0 CONN14/CONN16 (shared with CONN14, addr must differ)
# --- I2C Configuration --- # --- I2C Configuration ---
# Format: "bus:address" (8 channels per entry / per LTC2977) # Format: "bus:address" (8 channels per entry / per LTC2977)
CHIPS=( CHIPS=(
# chip1: SWB0 (CONN16) # chip1: SWB0 (CONN16)
"0:0x64" # CH0 ~ CH7 "0:0x62" # CH0 ~ CH7
"0:0x65" # CH8 ~ CH15 "0:0x64" # CH8 ~ CH15
) )
# Pre-commands to run before accessing each chip (empty = none) # Pre-commands to run before accessing each chip (empty = none)
@@ -9,7 +9,7 @@
# --- SWB transport (added): this LTC2980 sits behind the CB FPGA F3 I2C; reach # --- SWB transport (added): this LTC2980 sits behind the CB FPGA F3 I2C; reach
# it with cb_pmbus, not i2cset. CB_I2C_CH = FPGA I2C channel for this CONN. --- # it with cb_pmbus, not i2cset. CB_I2C_CH = FPGA I2C channel for this CONN. ---
TRANSPORT="swb" TRANSPORT="swb"
CB_I2C_CH=7 CB_I2C_CH=9 # CB F3 Ch9 = SWB1 CONN13/CONN15 (shared with CONN15, addr must differ)
# --- I2C Configuration --- # --- I2C Configuration ---
@@ -17,7 +17,7 @@ CB_I2C_CH=7
CHIPS=( CHIPS=(
# chip1: SWB1 (CONN13) # chip1: SWB1 (CONN13)
"1:0x5C" # CH0 ~ CH7 "1:0x5C" # CH0 ~ CH7
"1:0x5D" # CH8 ~ CH15 "1:0x5E" # CH8 ~ CH15
) )
# Pre-commands to run before accessing each chip (empty = none) # Pre-commands to run before accessing each chip (empty = none)
@@ -9,15 +9,15 @@
# --- SWB transport (added): this LTC2980 sits behind the CB FPGA F3 I2C; reach # --- SWB transport (added): this LTC2980 sits behind the CB FPGA F3 I2C; reach
# it with cb_pmbus, not i2cset. CB_I2C_CH = FPGA I2C channel for this CONN. --- # it with cb_pmbus, not i2cset. CB_I2C_CH = FPGA I2C channel for this CONN. ---
TRANSPORT="swb" TRANSPORT="swb"
CB_I2C_CH=9 CB_I2C_CH=7 # CB F3 Ch7 = SWB1 CONN14/CONN16 (shared with CONN16, addr must differ)
# --- I2C Configuration --- # --- I2C Configuration ---
# Format: "bus:address" (8 channels per entry / per LTC2977) # Format: "bus:address" (8 channels per entry / per LTC2977)
CHIPS=( CHIPS=(
# chip1: SWB1 (CONN14) # chip1: SWB1 (CONN14)
"1:0x5E" # CH0 ~ CH7 "1:0x5C" # CH0 ~ CH7
"1:0x5F" # CH8 ~ CH15 "1:0x5E" # CH8 ~ CH15
) )
# Pre-commands to run before accessing each chip (empty = none) # Pre-commands to run before accessing each chip (empty = none)
@@ -9,7 +9,7 @@
# --- SWB transport (added): this LTC2980 sits behind the CB FPGA F3 I2C; reach # --- SWB transport (added): this LTC2980 sits behind the CB FPGA F3 I2C; reach
# it with cb_pmbus, not i2cset. CB_I2C_CH = FPGA I2C channel for this CONN. --- # it with cb_pmbus, not i2cset. CB_I2C_CH = FPGA I2C channel for this CONN. ---
TRANSPORT="swb" TRANSPORT="swb"
CB_I2C_CH=7 CB_I2C_CH=9 # CB F3 Ch9 = SWB1 CONN13/CONN15 (shared with CONN13, addr must differ)
# --- I2C Configuration --- # --- I2C Configuration ---
@@ -17,7 +17,7 @@ CB_I2C_CH=7
CHIPS=( CHIPS=(
# chip1: SWB1 (CONN15) # chip1: SWB1 (CONN15)
"1:0x62" # CH0 ~ CH7 "1:0x62" # CH0 ~ CH7
"1:0x63" # CH8 ~ CH15 "1:0x64" # CH8 ~ CH15
) )
# Pre-commands to run before accessing each chip (empty = none) # Pre-commands to run before accessing each chip (empty = none)
@@ -9,15 +9,15 @@
# --- SWB transport (added): this LTC2980 sits behind the CB FPGA F3 I2C; reach # --- SWB transport (added): this LTC2980 sits behind the CB FPGA F3 I2C; reach
# it with cb_pmbus, not i2cset. CB_I2C_CH = FPGA I2C channel for this CONN. --- # it with cb_pmbus, not i2cset. CB_I2C_CH = FPGA I2C channel for this CONN. ---
TRANSPORT="swb" TRANSPORT="swb"
CB_I2C_CH=9 CB_I2C_CH=7 # CB F3 Ch7 = SWB1 CONN14/CONN16 (shared with CONN14, addr must differ)
# --- I2C Configuration --- # --- I2C Configuration ---
# Format: "bus:address" (8 channels per entry / per LTC2977) # Format: "bus:address" (8 channels per entry / per LTC2977)
CHIPS=( CHIPS=(
# chip1: SWB1 (CONN16) # chip1: SWB1 (CONN16)
"1:0x64" # CH0 ~ CH7 "1:0x62" # CH0 ~ CH7
"1:0x65" # CH8 ~ CH15 "1:0x64" # CH8 ~ CH15
) )
# Pre-commands to run before accessing each chip (empty = none) # Pre-commands to run before accessing each chip (empty = none)
@@ -40,6 +40,40 @@
# to the plain text before colouring so the columns stay # to the plain text before colouring so the columns stay
# aligned. Auto-disabled when stdout is not a terminal or # aligned. Auto-disabled when stdout is not a terminal or
# with --tsv; --color / --no-color / TL_COLOR force it. # with --tsv; --color / --no-color / TL_COLOR force it.
# V0.3.3 2026-08-17 Add: -tx / -length aliases for -c / -l so the tx burst can
# be written the way the bcm command reads
# ("start -u 0 -tx 100 -length 512"). Values are validated
# as positive integers; omitted = defaults 100 / 512.
# Fix: --dry-run printed nothing for init/start/stop/report
# since V0.3.1 (the new wrapper discarded the "[DRY ]"
# lines); the commands are listed again.
# V0.4.0 2026-08-17 Change: report now lists ALL 108 pairs, PASS / FAIL / NA
# alike, keeping one line per port (two lines per pair):
# Port TX RX PASS/FAIL
# cd0 4 4 PASS
# cd32 4 4 PASS
# cd204 - - NA
# cd236 - - NA
# Missing counters show '-'. -a is therefore the default
# (the flag is still accepted), and the long
# "# NA (no counters): ..." summary line is dropped because
# every NA pair is now a visible row. This is the only
# intentional difference from
# tools/bcm_mibpair_report_V1.1.0.py, which hides NA pairs
# unless -a is given.
# V0.4.1 2026-08-19 Fix: report printed every counter as 2147483647 (INT32_MAX).
# grp() rendered values with sprintf("%d", v); the DUT's awk
# casts to a 32-bit int for %d, so any counter above 2^31-1
# -- i.e. every real line-speed run -- saturated. Values are
# now formatted with %.0f, which stays in awk's double domain
# (exact to 2^53, far above any MIB counter).
# NOTE: the PASS/FAIL verdicts were NEVER affected -- the
# cross-check compares TX[]/RX[] doubles directly and only
# the display path went through grp(). Past reports that said
# PASS were right; only the numbers shown were wrong.
# Same fix applied to the "# FAIL ..." delta line (%+d ->
# %+.0f), which would otherwise saturate when a dead port
# makes the delta the size of the whole counter.
# ============================================================================= # =============================================================================
# #
# The VLAN/port table below is copied verbatim from # The VLAN/port table below is copied verbatim from
@@ -67,11 +101,14 @@
# Options (any order, after the subcommand): # Options (any order, after the subcommand):
# -u|--unit <0|1|all|0,1> bcmcmd unit(s). Default: $TL_UNITS ("0 1") # -u|--unit <0|1|all|0,1> bcmcmd unit(s). Default: $TL_UNITS ("0 1")
# -c|--count <n> tx packet count (default $TL_TX_COUNT) # -c|--count <n> tx packet count (default $TL_TX_COUNT)
# aliases: -tx | --tx
# -l|--length <n> tx packet length (default $TL_TX_LENGTH) # -l|--length <n> tx packet length (default $TL_TX_LENGTH)
# aliases: -length | --len
# -p|--ports <list|all> ps: port list ('all' = plain 'ps', every port) # -p|--ports <list|all> ps: port list ('all' = plain 'ps', every port)
# -T|--tolerance <n> report: allowed |delta| in packets (default 0; # -T|--tolerance <n> report: allowed |delta| in packets (default 0;
# -T 1 absorbs the normal snapshot skew) # -T 1 absorbs the normal snapshot skew)
# -a|--all report: also list pairs that have no counters # -a|--all report: accepted for compatibility -- every pair is
# listed by default, NA included
# -g|--grouped report: comma-group the values (1,234,567) # -g|--grouped report: comma-group the values (1,234,567)
# -P|--with-port report: render cd1(2) instead of cd1 # -P|--with-port report: render cd1(2) instead of cd1
# -q|--quiet report: drop the trailing summary comments # -q|--quiet report: drop the trailing summary comments
@@ -283,7 +320,8 @@ TL_BENIGN_REMOVE="${TL_BENIGN_REMOVE:-Entry not found|not found|does not exist|n
_tl_bcm_run() { _tl_bcm_run() {
local unit="$1" benign="$2"; shift 2 local unit="$1" benign="$2"; shift 2
local cmd="$*" out rc local cmd="$*" out rc
if [ "$TL_DRY_RUN" = "1" ]; then _tl_bcm "$unit" "$cmd" >/dev/null; return 0; fi # dry-run: let _tl_bcm print the "[DRY ] bcmcmd ..." line, run nothing
if [ "$TL_DRY_RUN" = "1" ]; then _tl_bcm "$unit" "$cmd"; return 0; fi
_tl_dbg "$BCMCMD_CMD -n $unit -c '$cmd'" _tl_dbg "$BCMCMD_CMD -n $unit -c '$cmd'"
out=$("$BCMCMD_CMD" -n "$unit" -c "$cmd" </dev/null 2>&1); rc=$? out=$("$BCMCMD_CMD" -n "$unit" -c "$cmd" </dev/null 2>&1); rc=$?
[ "$DEBUG_MODE" = "1" ] && [ -n "$out" ] && echo "$out" [ "$DEBUG_MODE" = "1" ] && [ -n "$out" ] && echo "$out"
@@ -319,6 +357,9 @@ _tl_units_check() {
_tl_pair_count() { echo "$TL_PAIRS" | grep -c . ; } _tl_pair_count() { echo "$TL_PAIRS" | grep -c . ; }
# positive integer? (tx count / length come straight from the command line)
_tl_is_uint() { case "$1" in ''|*[!0-9]*) return 1 ;; 0) return 1 ;; *) return 0 ;; esac; }
# === Step 1: VLAN loopback setup (traffic_loopback_vlan_setting.ttl) ========= # === Step 1: VLAN loopback setup (traffic_loopback_vlan_setting.ttl) =========
traffic_linespeed_init() { traffic_linespeed_init() {
_tl_units_check || return 1 _tl_units_check || return 1
@@ -369,6 +410,8 @@ traffic_linespeed_show() {
# === Step 4: start traffic (traffic_loopback_start.ttl) ===================== # === Step 4: start traffic (traffic_loopback_start.ttl) =====================
traffic_linespeed_start() { traffic_linespeed_start() {
_tl_units_check || return 1 _tl_units_check || return 1
_tl_is_uint "$TL_TX_COUNT" || { _tl_err "tx count '$TL_TX_COUNT' is not a positive integer (-tx|-c <n>)"; return 1; }
_tl_is_uint "$TL_TX_LENGTH" || { _tl_err "tx length '$TL_TX_LENGTH' is not a positive integer (-length|-l <n>)"; return 1; }
local unit vid pa pb n fail local unit vid pa pb n fail
for unit in $TL_UNITS; do for unit in $TL_UNITS; do
n=0; fail=0 n=0; fail=0
@@ -456,7 +499,7 @@ _tl_pair_report() {
-v TSV="$TL_REPORT_TSV" \ -v TSV="$TL_REPORT_TSV" \
-v COLOR="$color" ' -v COLOR="$color" '
function grp(v, s,out,l,i) { function grp(v, s,out,l,i) {
s = sprintf("%d", v) s = sprintf("%.0f", v)
if (!GROUPED) return s if (!GROUPED) return s
out = ""; l = length(s) out = ""; l = length(s)
for (i = 1; i <= l; i++) { for (i = 1; i <= l; i++) {
@@ -480,6 +523,12 @@ _tl_pair_report() {
NP = split(PAIRS, L, "\n") NP = split(PAIRS, L, "\n")
for (i = 1; i <= NP; i++) { split(L[i], f, " "); VID[i] = f[1]; A[i] = f[2]; B[i] = f[3] } for (i = 1; i <= NP; i++) { split(L[i], f, " "); VID[i] = f[1]; A[i] = f[2]; B[i] = f[3] }
MISSING = "-" MISSING = "-"
NC = 4
HDR[1] = "Port"; AL[1] = 0
HDR[2] = "TX"; AL[2] = 1
HDR[3] = "RX"; AL[3] = 1
HDR[4] = "PASS/FAIL"; AL[4] = 1
VCOL = NC # the verdict is always the last column
} }
{ {
idx = index($0, "MIB_") idx = index($0, "MIB_")
@@ -512,44 +561,52 @@ _tl_pair_report() {
oka = (a in HTX) && (a in HRX); okb = (b in HTX) && (b in HRX) oka = (a in HTX) && (a in HRX); okb = (b in HTX) && (b in HRX)
if (!(oka && okb)) { if (!(oka && okb)) {
v = "NA"; nna++ v = "NA"; nna++
miss = miss (miss == "" ? "" : " ") a "/" b # every pair is listed, NA included; absent counters show '-'
if (!ALL) continue ta = (a in HTX) ? grp(TX[a]) : MISSING; ra = (a in HRX) ? grp(RX[a]) : MISSING
ta = oka ? grp(TX[a]) : MISSING; ra = oka ? grp(RX[a]) : MISSING tb = (b in HTX) ? grp(TX[b]) : MISSING; rb = (b in HRX) ? grp(RX[b]) : MISSING
tb = okb ? grp(TX[b]) : MISSING; rb = okb ? grp(RX[b]) : MISSING
} else { } else {
dtx = TX[a] - RX[b]; drx = RX[a] - TX[b] dtx = TX[a] - RX[b]; drx = RX[a] - TX[b]
d = absd(dtx); if (absd(drx) > d) d = absd(drx) d = absd(dtx); if (absd(drx) > d) d = absd(drx)
if (d <= TOL + 0) { v = "PASS"; npass++ } if (d <= TOL + 0) { v = "PASS"; npass++ }
else { else {
v = "FAIL"; nfail++ v = "FAIL"; nfail++
FL[++nf] = sprintf("# FAIL %s/%s: %s.TX-%s.RX=%+d %s.RX-%s.TX=%+d", \ FL[++nf] = sprintf("# FAIL %s/%s: %s.TX-%s.RX=%+.0f %s.RX-%s.TX=%+.0f", \
a, b, a, b, dtx, a, b, drx) a, b, a, b, dtx, a, b, drx)
} }
ta = grp(TX[a]); ra = grp(RX[a]); tb = grp(TX[b]); rb = grp(RX[b]) ta = grp(TX[a]); ra = grp(RX[a]); tb = grp(TX[b]); rb = grp(RX[b])
} }
nrep++ nrep++
nrow++; C0[nrow] = pname(a); C1[nrow] = ta; C2[nrow] = ra; C3[nrow] = v nrow++; CELL[nrow,1] = pname(a); CELL[nrow,2] = ta; CELL[nrow,3] = ra; CELL[nrow,4] = v
nrow++; C0[nrow] = pname(b); C1[nrow] = tb; C2[nrow] = rb; C3[nrow] = v nrow++; CELL[nrow,1] = pname(b); CELL[nrow,2] = tb; CELL[nrow,3] = rb; CELL[nrow,4] = v
} }
H0 = "Port"; H1 = "TX"; H2 = "RX"; H3 = "PASS/FAIL"
if (TSV) { if (TSV) {
print H0 "\t" H1 "\t" H2 "\t" H3 line = HDR[1]
for (r = 1; r <= nrow; r++) print C0[r] "\t" C1[r] "\t" C2[r] "\t" C3[r] for (c = 2; c <= NC; c++) line = line "\t" HDR[c]
} else { print line
w0 = length(H0); w1 = length(H1); w2 = length(H2); w3 = length(H3)
for (r = 1; r <= nrow; r++) { for (r = 1; r <= nrow; r++) {
if (length(C0[r]) > w0) w0 = length(C0[r]) line = CELL[r, 1]
if (length(C1[r]) > w1) w1 = length(C1[r]) for (c = 2; c <= NC; c++) line = line "\t" CELL[r, c]
if (length(C2[r]) > w2) w2 = length(C2[r]) print line
if (length(C3[r]) > w3) w3 = length(C3[r])
} }
w0 += 2; w1 += 2; w2 += 2; w3 += 2 } else {
printf "%-*s%*s%*s%*s\n", w0, H0, w1, H1, w2, H2, w3, H3 for (c = 1; c <= NC; c++) {
w[c] = length(HDR[c])
for (r = 1; r <= nrow; r++) if (length(CELL[r, c]) > w[c]) w[c] = length(CELL[r, c])
w[c] += 2
}
for (c = 1; c <= NC; c++) printf(AL[c] ? "%*s" : "%-*s", w[c], HDR[c])
printf "\n"
for (r = 1; r <= nrow; r++) { for (r = 1; r <= nrow; r++) {
# pad on the plain text, then colour, so the columns stay aligned for (c = 1; c <= NC; c++) {
printf "%-*s%*s%*s", w0, C0[r], w1, C1[r], w2, C2[r] if (c == VCOL) {
printf "%*s%s\n", w3 - length(C3[r]), "", vc(C3[r]) # pad on the plain text, then colour, so columns stay aligned
printf "%*s%s", w[c] - length(CELL[r, c]), "", vc(CELL[r, c])
} else {
printf(AL[c] ? "%*s" : "%-*s", w[c], CELL[r, c])
}
}
printf "\n"
} }
} }
@@ -559,7 +616,7 @@ _tl_pair_report() {
printf "# pairs reported : %d\n", nrep printf "# pairs reported : %d\n", nrep
printf "# %s %d %s %d %s %d (tolerance %d)\n", \ printf "# %s %d %s %d %s %d (tolerance %d)\n", \
vc("PASS"), npass, vc("FAIL"), nfail, vc("NA"), nna, TOL + 0 vc("PASS"), npass, vc("FAIL"), nfail, vc("NA"), nna, TOL + 0
if (miss != "") printf "# %s (no counters): %s\n", vc("NA"), miss # every NA pair is a visible row -> no long "no counters" list needed
for (i = 1; i <= nf; i++) print redl(FL[i]) for (i = 1; i <= nf; i++) print redl(FL[i])
} }
exit (nfail || nna) ? 2 : 0 exit (nfail || nna) ? 2 : 0
@@ -572,13 +629,14 @@ traffic_linespeed_report() {
# -f <log>: offline mode, no DUT access, no unit loop # -f <log>: offline mode, no DUT access, no unit loop
if [ -n "$TL_REPORT_FILE" ]; then if [ -n "$TL_REPORT_FILE" ]; then
[ -r "$TL_REPORT_FILE" ] || { _tl_err "cannot read log '$TL_REPORT_FILE'"; return 1; } [ -r "$TL_REPORT_FILE" ] || { _tl_err "cannot read log '$TL_REPORT_FILE'"; return 1; }
_tl_info "report from log: $TL_REPORT_FILE (tolerance $TL_TOLERANCE)" # note on stderr so the table on stdout stays pipe/diff clean
_tl_info "report from log: $TL_REPORT_FILE (tolerance $TL_TOLERANCE)" >&2
_tl_pair_report "$TL_REPORT_FILE"; return $? _tl_pair_report "$TL_REPORT_FILE"; return $?
fi fi
_tl_units_check || return 1 _tl_units_check || return 1
if [ "$TL_DRY_RUN" = "1" ]; then if [ "$TL_DRY_RUN" = "1" ]; then
for unit in $TL_UNITS; do _tl_bcm "$unit" "show c" >/dev/null; done for unit in $TL_UNITS; do _tl_bcm "$unit" "show c"; done
return 0 return 0
fi fi
@@ -627,17 +685,17 @@ blanton_traffic_linespeed_help() {
echo -e " \033[33mstart\033[0m step 4 tx <count> length=<len> VLantag=<vid> per VLAN" echo -e " \033[33mstart\033[0m step 4 tx <count> length=<len> VLantag=<vid> per VLAN"
echo -e " \033[33mstop\033[0m step 5 vlan remove <vid> pbm=<pair> [--destroy also destroys]" echo -e " \033[33mstop\033[0m step 5 vlan remove <vid> pbm=<pair> [--destroy also destroys]"
echo -e " \033[33mps\033[0m ps <portlist> port status (link/speed) of the cabled ports" echo -e " \033[33mps\033[0m ps <portlist> port status (link/speed) of the cabled ports"
echo -e " \033[33mreport\033[0m show c -> per-pair TX/RX table + PASS/FAIL cross-check" echo -e " \033[33mreport\033[0m show c -> per-port TX/RX per pair + PASS/FAIL/NA cross-check"
echo -e " \033[33mrun\033[0m ps -> init -> clear -> start -> report" echo -e " \033[33mrun\033[0m ps -> init -> clear -> start -> report"
echo -e " \033[33mhelp\033[0m" echo -e " \033[33mhelp\033[0m"
echo "" echo ""
echo -e "\033[1mOptions\033[0m" echo -e "\033[1mOptions\033[0m"
echo -e " \033[33m-u|--unit\033[0m <0|1|all> bcmcmd unit(s); 'all' = 0 1 (default: $TL_UNITS)" echo -e " \033[33m-u|--unit\033[0m <0|1|all> bcmcmd unit(s); 'all' = 0 1 (default: $TL_UNITS)"
echo -e " \033[33m-c|--count\033[0m <n> tx packet count (default $TL_TX_COUNT)" echo -e " \033[33m-c|--count|-tx\033[0m <n> tx packet count (default $TL_TX_COUNT)"
echo -e " \033[33m-l|--length\033[0m <n> tx packet length (default $TL_TX_LENGTH)" echo -e " \033[33m-l|--length|-length\033[0m <n> tx packet length (default $TL_TX_LENGTH)"
echo -e " \033[33m-p|--ports\033[0m <list> ps port list, or 'all' for every port" echo -e " \033[33m-p|--ports\033[0m <list> ps port list, or 'all' for every port"
echo -e " \033[33m-T|--tolerance\033[0m <n> report: allowed |delta| pkts (default $TL_TOLERANCE; -T 1 = snapshot skew)" echo -e " \033[33m-T|--tolerance\033[0m <n> report: allowed |delta| pkts (default $TL_TOLERANCE; -T 1 = snapshot skew)"
echo -e " \033[33m-a|--all\033[0m report: also list pairs with no counters" echo -e " \033[33m-a|--all\033[0m report: kept for compatibility (all pairs are listed)"
echo -e " \033[33m-g|--grouped\033[0m report: comma-group values" echo -e " \033[33m-g|--grouped\033[0m report: comma-group values"
echo -e " \033[33m-P|--with-port\033[0m report: render cd1(2) instead of cd1" echo -e " \033[33m-P|--with-port\033[0m report: render cd1(2) instead of cd1"
echo -e " \033[33m-q|--quiet\033[0m report: no trailing summary" echo -e " \033[33m-q|--quiet\033[0m report: no trailing summary"
@@ -658,7 +716,8 @@ blanton_traffic_linespeed_help() {
echo -e " blanton_traffic_linespeed report -u 1 -q | grep -v PASS" echo -e " blanton_traffic_linespeed report -u 1 -q | grep -v PASS"
echo -e " blanton_traffic_linespeed report -f /tmp/showc_unit1_*.log -a" echo -e " blanton_traffic_linespeed report -f /tmp/showc_unit1_*.log -a"
echo -e " blanton_traffic_linespeed show -u 1 | grep -iE 'error|discard|drop'" echo -e " blanton_traffic_linespeed show -u 1 | grep -iE 'error|discard|drop'"
echo -e " blanton_traffic_linespeed start -c 1000 -l 9216 -d" echo -e " blanton_traffic_linespeed start -u 0 -tx 100 -length 512"
echo -e " blanton_traffic_linespeed start -tx 100000 -length 9216 -d"
echo -e " blanton_traffic_linespeed stop --destroy" echo -e " blanton_traffic_linespeed stop --destroy"
echo "" echo ""
echo -e " report exit code: 0 = all pairs PASS, 2 = any FAIL/NA, 1 = parse error" echo -e " report exit code: 0 = all pairs PASS, 2 = any FAIL/NA, 1 = parse error"
@@ -679,8 +738,8 @@ blanton_traffic_linespeed() {
*) TL_UNITS="$2" ;; *) TL_UNITS="$2" ;;
esac esac
shift 2 ;; shift 2 ;;
-c|--count) TL_TX_COUNT="$2"; shift 2 ;; -c|--count|-tx|--tx) TL_TX_COUNT="$2"; shift 2 ;;
-l|--length) TL_TX_LENGTH="$2"; shift 2 ;; -l|-length|--length|--len) TL_TX_LENGTH="$2"; shift 2 ;;
-p|--ports) TL_PS_PORTS="$2"; shift 2 ;; -p|--ports) TL_PS_PORTS="$2"; shift 2 ;;
-T|--tolerance) TL_TOLERANCE="$2"; shift 2 ;; -T|--tolerance) TL_TOLERANCE="$2"; shift 2 ;;
-f|--file) TL_REPORT_FILE="$2"; shift 2 ;; -f|--file) TL_REPORT_FILE="$2"; shift 2 ;;
@@ -0,0 +1,340 @@
#!/bin/bash
###############################################################################
# bmc_monitor.sh
#
# Version : V1.1.0
# Author : ETWen
# Date : 20260820
#
# Purpose : Periodically execute a list of commands on the BMC through
# `bmc-manager run "<cmd>"` from the SONiC host side, and append all
# output to a rotating log file. Designed to run detached in the
# background so no interactive SSH session to the BMC is required.
#
# Notes : - Everything runs on the HOST. Nothing is deployed to the BMC, so
# the BMC busybox toolchain (no base64 / no setsid ...) is a
# non-issue.
# - `bmc-manager run` prints the remote command output on stdout and
# its own banner on stderr; the two streams are captured separately
# so the log stays clean.
#
# Version History
# V1.0.0 20260820 Initial Version
# V1.1.0 20260820 `start` clears the previous log by default
# (START_LOG_MODE: new / archive / append)
###############################################################################
set -u
###############################################################################
# User Configurable Section -- normally this is the only part you need to edit
###############################################################################
# Commands executed on the BMC, one per line, in this order.
# Add a line to add a command; comment it out to disable it.
COMMANDS=(
"free -m"
#"uptime"
#"cat /proc/loadavg"
#"cat /proc/meminfo"
#"df -h /tmp"
#"dmesg | tail -n 30"
)
# Seconds to wait after a full round (all COMMANDS executed) before the next one
INTERVAL_SEC=10
# Seconds to wait between two commands inside the same round (0 = no wait)
INTER_CMD_DELAY_SEC=0
# Per-command timeout in seconds; prevents a hung bmc-manager from freezing the
# whole loop. Set to 0 to disable.
CMD_TIMEOUT_SEC=30
# Stop after this many rounds. 0 = run until stopped manually.
MAX_ROUNDS=0
# Command runner. Final invocation is:
# "$RUNNER_BIN" "${RUNNER_ARGS[@]}" "<command string>"
# BMC side : RUNNER_BIN="bmc-manager" ; RUNNER_ARGS=(run)
# BMC by IP : RUNNER_BIN="bmc-manager" ; RUNNER_ARGS=(--ip 192.168.200.200 run)
# Host side : RUNNER_BIN="bash" ; RUNNER_ARGS=(-c)
RUNNER_BIN="bmc-manager"
RUNNER_ARGS=(run)
# stderr handling of the runner (bmc-manager banner lives here)
# 0 = log stderr only when the command fails (recommended, keeps log clean)
# 1 = always log stderr
LOG_STDERR=0
# Logging
LOG_DIR="" # empty = <script directory>/log
LOG_NAME="bmc_poll.log"
MAX_LOG_SIZE_MB=100 # rotate above this size; 0 = never rotate
MAX_LOG_KEEP=5 # keep .1 .. .N
# What `start` does with the log left behind by the previous run:
# new = begin with an empty log; previous content and rotated copies are
# discarded (default -- one run, one log)
# archive = rename the previous log to <log>.YYYYmmdd-HHMMSS, then begin empty
# append = keep appending to the existing log
START_LOG_MODE="new"
###############################################################################
# Internal
###############################################################################
SCRIPT_NAME="$(basename -- "${BASH_SOURCE[0]}")"
SCRIPT_PATH="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)/${SCRIPT_NAME}"
[ -n "${LOG_DIR}" ] || LOG_DIR="$(dirname -- "${SCRIPT_PATH}")/log"
LOG_FILE="${LOG_DIR}/${LOG_NAME}"
PID_FILE="${LOG_DIR}/${LOG_NAME%.log}.pid"
LOG_TO_FILE=0
STOP=0
SLEEP_PID=""
ts() { date '+%Y-%m-%d %H:%M:%S'; }
die() { printf '[ERROR] %s\n' "$*" >&2; exit 1; }
on_signal() {
STOP=1
[ -n "${SLEEP_PID}" ] && kill "${SLEEP_PID}" 2>/dev/null
}
# sleep that can be aborted immediately by SIGTERM/SIGINT
interruptible_sleep() {
local sec="$1"
[ "${sec}" -gt 0 ] 2>/dev/null || return 0
sleep "${sec}" &
SLEEP_PID=$!
wait "${SLEEP_PID}" 2>/dev/null
SLEEP_PID=""
}
# echo the pid and return 0 when a live instance is found
is_running() {
local pid
[ -f "${PID_FILE}" ] || return 1
pid="$(cat "${PID_FILE}" 2>/dev/null)"
[[ "${pid}" =~ ^[0-9]+$ ]] || return 1
kill -0 "${pid}" 2>/dev/null || return 1
grep -qa "${SCRIPT_NAME}" "/proc/${pid}/cmdline" 2>/dev/null || return 1
printf '%s' "${pid}"
return 0
}
rotate_log_if_needed() {
local max_bytes size i
[ "${LOG_TO_FILE}" -eq 1 ] || return 0
[ "${MAX_LOG_SIZE_MB}" -gt 0 ] || return 0
[ -f "${LOG_FILE}" ] || return 0
max_bytes=$(( MAX_LOG_SIZE_MB * 1024 * 1024 ))
size="$(stat -c %s "${LOG_FILE}" 2>/dev/null || echo 0)"
[ "${size}" -ge "${max_bytes}" ] || return 0
for (( i = MAX_LOG_KEEP - 1; i >= 1; i-- )); do
[ -f "${LOG_FILE}.${i}" ] && mv -f "${LOG_FILE}.${i}" "${LOG_FILE}.$(( i + 1 ))"
done
mv -f "${LOG_FILE}" "${LOG_FILE}.1"
exec >>"${LOG_FILE}" 2>&1 # re-bind stdout/stderr to the new file
printf '[%s] INFO : log rotated at %s MB\n' "$(ts)" "${MAX_LOG_SIZE_MB}"
}
# Remove the rotated copies (.1 .. .N) only. Timestamped archives are kept.
remove_rotated_logs() {
local i
for (( i = 1; i <= MAX_LOG_KEEP + 1; i++ )); do
rm -f "${LOG_FILE}.${i}"
done
}
# Apply START_LOG_MODE just before a background run is launched.
prepare_log_on_start() {
local stamp
case "${START_LOG_MODE}" in
append)
return 0
;;
archive)
if [ -s "${LOG_FILE}" ]; then
stamp="$(date '+%Y%m%d-%H%M%S')"
mv -f "${LOG_FILE}" "${LOG_FILE}.${stamp}" \
|| die "cannot archive ${LOG_FILE}"
printf 'previous log archived: %s.%s\n' "${LOG_FILE}" "${stamp}"
fi
remove_rotated_logs
;;
new)
# Truncate rather than unlink: the inode is preserved, so a `tail -f`
# that is already attached keeps following the new run.
if [ -f "${LOG_FILE}" ]; then
: > "${LOG_FILE}" || die "cannot truncate ${LOG_FILE}"
fi
remove_rotated_logs
;;
*)
die "invalid START_LOG_MODE: ${START_LOG_MODE}"
;;
esac
}
run_one_command() {
local cmd="$1"
local out err rc t0 elapsed err_file
err_file="$(mktemp)"
t0=${SECONDS}
if [ "${CMD_TIMEOUT_SEC}" -gt 0 ]; then
out="$(timeout "${CMD_TIMEOUT_SEC}" \
"${RUNNER_BIN}" "${RUNNER_ARGS[@]}" "${cmd}" 2>"${err_file}")"
else
out="$("${RUNNER_BIN}" "${RUNNER_ARGS[@]}" "${cmd}" 2>"${err_file}")"
fi
rc=$?
elapsed=$(( SECONDS - t0 ))
err="$(cat "${err_file}" 2>/dev/null)"
rm -f "${err_file}"
printf '===== [%s] CMD: %s | rc=%d | %ds =====\n' "$(ts)" "${cmd}" "${rc}" "${elapsed}"
printf '%s\n' "${out}"
if [ "${rc}" -eq 124 ]; then
printf '[%s] WARN : timeout after %ds\n' "$(ts)" "${CMD_TIMEOUT_SEC}"
fi
if [ -n "${err}" ] && { [ "${LOG_STDERR}" -eq 1 ] || [ "${rc}" -ne 0 ]; }; then
printf -- '--- stderr ---\n%s\n' "${err}"
fi
printf '\n'
}
main_loop() {
local round=0 cmd
[ "${#COMMANDS[@]}" -gt 0 ] || die "COMMANDS is empty"
command -v "${RUNNER_BIN}" >/dev/null 2>&1 || die "runner not found: ${RUNNER_BIN}"
trap on_signal INT TERM
printf '#############################################################\n'
printf '[%s] START: pid=%d interval=%ds timeout=%ds cmds=%d\n' \
"$(ts)" "$$" "${INTERVAL_SEC}" "${CMD_TIMEOUT_SEC}" "${#COMMANDS[@]}"
printf '#############################################################\n'
while [ "${STOP}" -eq 0 ]; do
round=$(( round + 1 ))
printf -- '---------- ROUND %d @ %s ----------\n' "${round}" "$(ts)"
for cmd in "${COMMANDS[@]}"; do
[ "${STOP}" -eq 0 ] || break
run_one_command "${cmd}"
[ "${INTER_CMD_DELAY_SEC}" -gt 0 ] && interruptible_sleep "${INTER_CMD_DELAY_SEC}"
done
rotate_log_if_needed
if [ "${MAX_ROUNDS}" -gt 0 ] && [ "${round}" -ge "${MAX_ROUNDS}" ]; then
break
fi
# Fixed gap between rounds. For a fixed cadence instead, replace with:
# interruptible_sleep $(( INTERVAL_SEC - round_elapsed ))
[ "${STOP}" -eq 0 ] && interruptible_sleep "${INTERVAL_SEC}"
done
printf '[%s] STOP : pid=%d after %d round(s)\n' "$(ts)" "$$" "${round}"
}
###############################################################################
# Sub-commands
###############################################################################
do_start() {
local pid
pid="$(is_running)" && die "already running (pid=${pid})"
mkdir -p "${LOG_DIR}" || die "cannot create ${LOG_DIR}"
prepare_log_on_start
if command -v setsid >/dev/null 2>&1; then
setsid "${SCRIPT_PATH}" __daemon >/dev/null 2>&1 &
else
nohup "${SCRIPT_PATH}" __daemon >/dev/null 2>&1 &
disown 2>/dev/null
fi
sleep 1
pid="$(is_running)" || die "start failed, check ${LOG_FILE}"
printf 'started (pid=%s)\nlog: %s\n' "${pid}" "${LOG_FILE}"
}
do_daemon() {
mkdir -p "${LOG_DIR}" || die "cannot create ${LOG_DIR}"
LOG_TO_FILE=1
exec >>"${LOG_FILE}" 2>&1
printf '%d\n' "$$" > "${PID_FILE}"
trap 'rm -f "${PID_FILE}"' EXIT
main_loop
}
do_stop() {
local pid i
pid="$(is_running)" || { printf 'not running\n'; rm -f "${PID_FILE}"; return 0; }
kill -TERM -"${pid}" 2>/dev/null || kill -TERM "${pid}" 2>/dev/null
for (( i = 0; i < 20; i++ )); do
kill -0 "${pid}" 2>/dev/null || break
sleep 0.5
done
if kill -0 "${pid}" 2>/dev/null; then
printf 'SIGTERM ignored, sending SIGKILL\n'
kill -KILL -"${pid}" 2>/dev/null || kill -KILL "${pid}" 2>/dev/null
fi
rm -f "${PID_FILE}"
printf 'stopped (pid=%s)\n' "${pid}"
}
do_status() {
local pid
if pid="$(is_running)"; then
printf 'status : running (pid=%s)\n' "${pid}"
else
printf 'status : stopped\n'
fi
printf 'log : %s\n' "${LOG_FILE}"
[ -f "${LOG_FILE}" ] && printf 'size : %s\n' "$(du -h "${LOG_FILE}" | cut -f1)"
printf 'cmds : %d, interval=%ds\n' "${#COMMANDS[@]}" "${INTERVAL_SEC}"
}
usage() {
cat <<EOF
Usage: ${SCRIPT_NAME} {start|stop|status|fg|tail|clear}
start Run detached in the background; the previous log is discarded first
(see START_LOG_MODE)
stop Stop the background instance
status Show pid / log path / log size
fg Run in the foreground, output to the terminal (for a quick check)
tail tail -f the log file
clear Remove the log file and all rotated copies (must be stopped first)
Log file : ${LOG_FILE}
To add a command, edit the COMMANDS array at the top of this script.
EOF
}
###############################################################################
# Entry point
###############################################################################
case "${1:-}" in
start) do_start ;;
stop) do_stop ;;
status) do_status ;;
fg) LOG_TO_FILE=0; main_loop ;;
tail) tail -n 50 -f "${LOG_FILE}" ;;
clear)
is_running >/dev/null && die "still running, stop it first"
rm -f "${LOG_FILE}" "${LOG_FILE}".[0-9]*
printf 'log cleared\n'
;;
__daemon) do_daemon ;;
-h|--help|"") usage ;;
*) printf '[ERROR] unknown sub-command: %s\n\n' "$1" >&2; usage; exit 1 ;;
esac
@@ -0,0 +1,340 @@
#!/bin/bash
###############################################################################
# bmc_monitor_ddr.sh
#
# Version : V1.1.0
# Author : ETWen
# Date : 20260820
#
# Purpose : Periodically execute a list of commands on the BMC through
# `bmc-manager run "<cmd>"` from the SONiC host side, and append all
# output to a rotating log file. Designed to run detached in the
# background so no interactive SSH session to the BMC is required.
#
# Notes : - Everything runs on the HOST. Nothing is deployed to the BMC, so
# the BMC busybox toolchain (no base64 / no setsid ...) is a
# non-issue.
# - `bmc-manager run` prints the remote command output on stdout and
# its own banner on stderr; the two streams are captured separately
# so the log stays clean.
#
# Version History
# V1.0.0 20260820 Initial Version
# V1.1.0 20260820 `start` clears the previous log by default
# (START_LOG_MODE: new / archive / append)
###############################################################################
set -u
###############################################################################
# User Configurable Section -- normally this is the only part you need to edit
###############################################################################
# Commands executed on the BMC, one per line, in this order.
# Add a line to add a command; comment it out to disable it.
COMMANDS=(
"memtester 500M 1"
#"uptime"
#"cat /proc/loadavg"
#"cat /proc/meminfo"
#"df -h /tmp"
#"dmesg | tail -n 30"
)
# Seconds to wait after a full round (all COMMANDS executed) before the next one
INTERVAL_SEC=5
# Seconds to wait between two commands inside the same round (0 = no wait)
INTER_CMD_DELAY_SEC=0
# Per-command timeout in seconds; prevents a hung bmc-manager from freezing the
# whole loop. Set to 0 to disable.
CMD_TIMEOUT_SEC=3600
# Stop after this many rounds. 0 = run until stopped manually.
MAX_ROUNDS=0
# Command runner. Final invocation is:
# "$RUNNER_BIN" "${RUNNER_ARGS[@]}" "<command string>"
# BMC side : RUNNER_BIN="bmc-manager" ; RUNNER_ARGS=(run)
# BMC by IP : RUNNER_BIN="bmc-manager" ; RUNNER_ARGS=(--ip 192.168.200.200 run)
# Host side : RUNNER_BIN="bash" ; RUNNER_ARGS=(-c)
RUNNER_BIN="bmc-manager"
RUNNER_ARGS=(run)
# stderr handling of the runner (bmc-manager banner lives here)
# 0 = log stderr only when the command fails (recommended, keeps log clean)
# 1 = always log stderr
LOG_STDERR=0
# Logging
LOG_DIR="" # empty = <script directory>/log
LOG_NAME="bmc_ddr.log"
MAX_LOG_SIZE_MB=100 # rotate above this size; 0 = never rotate
MAX_LOG_KEEP=5 # keep .1 .. .N
# What `start` does with the log left behind by the previous run:
# new = begin with an empty log; previous content and rotated copies are
# discarded (default -- one run, one log)
# archive = rename the previous log to <log>.YYYYmmdd-HHMMSS, then begin empty
# append = keep appending to the existing log
START_LOG_MODE="new"
###############################################################################
# Internal
###############################################################################
SCRIPT_NAME="$(basename -- "${BASH_SOURCE[0]}")"
SCRIPT_PATH="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)/${SCRIPT_NAME}"
[ -n "${LOG_DIR}" ] || LOG_DIR="$(dirname -- "${SCRIPT_PATH}")/log"
LOG_FILE="${LOG_DIR}/${LOG_NAME}"
PID_FILE="${LOG_DIR}/${LOG_NAME%.log}.pid"
LOG_TO_FILE=0
STOP=0
SLEEP_PID=""
ts() { date '+%Y-%m-%d %H:%M:%S'; }
die() { printf '[ERROR] %s\n' "$*" >&2; exit 1; }
on_signal() {
STOP=1
[ -n "${SLEEP_PID}" ] && kill "${SLEEP_PID}" 2>/dev/null
}
# sleep that can be aborted immediately by SIGTERM/SIGINT
interruptible_sleep() {
local sec="$1"
[ "${sec}" -gt 0 ] 2>/dev/null || return 0
sleep "${sec}" &
SLEEP_PID=$!
wait "${SLEEP_PID}" 2>/dev/null
SLEEP_PID=""
}
# echo the pid and return 0 when a live instance is found
is_running() {
local pid
[ -f "${PID_FILE}" ] || return 1
pid="$(cat "${PID_FILE}" 2>/dev/null)"
[[ "${pid}" =~ ^[0-9]+$ ]] || return 1
kill -0 "${pid}" 2>/dev/null || return 1
grep -qa "${SCRIPT_NAME}" "/proc/${pid}/cmdline" 2>/dev/null || return 1
printf '%s' "${pid}"
return 0
}
rotate_log_if_needed() {
local max_bytes size i
[ "${LOG_TO_FILE}" -eq 1 ] || return 0
[ "${MAX_LOG_SIZE_MB}" -gt 0 ] || return 0
[ -f "${LOG_FILE}" ] || return 0
max_bytes=$(( MAX_LOG_SIZE_MB * 1024 * 1024 ))
size="$(stat -c %s "${LOG_FILE}" 2>/dev/null || echo 0)"
[ "${size}" -ge "${max_bytes}" ] || return 0
for (( i = MAX_LOG_KEEP - 1; i >= 1; i-- )); do
[ -f "${LOG_FILE}.${i}" ] && mv -f "${LOG_FILE}.${i}" "${LOG_FILE}.$(( i + 1 ))"
done
mv -f "${LOG_FILE}" "${LOG_FILE}.1"
exec >>"${LOG_FILE}" 2>&1 # re-bind stdout/stderr to the new file
printf '[%s] INFO : log rotated at %s MB\n' "$(ts)" "${MAX_LOG_SIZE_MB}"
}
# Remove the rotated copies (.1 .. .N) only. Timestamped archives are kept.
remove_rotated_logs() {
local i
for (( i = 1; i <= MAX_LOG_KEEP + 1; i++ )); do
rm -f "${LOG_FILE}.${i}"
done
}
# Apply START_LOG_MODE just before a background run is launched.
prepare_log_on_start() {
local stamp
case "${START_LOG_MODE}" in
append)
return 0
;;
archive)
if [ -s "${LOG_FILE}" ]; then
stamp="$(date '+%Y%m%d-%H%M%S')"
mv -f "${LOG_FILE}" "${LOG_FILE}.${stamp}" \
|| die "cannot archive ${LOG_FILE}"
printf 'previous log archived: %s.%s\n' "${LOG_FILE}" "${stamp}"
fi
remove_rotated_logs
;;
new)
# Truncate rather than unlink: the inode is preserved, so a `tail -f`
# that is already attached keeps following the new run.
if [ -f "${LOG_FILE}" ]; then
: > "${LOG_FILE}" || die "cannot truncate ${LOG_FILE}"
fi
remove_rotated_logs
;;
*)
die "invalid START_LOG_MODE: ${START_LOG_MODE}"
;;
esac
}
run_one_command() {
local cmd="$1"
local out err rc t0 elapsed err_file
err_file="$(mktemp)"
t0=${SECONDS}
if [ "${CMD_TIMEOUT_SEC}" -gt 0 ]; then
out="$(timeout "${CMD_TIMEOUT_SEC}" \
"${RUNNER_BIN}" "${RUNNER_ARGS[@]}" "${cmd}" 2>"${err_file}")"
else
out="$("${RUNNER_BIN}" "${RUNNER_ARGS[@]}" "${cmd}" 2>"${err_file}")"
fi
rc=$?
elapsed=$(( SECONDS - t0 ))
err="$(cat "${err_file}" 2>/dev/null)"
rm -f "${err_file}"
printf '===== [%s] CMD: %s | rc=%d | %ds =====\n' "$(ts)" "${cmd}" "${rc}" "${elapsed}"
printf '%s\n' "${out}"
if [ "${rc}" -eq 124 ]; then
printf '[%s] WARN : timeout after %ds\n' "$(ts)" "${CMD_TIMEOUT_SEC}"
fi
if [ -n "${err}" ] && { [ "${LOG_STDERR}" -eq 1 ] || [ "${rc}" -ne 0 ]; }; then
printf -- '--- stderr ---\n%s\n' "${err}"
fi
printf '\n'
}
main_loop() {
local round=0 cmd
[ "${#COMMANDS[@]}" -gt 0 ] || die "COMMANDS is empty"
command -v "${RUNNER_BIN}" >/dev/null 2>&1 || die "runner not found: ${RUNNER_BIN}"
trap on_signal INT TERM
printf '#############################################################\n'
printf '[%s] START: pid=%d interval=%ds timeout=%ds cmds=%d\n' \
"$(ts)" "$$" "${INTERVAL_SEC}" "${CMD_TIMEOUT_SEC}" "${#COMMANDS[@]}"
printf '#############################################################\n'
while [ "${STOP}" -eq 0 ]; do
round=$(( round + 1 ))
printf -- '---------- ROUND %d @ %s ----------\n' "${round}" "$(ts)"
for cmd in "${COMMANDS[@]}"; do
[ "${STOP}" -eq 0 ] || break
run_one_command "${cmd}"
[ "${INTER_CMD_DELAY_SEC}" -gt 0 ] && interruptible_sleep "${INTER_CMD_DELAY_SEC}"
done
rotate_log_if_needed
if [ "${MAX_ROUNDS}" -gt 0 ] && [ "${round}" -ge "${MAX_ROUNDS}" ]; then
break
fi
# Fixed gap between rounds. For a fixed cadence instead, replace with:
# interruptible_sleep $(( INTERVAL_SEC - round_elapsed ))
[ "${STOP}" -eq 0 ] && interruptible_sleep "${INTERVAL_SEC}"
done
printf '[%s] STOP : pid=%d after %d round(s)\n' "$(ts)" "$$" "${round}"
}
###############################################################################
# Sub-commands
###############################################################################
do_start() {
local pid
pid="$(is_running)" && die "already running (pid=${pid})"
mkdir -p "${LOG_DIR}" || die "cannot create ${LOG_DIR}"
prepare_log_on_start
if command -v setsid >/dev/null 2>&1; then
setsid "${SCRIPT_PATH}" __daemon >/dev/null 2>&1 &
else
nohup "${SCRIPT_PATH}" __daemon >/dev/null 2>&1 &
disown 2>/dev/null
fi
sleep 1
pid="$(is_running)" || die "start failed, check ${LOG_FILE}"
printf 'started (pid=%s)\nlog: %s\n' "${pid}" "${LOG_FILE}"
}
do_daemon() {
mkdir -p "${LOG_DIR}" || die "cannot create ${LOG_DIR}"
LOG_TO_FILE=1
exec >>"${LOG_FILE}" 2>&1
printf '%d\n' "$$" > "${PID_FILE}"
trap 'rm -f "${PID_FILE}"' EXIT
main_loop
}
do_stop() {
local pid i
pid="$(is_running)" || { printf 'not running\n'; rm -f "${PID_FILE}"; return 0; }
kill -TERM -"${pid}" 2>/dev/null || kill -TERM "${pid}" 2>/dev/null
for (( i = 0; i < 20; i++ )); do
kill -0 "${pid}" 2>/dev/null || break
sleep 0.5
done
if kill -0 "${pid}" 2>/dev/null; then
printf 'SIGTERM ignored, sending SIGKILL\n'
kill -KILL -"${pid}" 2>/dev/null || kill -KILL "${pid}" 2>/dev/null
fi
rm -f "${PID_FILE}"
printf 'stopped (pid=%s)\n' "${pid}"
}
do_status() {
local pid
if pid="$(is_running)"; then
printf 'status : running (pid=%s)\n' "${pid}"
else
printf 'status : stopped\n'
fi
printf 'log : %s\n' "${LOG_FILE}"
[ -f "${LOG_FILE}" ] && printf 'size : %s\n' "$(du -h "${LOG_FILE}" | cut -f1)"
printf 'cmds : %d, interval=%ds\n' "${#COMMANDS[@]}" "${INTERVAL_SEC}"
}
usage() {
cat <<EOF
Usage: ${SCRIPT_NAME} {start|stop|status|fg|tail|clear}
start Run detached in the background; the previous log is discarded first
(see START_LOG_MODE)
stop Stop the background instance
status Show pid / log path / log size
fg Run in the foreground, output to the terminal (for a quick check)
tail tail -f the log file
clear Remove the log file and all rotated copies (must be stopped first)
Log file : ${LOG_FILE}
To add a command, edit the COMMANDS array at the top of this script.
EOF
}
###############################################################################
# Entry point
###############################################################################
case "${1:-}" in
start) do_start ;;
stop) do_stop ;;
status) do_status ;;
fg) LOG_TO_FILE=0; main_loop ;;
tail) tail -n 50 -f "${LOG_FILE}" ;;
clear)
is_running >/dev/null && die "still running, stop it first"
rm -f "${LOG_FILE}" "${LOG_FILE}".[0-9]*
printf 'log cleared\n'
;;
__daemon) do_daemon ;;
-h|--help|"") usage ;;
*) printf '[ERROR] unknown sub-command: %s\n\n' "$1" >&2; usage; exit 1 ;;
esac
+2 -2
View File
@@ -1,10 +1,10 @@
;================================================================ ;================================================================
; User Configuration ; User Configuration
;================================================================ ;================================================================
strTestcase="Margin"
project_name = "Blanton" project_name = "Blanton"
strTestcase="ENV" ;ENV,EMC,Margin...etc
EN_Margin = 1 ; 1: Enable Margin Test, 0: Disable Margin Test EN_Margin = 0 ; 1: Enable Margin Test, 0: Disable Margin Test
EN_log = 1 ; 1: Enable log, 0: Disable log EN_log = 1 ; 1: Enable log, 0: Disable log
;================================================================ ;================================================================
@@ -0,0 +1,30 @@
; =============================================================================
; File : utils/wait_init.ttl
; Version : V1.0.0
; Date : 2026-08-21
; Author : ETWen
; =============================================================================
; Wait until "show platform temperature" is not "Thermal Not detected".
; Enter : prompt of the previous command is NOT consumed.
; Exit : "show platform temperature" is sent, prompt NOT consumed.
;
; Version History:
; V1.0.0 2026-08-21 Initial Version
; =============================================================================
wt_interval = 10
timeout = 30
wait prompt_sonic_root
:WAIT_THERMAL
sendln "show platform temperature"
wait "Thermal Not detected" prompt_sonic_root
if result = 2 then
goto THERMAL_OK
endif
wait prompt_sonic_root
pause wt_interval
goto WAIT_THERMAL
:THERMAL_OK
timeout = 0
sendln "show platform temperature"