44 Commits
Author SHA1 Message Date
etwenandClaude Opus 5 bc2b529c3a feat(nfc): Poll the NFC tag through the soak instead of sampling it once
nfc_polling.sh wraps blantons_nfc_validate.py in a background loop and
keeps score: start/stop/status/tail/report/clear/fg. Script A takes a
30s baseline, Script B starts the poll for the soak, Script C stops it
with the other monitors and reports.

report only reads the log, so it can be run mid-soak without disturbing
anything - and it says RUNNING so a snapshot is not read as the final
answer. Each round appends a machine-readable RESULT line, which keeps
the tally independent of the tool's wording.

ERROR is counted apart from FAIL. A round where the tool printed neither
[PASS] nor [FAIL] - crashed, missing, hung past the timeout - measured
nothing, and that is a different fault from a tag that would not read.
Rolling them together hides whichever one you are not looking for.

Two habits from earlier bugs are baked in: tail does not follow (tail -f
never returns and a macro waiting on the prompt hangs there), and the .py
is called through python3 rather than ./ (it arrives mode 100644 and a
USB copy carries no execute bit).

Also fixes Script C copying "nfc_polling.log" to the job dir when the
file is "nfc_poll.log". The cp failed, nothing checked it, and the run
finished looking fine - you would find out at the desk, with a job
archive that had no NFC log in it.

Script A -> V1.1.2.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014RetWKFZFG1ZHcitQAyhwM
2026-08-28 22:41:02 +08:00
etwenandClaude Opus 5 c3b0d49145 fix(session): Clear the last run's jobs and session before starting
After a run that ended badly - Tera Term closed, DUT rebooted mid-soak,
macro stopped by hand - the stress jobs kept running and the test session
stayed open. The next run then started on a box that was already loaded.
Nothing errored; the numbers were just quietly wrong.

Script A now opens with hw-test-session finish, bgctl reset --yes,
bgctl stop --all, then bgctl list. The list is the point of the sequence:
it is the one line in the log that proves the DUT was idle at the start.

Script A -> V1.1.1.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014RetWKFZFG1ZHcitQAyhwM
2026-08-28 22:04:31 +08:00
etwenandClaude Opus 5 59861cf953 feat(ber,power): Add the BER sweep and TR518 load; keep ce0 actually sending
Three new tools and one fix to an old one.

blanton_ber.sh - PRBS BER across every cabled port, init/start/report/
stop/clear, -u 0|1|all. A lane passes only below 1e-6 (equal to the
threshold fails). report ends with a per-unit summary carrying the worst
lane, so an all-PASS run still shows how much margin there was. init
checks every lane locked, because an unlocked lane still reports a BER
and it is a meaningless one. Script C runs it after the traffic report,
following SWB_UNIT0/SWB_UNIT1 like the traffic block does.

blanton_tr518.sh - the built-in packet test, used to load the rails
before measuring power. start settles 120s so the reading afterwards is
loaded rather than mid-ramp. stop sends port cd lb=none and nothing else,
exactly what the bench procedure does - l2 learning and test mode are
left as start set them, and stop says so instead of quietly leaving the
box in a state nobody asked about.

TH6_SWB{0,1}_power_readback.sh - 19 rails, V x I and total. These read
the two sensor tables ONCE. The previous version read them per rail: 38
invocations of a slow CLI at 38 different instants, so the "total" was a
sum of readings seconds apart - and under load the currents move (TH6_CORE
186 A -> 636 A). A rail missing from the table now warns on stderr instead
of silently contributing 0 W.

blanton_traffic_linespeed.sh - ce0 (VLAN 138) joins the test. It has no
loopback partner and a switch never sends a frame back out its ingress
port, so a tx burst was one round trip and then silence. init now sets an
ingress mirror of ce0 back to itself; the mirrored copy is not
egress-filtered, so packets keep lapping until stop turns the mirror off.
stop is what ends it - removing the VLAN membership does not, because the
copy never consulted the VLAN. TL_CE_MODE also drops to 'self': ps ce
shows one ce port per unit and a unit-0-only burst returned on unit 0.
Read as cross-unit, both links dying would still report PASS (0 == 0).

Fixes a parser bug worth remembering: bcmcmd returns CRLF, and the BER is
the last token on its line, so the CR stayed glued to the number. Every
lane read as NA, and printing the CR sent the cursor to column 1 so the
RESULT column overwrote the start of the row. One stray \r, two symptoms.

Not yet verified on hardware, all called out in the release notes: the
ce0 mirror (destport == srcport may be refused), the 120s settle (tr 518
may be time-limited), and 'test mode nr=no' on stop.

Script A -> V1.1.0.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014RetWKFZFG1ZHcitQAyhwM
2026-08-28 16:33:40 +08:00
etwenandClaude Opus 5 43314444e9 feat(config): Run the fans at full speed by default
FAN_SPEED 30 -> 100. A soak is a thermal test of everything except the
cooling, so the cooling should not be one of the variables in it. At 30%
a long run can throttle partway through, and throttled results read like
a different fault entirely.

Folded into V1.0.11 rather than bumping the version: the tag has not
moved on and this is the same delivery.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014RetWKFZFG1ZHcitQAyhwM
2026-08-28 10:32:07 +08:00
etwenandClaude Opus 5 4854da8518 feat(margin): Save every LTC2977 in one command, and stop lying about failures
margin_save discarded the result of every write, so a NACK printed
"Change Saved" exactly like a successful store. For the one permanent
operation in this tool that is the worst place to stay quiet. Each store
is now checked, named on failure, and reflected in the exit code.

STORE_USER_ALL also went out as 0x15 followed by a dummy 0x00 - an SMBus
write-byte where the command is a send-byte. The LTC2977 tolerated it,
but tolerance is not correctness. Now i2cset ... 0x15 c on the native
path and a no-data cb_pmbus_write on the FPGA path.

Two new subcommands:

  margin_save all      - all nine boards in settings/, then re-init
                         whichever board was loaded beforehand so a
                         following margin_status still reports the board
                         you were looking at. Does not stop on the first
                         failure; a half-saved set is harder to reason
                         about than a fully-attempted one.

  margin_save blanton  - this platform exposes the CB FPGA F3 I2C
                         channels as native Linux i2c buses (Ch6->13,
                         Ch7->14, Ch8->15, Ch9->16; CB stays on bus 4),
                         so all 18 LTC2977 can be stored with plain
                         i2cset: no margin_init, no FPGA channel setup,
                         no pcimem. 18 commands instead of 42.

The 18 raw commands were verified on the DUT 2026-08-27; the wrapper was
tested against stubbed hardware only. Release notes say so.

Board list is MARGIN_BLANTON_STORE at the top of margin.sh - comment out
what this DUT does not have rather than letting it talk to absent boards.
Missing bus and unresponsive chip are reported as distinct failures.

MARGIN_STORE_SETTLE (0.5s) sits between stores because nothing here polls
the part's busy bit, and blanton fires 18 in a row.

Script A -> V1.0.11.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014RetWKFZFG1ZHcitQAyhwM
2026-08-28 08:39:31 +08:00
etwenandClaude Opus 5 15ea776b82 feat(soak): Show the background jobs each round, and stop the loop spinning
Script B's monitoring loop had no pause at all - each round started the
instant the last one ended, so an overnight soak filled the log with
near-identical samples taken seconds apart. The section was even labelled
"Get data every 10mins" while running with no delay whatsoever.

The loop now ends each round with pause 60 and prints both job lists:
bgctl list for the platform job runner, jobs for anything the macro
backgrounded in that shell. A stress job that dies mid-soak shows up as a
shorter list on the next round instead of going unnoticed until the end.

60s is still hard-coded and the Status sheet asks for 10 minutes, so the
Phase 3 item stays open - this makes the loop sane, it does not close it.

Also adds docs/LTC2980_channel_map.csv: the nine settings/*.conf flattened
into one Board,CONN,Ch,NetName,Vnom sheet, generated by
tools/gen_channel_map.sh. The CSV is output, not source. NC channels are
kept as NC/- because the channel number is also the PMBus page - dropping
the gaps would shift every channel after them.

Two things the merged table makes visible: SWB0 and SWB1 carry identical
net names and voltages (only the bus and CB_I2C_CH differ), so those are
two copies that can drift apart silently; and there are exactly 8 NC
channels, all on CONN14/CONN15.

tools/100G_PRBS.txt, TR518.txt and fan_ctrl.txt join the existing
traffic_loopback_*.txt references. TR518 is not scripted yet and cannot
share the hardware with blanton_traffic_linespeed - both drive port
loopback and L2 learning.

Script A -> V1.0.10.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014RetWKFZFG1ZHcitQAyhwM
2026-08-27 14:29:47 +08:00
etwenandClaude Opus 5 03d609cc1f feat(prbs): Add 100G PRBS monitor; fix the job log path and USB mount
port_prbs_monitor.sh drives PRBS on both 100G uplinks: lpmode off, arm
the pattern, then poll `phy diag <phy> prbs get` in the background until
stopped, counting a poll as PASS only when the output says "PRBS OK!".
prbsstat Ber is captured beside every poll for the record but does not
decide the verdict. `stop` runs prbsstat STOp and prbs clear before
rendering the report, so the test does not leave PRBS armed.

`start a` / `start b` runs one uplink alone, which is how a genuine port
fault is separated from the DUT not coping with two PRBS streams at
once. A port that was not run reports SKIP rather than FAIL -- deciding
on poll count alone would have made a deliberate single-port run look
like half the hardware was broken.

The port status is deliberately not printed at `start`: with PRBS armed
the link reports DOWN, which reads as a failure to anyone glancing at
the console. It appears in the report instead, after prbs clear and
labelled as the recovered state.

The calls in Script A, B and C are committed but commented out -- PRBS
is still under bring-up.

Fix: the job directory was written as /host/hw-eval/current/jobs in some
places and /host/hw-eval/jobs in others, so what Script B wrote was not
what Script C collected. Unified on /host/hw-eval/jobs.

Fix: Script C copied to /mnt/usb assuming it was mounted. It now finds
the device with usb_target.sh and mounts it first, so the logs actually
leave the DUT.

Script C also reorders its results -- stress logs, then the MGMT ping
report, then 100G status -- and cats the bgctl ssd/usb logs.

Script A -> V1.0.9. Docs and docs/release-notes/v1.0.9.md updated.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014RetWKFZFG1ZHcitQAyhwM
2026-08-26 15:35:48 +08:00
etwenandClaude Opus 5 5e1e4a6a8f docs(release): Add v1.0.8 notes; wire mgmt ping status into B and C
Script A: the baseline ping window is 30 s rather than 45, and the log
is cleared afterwards so Script B starts from nothing.

Script B: `status` after `start`, and Script C: `status` before `stop`
plus a `cat` of the report. The status line shows both pids and the
replies received per NIC, so a dead link is visible while the soak is
still running instead of only at the end.

docs/release-notes/v1.0.8.md written in the house format.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014RetWKFZFG1ZHcitQAyhwM
2026-08-26 10:13:34 +08:00
etwenandClaude Opus 5 a31c186142 feat(mgmt): Ping both NICs at once, and stop counting ARP as loss
The bench run reported FAIL=3 with no NIC errors and no drops. All three
failures were on eth1 and all three were missing exactly icmp_seq=1,
with the other nine replies present and sub-millisecond. That is
neighbour resolution: once the ARP entry for the target expires, the
first echo request is spent resolving it and ping counts it as loss.
Rounds sat about 24 s apart, right on the edge of the default reachable
time, so it happened on some rounds and not others.

mgmt_ping_monitor.sh V3.0.0:

- One discarded ping per NIC before the measured run resolves ARP, so
  MAX_LOSS_PCT can stay at 0 and a FAIL means a real drop. Raising the
  threshold instead would have hidden genuine single-packet loss, and a
  longer settle would not help -- nothing triggers ARP until the ping.
- Both NICs now ping at the same time and keep accumulating until
  stopped, rather than alternating fixed bursts. Each writes its own raw
  capture.
- `stop` sends SIGINT rather than SIGTERM, because ping prints its
  statistics block on interrupt and that block is what the report
  parses, then renders into the log: the last TAIL_LINES entries per
  NIC, each one's statistics and verdict, and `ip -s link show` for both
  interfaces.
- `-D -O` are on by default so every line carries a timestamp and a
  request that got no reply is visible in the tail instead of merely
  absent. Clear PING_EXTRA_OPTS if the platform's ping rejects them.

Since the script no longer re-execs itself to daemonise, `bash
mgmt_ping_monitor.sh start` works without the execute bit.

.gitignore: the new *.raw / *.pid / *.state runtime files were outside
the *.log rule and would have been committed.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014RetWKFZFG1ZHcitQAyhwM
2026-08-26 09:53:20 +08:00
etwenandClaude Opus 5 1b8d5a8e91 feat(usb): Detect the USB target instead of assuming /dev/sda1
New Blanton_Script/usb_target.sh reports a USB mass-storage device's
node, mount point or by-id path. It excludes whatever disk backs / and
/host -- this platform can boot from a USB DOM, which also reports
TRAN=usb -- refuses to guess when several USB disks are present, falls
back to the whole device for superfloppy media, and when mounting,
checks the result is actually writable rather than trusting that mount
succeeded.

Script B asks it for the device node and builds the stress command from
the answer, so a stick that enumerates as sdb no longer sends the write
test at whatever /dev/sda1 happens to be.

The device is carried back through an echo marker, split in the shell
literal as "<<D""EV=..." so the command echo cannot satisfy the pattern
the macro waits for -- the same false-match guard used for PORTS0= and
NCMIF=, arranged differently.

Script B: DDR memtester drops its 100-pass limit and runs until stopped,
matching the other soak loads.

Fix: Script C ran `bgctl reset --yes` while killing processes, which is
before the job logs are copied to USB. Moved to after the archive and
after hw-test-session finish, so the run's own logs are collected before
anything clears them.

Script A -> V1.0.8 history updated.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014RetWKFZFG1ZHcitQAyhwM
2026-08-25 17:03:36 +08:00
etwenandClaude Opus 5 cac37583e7 feat(ttl): Include mgmt_ping.log in the USB archive
Script C now copies log/mgmt_ping.log into
/host/hw-eval/current/jobs/ alongside bmc_poll.log, so the management
link results leave the DUT with the run instead of staying behind. Both
monitors' logs are now covered.

Script A history: the two copies are one action in one block, so they
share a single V1.0.8 line rather than reading as separate changes.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014RetWKFZFG1ZHcitQAyhwM
2026-08-25 15:57:23 +08:00
etwenandClaude Opus 5 802ee4d467 feat(ttl): Fold bmc_poll.log into the USB archive
Script C copies log/bmc_poll.log into /host/hw-eval/current/jobs/ before
that directory is archived to USB, so the BMC sampling and I2C
read-back results leave the DUT with the run they belong to rather than
only existing in the Tera Term capture.

The wait moves ahead of the getdate/sprintf2 block; those build strings
without touching the terminal, so the prompt is still consumed exactly
once before the copy is sent.

Script A history updated under the existing V1.0.8 block -- that version
is not tagged yet, so this is the same batch.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014RetWKFZFG1ZHcitQAyhwM
2026-08-25 15:55:05 +08:00
etwenandClaude Opus 5 166b53983f feat(bmc): Re-enable bmc_monitor with an I2C write/read-back test
bmc_monitor.sh comes back into the run with a second job beyond the
`free -m` sampling: an I2C pattern test against bus 1, address 0x41,
offset 0x00C0 -- write 55AA55AA and read it back, then write AA55AA55
and read that back. Two complementary patterns catch a stuck bit either
way round, and repeating it through the soak turns an intermittent I2C
fault into something the log shows rather than something the tester has
to catch live.

Script B starts it alongside the other stress; Script C stops it and
cats log/bmc_poll.log.

bmc_monitor_ddr.sh stays disabled -- BMC DDR stress runs through
bgctl + bmc-manager.

Docs: the "disabled" notes I added last time now applied to only half
the pair and read as wrong for bmc_monitor.sh. Split so the monitor is
documented as live with its I2C test, and only the _ddr variant is
marked disabled.

Script A -> V1.0.8.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014RetWKFZFG1ZHcitQAyhwM
2026-08-25 15:50:56 +08:00
etwenandClaude Opus 5 ef7cbdffd4 docs(release): Add v1.0.7 notes; bump Script A header to V1.0.7
Script A carried a V1.0.7 history block while its "; Version :" header
still read V1.0.6, so publish.sh built Script_ABC_Blanton_V1.0.6 and the
folder would have disagreed with the tag. Header and date corrected --
reading the version from the header rather than a flag is what surfaced
this.

utils/wait_init.ttl V3.1.1: poll interval 10 s -> 60 s, and the WT_MIN
comments now say "at least" to match the test, which has been
`< WT_MIN` since the threshold was fixed. The stale "more than" wording
described exactly the off-by-one that once hung Script A.

Script C: the BMC USB journalctl dump is commented out.

docs/release-notes/v1.0.7.md written in the house format.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014RetWKFZFG1ZHcitQAyhwM
2026-08-25 14:22:22 +08:00
etwenandClaude Opus 5 ae8941a844 feat(ttl): Set fan speed in Script A, archive job logs to USB in C
config.ttl gains FAN_SPEED (30). Script A rebinds the two max31790
controllers (18-0020, 25-0020) and applies the speed with
fan-speed-control.sh, answering its confirmation prompt, so a run starts
from a known thermal state rather than whatever the last test left.

Script A also clears /host/hw-eval/current/jobs before the run, and
Script C copies that directory to /mnt/usb/jobs-<date>_<time> so the
bgctl job logs leave the DUT with the run they belong to.

Fix: 25-0020 was bound twice in a row -- the second bind can only fail,
since the driver is already attached. Removed; the two controllers now
have a matching unbind/sleep/bind each.

Script A -> V1.0.7, history covering the config.ttl and Script C changes
as well.

Docs: fan control and the job-log archive added to Tech Stack, Key
Features and Data Flow; the config.ttl example refreshed with FAN_SPEED
and the SWB_UNIT flags. Three risks recorded in Future Extensions --
`rm` without -r cannot clear the job subdirectories that Script C copies
with -r, the fan confirmation wait has no timeout and would hang
silently if the prompt ever changes, and /mnt/usb is never checked for
being mounted.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014RetWKFZFG1ZHcitQAyhwM
2026-08-25 14:12:24 +08:00
etwenandClaude Opus 5 a6a12cdf2c docs: Sync ARCHITECTURE and CLAUDE with the bgctl and mgmt-ping work
Script A: wait 45 s between starting and stopping mgmt_ping_monitor.
`start` returns after one second, but a round is roughly 30 s -- without
the pause the log held only the START banner and no RESULT line.

Docs brought up to date with the eight commits since 419b680:

- Tech Stack gains bgctl, systemd-run, iproute2 and hw-test-session;
  the stress row moves off the hammer scripts
- Structure and Key Features cover mgmt_ping_monitor.sh, and mark
  bmc_monitor{,_ddr}.sh as disabled -- their call sites in B and C are
  commented out, though the files remain
- Data Flow rewritten for all three scripts
- Two constraints added: TTL's `timeout` is global and must be restored,
  and two management NICs on one subnet cause ARP flux (hence
  ARP_STRICT, and nic_tx/nic_rx as the evidence of which NIC sent)
- CLAUDE gotchas add the `ip -s link` column order, that a monitor's
  `start` returns in one second rather than after a round, and that
  show_dmesg now ends with `dmesg -C`
- Phase 3 ticks the move to bgctl; Phase 5 gains the mgmt-ping, NVMe and
  EDAC parsing targets. Future Extensions notes that Script C never cats
  mgmt_ping.log, so the soak's ping results stay on the DUT

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014RetWKFZFG1ZHcitQAyhwM
2026-08-24 23:34:33 +08:00
etwenandClaude Opus 5 e38d24c775 feat: Add mgmt_ping_monitor.sh and move stress onto bgctl
New Blanton_Script/mgmt_ping_monitor.sh: brings both management NICs up
with iproute2 (`ip link set` / `address replace` / `route replace` with
per-NIC metric) and pings each one's own target -- eth0 -> .30, eth1 ->
.31 -- appending to a rotating log. Same sub-commands as the bmc
monitors, plus `summary` for just the per-leg RESULT lines.

Both NICs stay up, which on a shared subnet lets the target's ARP be
answered by either one, so a reply can land on the NIC that did not
send. ARP_STRICT applies arp_ignore/arp_announce to prevent that, and
each leg logs `ip -s link show` with the interface's own TX/RX packet
delta across the burst as direct evidence of which NIC carried the
traffic. Note `ip -s link` orders columns "bytes packets ...", so the
packet count is the second field.

Script A: hw-test-session start/log/status, a fuller DUT inventory
(version, fwutil, syseeprom, ssdhealth, TPM, nvme smart-log, smartctl),
bmc-first-enroll and bmc-manager version/status, ras-mc-ctl summary, and
both 100G uplinks now brought up rather than 513 being left down.

Script B: stress moves to bgctl (memtester, qfx5252-stress-ssd/-usb) and
the BMC DDR load runs through bmc-manager, replacing the hammer scripts
and the bmc_monitor pair. Adds a BMC USB net test that discovers the
cdc_ncm interface and runs a 4-hour ping under systemd-run.

Script C: stops the bgctl jobs, the BMC USB unit and the mgmt ping, then
collects journalctl, per-NIC counters, ras-mc-ctl and NVMe health, and
closes the session with hw-test-session finish.

utils/show_dmesg.ttl: one combined error regex with `dmesg -T`, an
i2c-filtered view, then `dmesg -C` so Script C's capture shows only what
the soak produced. utils/setup_pmon.ttl: add the TPM FRU/read checks.

Fix: Script B set `timeout = 15` for the cdc_ncm probe and never
restored it, leaving the cap in force for everything after -- including
the traffic init, which walks 108 VLANs per unit and takes far longer
than 15 s. A timed-out wait returns without the prompt, so the macro
would have run ahead of the DUT for the rest of the soak. Reset to 0 at
:skip_ping, where both branches meet.

Script A -> V1.0.6.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014RetWKFZFG1ZHcitQAyhwM
2026-08-24 23:27:37 +08:00
etwenandClaude Opus 5 9a11c1d5bb fix(ttl): Wait for the prompt before config save in the uplink block
`config save -y` was sent straight after the Ethernet514 startup with no
wait in between. The shell buffers the second line and still runs it, so
nothing visibly breaks, but every wait from there on matches the prompt
of the previous command -- leaving the macro permanently one step ahead
and issuing `show interfaces status` while `config save` is still
running. Every other sendln in the file waits first; this one now does
too.

Version left at V1.0.5.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014RetWKFZFG1ZHcitQAyhwM
2026-08-21 12:58:06 +08:00
etwenandClaude Opus 5 cbef5865ec fix(ttl): Treat 216 ports up as ready, and configure the 100G uplinks
wait_init.ttl: the comparison was strictly greater than WT_MIN, so a unit
reporting exactly 216 never passed and Script A sat in the poll loop
forever. 216 is not an arbitrary number -- TL_PAIRS holds 108 loopback
pairs, so 108 x 2 = 216 is every cabled port being up, i.e. precisely the
state being waited for. Comparison is now `< WT_MIN`, making the
threshold "at least 216".

Script A: bring the 100G uplinks into a known state before reading their
status -- Ethernet513 on asic0, Ethernet514 on asic1 -- and persist it
with `config save -y`. The stray `wait` after show_dmesg is dropped; the
one opening the new block consumes that prompt instead.

Version left at V1.0.5.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014RetWKFZFG1ZHcitQAyhwM
2026-08-21 12:56:13 +08:00
etwenandClaude Opus 5 8714bbb773 fix(ttl): Compare SWB_UNIT1 in the second operand of the traffic branches
Rework the traffic blocks in A, B and C as explicit if/elseif branches
holding literal commands, and drop the derived swb_any/swb_opt from
config.ttl -- the branches read straight off the page and there is no
indirection to follow.

All nine conditions compared SWB_UNIT0 against itself, though:

    if     SWB_UNIT0 = 1 && SWB_UNIT0 = 1     ->  SWB_UNIT0 = 1
    elseif SWB_UNIT0 = 1 && SWB_UNIT0 = 0     ->  never
    elseif SWB_UNIT0 = 0 && SWB_UNIT0 = 1     ->  never

so the single-unit paths were unreachable. A DUT with only unit 0 would
have run the both-unit commands and hit bcmcmd on an absent unit 1,
while a DUT with only unit 1 fell through to the empty else and skipped
traffic entirely. Second operand is now SWB_UNIT1.

Verified per branch that the -u argument matches the condition guarding
it: both units carry no -u (tool default TL_UNITS="0 1"), unit-0-only
carries -u 0, unit-1-only carries -u 1, and the else stays empty.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014RetWKFZFG1ZHcitQAyhwM
2026-08-21 12:19:18 +08:00
etwenandClaude Opus 5 d8bf1f7a41 feat(ttl): Drive traffic and port wait from SWB_UNIT0/SWB_UNIT1
The bench is not always fully populated. config.ttl now declares which
switch units exist:

    SWB_UNIT0 = 1   ; this DUT has switch unit 0
    SWB_UNIT1 = 1   ; this DUT has switch unit 1

Two values are derived there rather than repeating the same test in
three scripts: swb_any (0 = no unit at all) and swb_opt, the suffix
appended to each blanton_traffic_linespeed call -- "" for both units so
the tool's own TL_UNITS="0 1" applies, " -u 0" or " -u 1" for a single
one.

Script A, B and C build their traffic commands with sprintf2 and the
suffix, wrapped in `if swb_any = 1`. A half-populated DUT no longer
issues bcmcmd against an absent unit, and a DUT with no switch board
skips the traffic stage outright instead of filling the log with
failures.

wait_init.ttl reads the same two flags instead of its own copies, so the
wait and the traffic blocks cannot disagree about what is installed.

Script A -> V1.0.5.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014RetWKFZFG1ZHcitQAyhwM
2026-08-21 12:06:28 +08:00
etwenandClaude Opus 5 da39122c43 feat(ttl): Let wait_init skip switch units the DUT does not have
wait_init.ttl V3.0.0 adds WT_UNIT0 and WT_UNIT1 at the top of the file.
Benches are not always fully populated, and V2.0.0 waited on both units
unconditionally, so a DUT with one switch board sat in the poll loop
forever -- silently, since the loop neither advances nor reports.

    both units  -> 1 , 1
    unit 0 only -> 1 , 0
    unit 1 only -> 0 , 1
    no unit     -> 0 , 0   (bypass, returns immediately)

A disabled unit is skipped rather than polled and ignored: its whole
block sits inside the if, so no bcmcmd is issued for it and no
misleading error reaches the log.

Two independent integer flags rather than a "0 1" string, because
parsing a string in TTL needs strscan and this is meant to be edited by
hand at the bench.

All three exit paths still leave exactly one prompt unconsumed, so
Script A's surrounding waits are unaffected.

Script A history: recorded under the existing V1.0.4 block rather than a
new version, matching the consolidation done there.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014RetWKFZFG1ZHcitQAyhwM
2026-08-21 11:36:38 +08:00
etwenandClaude Opus 5 b66ae47a58 feat(ttl): Read EEPROM, quiet LLDP and clear lpmode before traffic
Script A: `hpe-eeprom-tlv show --bus 4 --addr 0x50` after `show boot`, so
the board identity is on record next to the image it booted. The empty
Check History placeholder is dropped.

Script A and B: disable the lldp feature and `config save -y` before the
traffic stage, so the switch stops sourcing its own frames and the
loopback pair counters reflect only the injected burst.

Script B: `sfputil lpmode off` on Ethernet513/514 before reading their
status -- a transceiver left in low-power mode will not link.

Script A -> V1.0.5, with the history covering the Script B changes too,
per this project's convention of keeping one consolidated log in A.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014RetWKFZFG1ZHcitQAyhwM
2026-08-21 11:28:06 +08:00
etwenandClaude Opus 5 d065db7d71 feat(ttl): Gate Script A on bcmcmd port-up count, not thermal sensors
wait_init.ttl V2.0.0 now polls

    bcmcmd -n 0 -c ps | grep -w up | wc -l
    bcmcmd -n 1 -c ps | grep -w up | wc -l

and proceeds only when BOTH exceed 216, so the baseline is taken with
the data plane actually up rather than merely with pmon answering.

V1.0.0 could use `wait "Thermal Not detected" prompt` because that was a
string-presence test. Comparing a count needs the value captured, so the
count is wrapped in an echo marker and read with waitregex +
groupmatchstr1 + str2int. The command echo cannot false-match: it reads
"PORTS0=$(bcmcmd ..." and the pattern requires a digit immediately after
the "=". The optional-space allowance covers a wc that pads its output.

The two thresholds are compared in nested ifs rather than with `and`,
which is bitwise in TTL.

WT_MIN and WT_INTERVAL are at the top of the file. The enter/exit prompt
contract is unchanged, so Script A's surrounding waits still line up.
Script A -> V1.0.4.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014RetWKFZFG1ZHcitQAyhwM
2026-08-21 10:41:50 +08:00
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
etwen 9d61b1aa43 docs(status): Add TTL script status sheet 20260817 2026-08-17 16:32:44 +08:00
etwen 071244fde1 feat(traffic): Add SWB loopback line-speed traffic tool
Blanton_Script/blanton_traffic_linespeed.sh V0.3.2 drives the whole
loopback traffic run from the DUT shell via bcmcmd, for both switch
boards (unit 0 and 1):

- init  : vlan remove 1 pbm=cd, then create/add the 108 loopback
          pairs (VLAN 30..137, cdN <-> cdN+32)
- clear : clear c
- show  : show c (raw)
- start : tx <count> length=<len> VLantag=<vid> per VLAN
- stop  : remove the pairs, optional --destroy
- ps    : port status of the cabled ports
- report: parse the show c MIB_TPOK/MIB_RPOK counters and lay them
          out one pair per block with a cross-check verdict
          (cdA.TX == cdB.RX and cdA.RX == cdB.TX), PASS green /
          FAIL red / NA yellow, --tolerance for snapshot skew,
          --tsv, --raw and -f <log> for offline analysis; the raw
          capture is kept under /tmp
- run   : ps -> init -> clear -> start -> report

Options -u/-c/-l/-p/-T/-d/-n and a sourceable function API, in the
style of the other blanton_*.sh tools. Command output matches the
former Tera Term macros byte for byte; report output matches
tools/bcm_mibpair_report_V1.1.0.py, added here as the off-box
equivalent, together with the raw bcmcmd command lists under tools/.

ScriptA sources the tool, ScriptB runs ps/init/clear/show/start and
drops stress_hhmd and stress_pcie, ScriptC stops the traffic and
prints the pair counter report.
2026-08-17 16:32:43 +08:00
etwen 9bf4291d17 feat(fpga): Auto-detect CB FPGA BDF at source time
blanton_fpga_pcimem.sh V1.5.0.

The four FUNCT0..3_RES sysfs paths were hard-coded and had to be
edited for every DUT, because the CB FPGA's BDF changes with the
PCIe enumeration (0000:03:00.x / 0000:05:00.x seen so far).

Detection order at source time: FPGA_BDF override -> sysfs scan for
vendor 0x1590 with all four functions exposing resource0 -> lspci
-Dnn name/id match -> FPGA_BDF_FALLBACK. Adds fpga_rescan for a
DUT swap, FPGA_BDF / FPGA_VENDOR_ID / FPGA_LSPCI_MATCH overrides,
FPGA_BDF_ACTIVE reporting and FPGA_QUIET. Detection runs once, so
cb_fpga / pmc / icb / swb keep zero per-command overhead.
2026-08-17 16:32:42 +08:00
60 changed files with 13651 additions and 1850 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
+7 -1
View File
@@ -12,9 +12,15 @@ For_AI/
!**/logs/.gitkeep !**/logs/.gitkeep
!**/Logs/.gitkeep !**/Logs/.gitkeep
# 打包輸出 # monitor 腳本的執行期產物(原始擷取、pid、狀態)
*.raw
*.pid
*.state
# 打包輸出:產出不進 git,打包工具本身要進
publish/* publish/*
!publish/.gitkeep !publish/.gitkeep
!publish/publish.sh
# Office 暫存檔 # Office 暫存檔
~$* ~$*
+341 -76
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,16 @@ 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 工具鏈);`bmc-first-enroll` / `bmc-manager version\|status` |
| 背景工作管理 | `bgctl run/list/stop --all/reset`(平台工具);長時間 ping 用 `systemd-run --unit=` 掛成 transient unit |
| 管理網路 | iproute2`ip link set` / `ip address replace` / `ip route replace ... metric`+ `ping -I` |
| 測試 session | `hw-test-session start\|log\|status\|finish`job log 收在 `/host/hw-eval/jobs/` |
| 風扇控制 | max31790 driver rebind`/sys/bus/i2c/drivers/max31790/{bind,unbind}`+ `fan-speed-control.sh <%>` |
| 100G PRBS | `bcmcmd -n <unit> -c 'dsh -c "phy diag <phy> prbs …"'`set / prbsstat STArt / get / Ber / STOp / clear |
| 壓力測試 | `mlucas-avx2`AMD CPU)、`bgctl run` 排程的 `memtester` / `qfx5252-stress-ssd` / `qfx5252-stress-usb` |
| 資料面流量 | `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 +78,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 +112,31 @@ 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 ── LTC2980_channel_map.csv # 9 個 settings/*.conf 合併:Board,CONN,Ch,NetName,Vnom144 列
│ ├── 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 版
│ ├── 100G_PRBS.txt # 100G PRBS 原始指令(port_prbs_monitor.sh 的來源)
│ ├── TR518.txt # 內建封包測試 tr 518 → blanton_tr518.sh
│ ├── BER_Test.txt # 全機 PRBS BER 指令 → blanton_ber.sh
│ ├── 100G_Traffic.txt # ce0 VLAN 138 打流 → blanton_traffic_linespeed.sh
│ ├── fan_ctrl.txt # MAX31790 unbind/bind + fan-speed-control.sh 30
│ └── gen_channel_map.sh # settings/*.conf → docs/LTC2980_channel_map.csv(需 gawk
├── secret/ # 🚫 gitignored — DUT 密碼、per-unit BDF、COM 設定 ├── secret/ # 🚫 gitignored — DUT 密碼、per-unit BDF、COM 設定
│ ├── README.md # ✅ committed — 用途索引 │ ├── README.md # ✅ committed — 用途索引
@@ -119,7 +145,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 +159,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/
├── show_dmesg.ttl # date + dmesg grep error/fail/warning │ # current/ssdhealth/leak status/leak channels + TPM11 項)
│ ├── show_dmesg.ttl # dmesg -T 合併 error 正則 + i2c 過濾 + dmesg -C 清空
│ ├── 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 +182,32 @@ 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
├── mgmt_ping_monitor.sh # 10G/1G 管理網路 ping 監控(iproute2
│ # eth0→TARGET_10G、eth1→TARGET_1G,兩張卡同時持續 ping
├── port_prbs_monitor.sh # 100G uplink PRBS 測試(bcmcmd phy diag
├── blanton_ber.sh # 全機 SerDes BER 掃描(phydiag prbs / prbsstat
├── nfc_polling.sh # NFC tag 背景輪詢 + PASS/FAIL 統計
├── blantons_nfc_validate.py # NFC/NCI 驗證工具(廠商提供,未修改)
├── blanton_tr518.sh # TR518 封包測試,量功耗前的負載來源
├── TH6_SWB0_power_readback.sh # SWB0 19 條軌 V×I + 總功耗
├── TH6_SWB1_power_readback.sh # SWB1 同上
│ # ⚠️ 已納入交付,但 A/B/C 的呼叫仍註解掉(bring-up 中)
├── usb_target.sh # 偵測 USB 裝置節點/掛載點(排除系統碟)
├── bmc_monitor.sh # BMC 監控:free -m + I2C 寫入/讀回圖樣測試
├── bmc_monitor_ddr.sh # ⚠️ 已停用:B/C 內呼叫處已註解,DDR 壓力改走 bgctl + bmc-manager
│ # ↑ 三支都要 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
# # v2.7.0margin_save all / margin_save blanton
├── 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/ 找共用版
``` ```
--- ---
@@ -169,15 +219,20 @@ Blanton_TTL_Script/
### 1. `config.ttl` — 全域測試參數(TTL 變數) ### 1. `config.ttl` — 全域測試參數(TTL 變數)
```ini ```ini
strTestcase = "Margin" ; 測項名,進 log 檔名
project_name = "Blanton" ; 專案名,進 log 檔名 project_name = "Blanton" ; 專案名,進 log 檔名
EN_Margin = 1 ; 1=跑 margin 掃描, 0=跳過 strTestcase = "ENV" ; 測項名(ENV/EMC/Margin...),進 log 檔名
EN_Margin = 0 ; 1=跑 margin 掃描, 0=跳過
EN_log = 1 ; 1=logopen 存檔, 0=不存 EN_log = 1 ; 1=logopen 存檔, 0=不存
FAN_SPEED = 100 ; 傳給 fan-speed-control.sh 的風扇轉速 (%),預設全速
SWB_UNIT0 = 1 ; 這台有 switch unit 0(流量與 wait_init 都依此)
SWB_UNIT1 = 1 ; 這台有 switch unit 1
prompt_login = "sonic login:" prompt_login = "sonic login:"
prompt_sonic = "admin@sonic:~$" prompt_sonic = "admin@sonic:~$"
prompt_sonic_root = "root@sonic:~#" prompt_sonic_root = "root@sonic:~#"
``` ```
> `SWB_UNIT0` / `SWB_UNIT1` 兩個都是 0 時,`wait_init.ttl` 直接 bypass、A/B/C 的流量段整段跳過。
Log 檔名規則:`<mdir>\Logs\<project_name>_<strTestcase>_<YYYYmmdd-HHMMSS>.log` Log 檔名規則:`<mdir>\Logs\<project_name>_<strTestcase>_<YYYYmmdd-HHMMSS>.log`
### 2. `settings/<board>.conf` — 一顆 LTC298016 channel ### 2. `settings/<board>.conf` — 一顆 LTC298016 channel
@@ -193,6 +248,43 @@ 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`)不重疊。
**同一批 LTC2977 也掛在原生 i2c bus 上**2026-08-27 上機驗證):Blanton 的 SONiC 把 CB FPGA F3
的這四條 I2C 通道同時註冊成 kernel i2c adapter,因此除了 `cb_pmbus_*` → pcimem 這條路,
還可以用 `i2cset -f -y <bus>` 直接打到同一顆晶片:
| FPGA F3 通道 | Linux bus | CB FPGA 路徑 | 原生 i2c 路徑 |
|---|---|---|---|
| Ch6 | 13 | `cb_pmbus_write 6 <addr> …` | `i2cset -f -y 13 <addr> …` |
| Ch7 | 14 | `cb_pmbus_write 7 <addr> …` | `i2cset -f -y 14 <addr> …` |
| Ch8 | 15 | `cb_pmbus_write 8 <addr> …` | `i2cset -f -y 15 <addr> …` |
| Ch9 | 16 | `cb_pmbus_write 9 <addr> …` | `i2cset -f -y 16 <addr> …` |
`margin.sh``margin_save blanton` 走原生路徑(`MARGIN_BLANTON_STORE` 表),
其餘功能仍走 `TRANSPORT="swb"` 的 FPGA 路徑。兩條路到的是同一顆 LTC2977,
**不要同時用**(同一條匯流排上兩個 master 語意的存取)。
**全通道 net / 電壓對照:`docs/LTC2980_channel_map.csv`**9 個 `.conf` × 16ch = 144 列,
欄位 `Board,CONN,Ch,NetName,Vnom`)。查「某個 net 在哪片哪個 channel」比翻 9 個 `.conf` 快。
CSV 是產出物,不是正本 —— 改完 `.conf``./tools/gen_channel_map.sh` 重產,別手改 CSV。
兩件從表上看得出來、改 `.conf` 時要記得的事:
- **SWB0 與 SWB1 的 net / 電壓完全相同**,差別只在 `CHIPS[]` 的 bus`0:` vs `1:`)與 `CB_I2C_CH`
也就是說有兩份會各自漂移的重複資料 —— 改了 SWB0 沒改 SWB1,不會有任何東西擋你。
- **8 個 NC 通道**SWB0/1 CONN14 的 CH10/CH11/CH13、CONN15 的 CH10)在 CSV 裡以
`NC` / `-` 原樣保留,不是漏掉;濾掉它們會讓 page 編號對不上。
### 3. `profiles/*.conf` — 16-ch 批次 margin 組合 ### 3. `profiles/*.conf` — 16-ch 批次 margin 組合
```bash ```bash
@@ -235,9 +327,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 +344,27 @@ 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.1.2 | root 登入 → `date -s` 對時 → 清 job log → **收掉前一輪殘留**`hw-test-session finish``bgctl reset --yes``bgctl stop --all``bgctl list` 確認清空)→ `hw-test-session start`**設定風扇轉速****等資料面就緒**`wait_init.ttl`)→ DUT 清單(boot/version/fwutil/syseeprom/ssdhealth/TPM/NVMe)→ BMC enroll + version/status → source bash 工具 → 清/讀 7 組 PCIe AER + `ras-mc-ctl --summary``lspci` → margin → PMON → dmesg **10G/1G 管理網路 ping****NFC 基線**start → 30s → stop → report)→ 100G uplink 設定與狀態 → **一輪流量基線**`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` + `bgctl run``memtester` / SSD / USB 壓力 + BMC DDR`bmc-manager run memtester`)→ BMC USB net test(探測 cdc_ncm 介面,`systemd-run` 掛 4 小時 ping)→ 啟動 mgmt ping 監控 → **啟動 NFC 輪詢**`jobs` / `bgctl list` → traffic(依 `SWB_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)` + `bgctl stop --all` / `reset` + 停 BMC USB unit、mgmt ping 與 **NFC 輪詢** → PMON + dmesg → 7 組 AER + `ras-mc-ctl` → BMC USB `journalctl``ip -s link show eth0/eth1``cat` mlucas log → **NFC `report`** → traffic `stop` + `report`**BER 掃描**(依 `SWB_UNIT`)→ NVMe 健康 → `show reboot-cause` / `uptime`**job log 複製到 USB(帶時戳)**`hw-test-session finish` |
| **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`(一條龍)。**V0.6.0 起含 100G uplink `ce0`**VLAN 138):它沒有 loopback 對象,靠 **ingress mirror 打回自己**維持循環,`report` 最後一列就是它,底下再接一份跨 unit summary |
| **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` 除錯包裝 |
| **Margin 寫 NVM** | `margin_save` / `margin_save all` / `margin_save blanton` | ⚠️ 永久寫入。`0x15` 送 PMBus **send-byte**(不帶 data byte)。`all` = 依 `settings/` 走 9 顆、跑完還原原本載入的板子;`blanton` = 本平台捷徑,18 顆全走原生 i2c bus(`MARGIN_BLANTON_STORE` 表),不需 `margin_init`。每筆之間 `MARGIN_STORE_SETTLE`0.5s),工具**不 poll busy bit** |
| **管理網路 ping** | `mgmt_ping_monitor.sh {start\|stop\|status\|fg\|tail\|clear\|summary}` | 一輪 = 10G leg + 1G leg,各自 `ip address/route replace``ping -I <if>` 打自己的對端;兩張卡都保持 up。每 leg 記 `ip -s link show` 與該卡的 TX/RX packet delta,另出一行可 grep 的 `RESULT` |
| **BMC 監控** | `bmc_monitor.sh {start\|stop\|status\|fg\|tail\|clear}` | 由 host 端 `bmc-manager run` 週期取樣 `free -m`,另加一組 **I2C 寫入/讀回圖樣測試**bus 1、addr 0x41、offset 0x00C0,寫 `55AA55AA` 讀回、再寫 `AA55AA55` 讀回)。B 啟動、C 停止並 `cat log/bmc_poll.log` |
| **BMC DDR 壓力** | `bmc_monitor_ddr.sh` | ⚠️ **已停用** —— B/C 內呼叫處已註解,改由 `bgctl run bmc-manager run memtester` 取代。檔案保留未刪 |
| **100G PRBS** | `port_prbs_monitor.sh {start [both\|a\|b]\|stop\|status\|report\|clear}` | 兩個 uplink 各一個 worker,可同時或單獨跑。setup`lpmode off``prbs set``prbsstat STArt`)→ 週期 `prbs get`(判定)+ `prbsstat Ber`(只記錄)→ teardown`STOp` + `prbs clear`)。輸出含 `PRBS OK!` 才算 PASS。**⚠️ A/B/C 的呼叫目前註解掉** |
| **SerDes BER 掃描** | `blanton_ber.sh {init\|clear\|start\|stop\|report} [-u 0\|1\|all]` | 全機每個對接 port 的 PRBS BER`prbs set p=3` + `prbs get`(確認每條 lane 都鎖上)→ `prbsstat STArt Interval=30` →(等一個 interval)→ `prbsstat Ber``STOp``prbs clear``report` 一列一條 lane**BER < 1e-6 才 PASS**,最後跨 unit 的 summary 帶最差 lane。⚠️ PRBS 期間 link 顯示 DOWN、不過流量 |
| **TR518 負載** | `blanton_tr518.sh {start\|stop} [-u 0\|1\|all]` | `port cd lb=mac``l2 learn off``test mode nr=yes``tr 518 ... testphase=1`,然後 settle `TR_SETTLE_SEC`120s)再讓人量功耗。`stop` 只送 `port cd lb=none`(照 `TR518.txt`),L2 學習與 test mode **不會還原**`TR_RESTORE_*=1` 才會 |
| **SWB 軌功耗** | `TH6_SWB{0,1}_power_readback.sh` | 讀 `show platform voltage/current` **各一次**,19 條軌 V×I 與總功耗。舊版每軌各讀一次(38 次呼叫、每軌不同時刻),總和是不同瞬間的加總 |
| **NFC 讀取輪詢** | `nfc_polling.sh {start\|stop\|status\|tail\|report\|clear\|fg}` | 背景重複跑 `blantons_nfc_validate.py`,每輪追加一行機器可讀的 `RESULT=`(PASS/FAIL 取自工具自己的摘要行,兩者皆無則 **ERROR**,與 FAIL 分開計數)。`start` 會先清空 log`report` **只讀 logpolling 中隨時可跑**,並標示 `RUNNING` 免得快照被當最終結果。`tail` **不 follow** |
| **USB 目標偵測** | `usb_target.sh [-o dev\|mnt\|both\|id] [-n] [-r sec]` | 找出插入的 USB 儲存裝置,排除 `/``/host` 的底層碟(本平台可能從 USB DOM 開機),多顆時拒絕猜;掛載時會驗證真的可寫 |
| **打包交付** | `publish/publish.sh [-v Ver] [-f] [-z] [-n]` | 版號讀 Script A 檔頭,複製成 `publish/Script_ABC_Blanton_<Ver>/`,排除 `*.log`,可另出 zip |
--- ---
@@ -268,31 +379,55 @@ PROFILE_CHANNELS=( "0:high:8" "1:low:8" ... ) # "channel:operation:change_perc
Script A ── logopen Logs/Blanton_Margin_<ts>.log ──┐ (之後所有畫面都進這個檔) 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) │ ├─ rm /host/hw-eval/jobs/* ← 清掉上一輪的 job log
├─ hw-test-session finish / bgctl reset / stop --all / list (收前一輪)
├─ hw-test-session start / log / status
├─ 風扇:max31790 unbind→sleep→bind18-0020 與 25-0020 各一次)
│ → fan-speed-control.sh <FAN_SPEED> → 回答 y 確認 │
├─ 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 -T(合併 error 正則 + i2c 過濾)→ dmesg -C 清空 ← C 只會看到 soak 期間新產生的
├─ mgmt pingstart → pause 45 → stop → cat log ← 10G/1G 各一輪
├─ 100G uplinklpmode off → 兩埠 startup → config save → show interfaces status
├─ 一輪流量基線: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"
├─ jobs ← 確認全部起來了 ├─ bgctl run: memtester 1G / qfx5252-stress-ssd / qfx5252-stress-usb
while 1: fan → temperature → margin_status_all soak 期間持續取樣 BMC: bmc-manager run memtesterDDR)、cdc_ncm 介面探測 + systemd-run 4hr pingUSB
├─ mgmt_ping_monitor.sh start
├─ jobs / bgctl list ← 確認全部起來了
├─ 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 bgctl stop --all / reset、systemctl stop BMC USB unit、mgmt ping stop
├─ journalctl BMC USB unit、ip -s link show eth0/eth1、ras-mc-ctl --summary
├─ cat mlucas_amm_log ← CPU 壓力程式自身的 pass/fail
├─ nvme smart-log / smartctl -x /dev/nvme0
├─ traffic: stop → report ← per-pair TX/RX 交叉比對,PASS/FAIL/NAexit 2 = 有 FAIL/NA
├─ show reboot-cause / show uptime ← soak 期間有沒有意外重開,一眼看得到
├─ usb_target.sh -o dev → sync → mount <dev> /mnt/usb
├─ cp bmc_poll.log / mgmt_ping.log → jobs/,再 cp -r jobs/ /mnt/usb/jobs-<YYYYmmdd>_<HHMM>
└─ hw-test-session finish
[messagebox "GOOD JOB! Test Case DONE"] → 人工檢查 log [messagebox "GOOD JOB! Test Case DONE"] → 人工檢查 log
``` ```
@@ -321,22 +456,52 @@ 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
只吃 Sr;只有純暫存器裝置兩種都行。 只吃 Sr;只有純暫存器裝置兩種都行。
7. **`margin_save``STORE_USER_ALL`)會永久寫進 NVM**,斷電保留。測試流程中**絕不執行**, 7. **`margin_save``STORE_USER_ALL`)會永久寫進 NVM**,斷電保留。測試流程中**絕不執行**,
以免覆蓋客戶出廠 config。`margin.sh` 的 auto-enable 只改 RAM`ON_OFF_CONFIG`),斷電復原。 以免覆蓋客戶出廠 config。`margin.sh` 的 auto-enable 只改 RAM`ON_OFF_CONFIG`),斷電復原。
`margin_save all` / `margin_save blanton`v2.7.0)一次對 9 顆 LTC298018 個 LTC2977
下同一道命令,風險同性質但範圍是全機;命令本身是 PMBus send-byte`0x15`,不帶 data byte)。
8. **操作硬體電壓有損壞 DUT 的風險**:套 profile 前先確認該軌 OV/UV limit 與 servo DAC 注入電阻 8. **操作硬體電壓有損壞 DUT 的風險**:套 profile 前先確認該軌 OV/UV limit 與 servo DAC 注入電阻
有 populate(本板部分 margin 電阻標 PROTO,沒 populate 的軌電壓不會動)。 有 populate(本板部分 margin 電阻標 PROTO,沒 populate 的軌電壓不會動)。
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` / `mgmt_ping_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. **TTL 的 `timeout` 是全域的,設了就要還原**`timeout = N` 之後**每一個** `wait` 都被限制在 N 秒,
逾時的 `wait` 會直接返回而沒吃到 prompt,於是下一個指令在前一個還沒跑完就送出,之後整輪錯開一拍。
`wait_init.ttl` 設 60 後還原 0Script B 的 cdc_ncm 探測設 15,在 `:skip_ping`(兩條路徑的匯流點)還原。
加新的 `timeout` 時務必配一個 `timeout = 0`
15. **兩張管理網卡同網段時會 ARP flux**`mgmt_ping_monitor.sh` 讓 eth0/eth1 同時 up,若兩者在同一個
/24(本專案預設 `192.168.1.99` / `.101`),對端 ARP 可能由任一張卡回應,回包落在沒送封包的那張,
量到的數字不能歸屬。`ARP_STRICT=1` 會設 `arp_ignore=1` / `arp_announce=2` 擋掉;
另外每個 leg 的 `RESULT` 行帶 `nic_tx` / `nic_rx`(該卡自己的 packet delta),
**ping 成功但 `nic_tx` 接近 0 就代表封包從另一張卡出去了**。兩張卡在不同網段時可設 0。
16. **客戶 NDA**`docs/` 內含客戶 FPGA 規格與 schematic 衍生資訊,**本 repo 只推 NAS + Gitea
絕不推 GitHub**。 絕不推 GitHub**。
--- ---
@@ -350,7 +515,7 @@ pwr_data
| **客戶機密文件** | `docs/` 內 FPGA 規格(docx/xlsx/md)屬客戶 NDA 範圍;schematic 原始檔(`*.DSN`)一律 gitignore,不進 repo | | **客戶機密文件** | `docs/` 內 FPGA 規格(docx/xlsx/md)屬客戶 NDA 範圍;schematic 原始檔(`*.DSN`)一律 gitignore,不進 repo |
| **Remote 限制** | 只推私有 remote`nas``git.et-wen.com:51222`+ `gitea``etgit.et-wen.com`)。**不設 GitHub `origin`** | | **Remote 限制** | 只推私有 remote`nas``git.et-wen.com:51222`+ `gitea``etgit.et-wen.com`)。**不設 GitHub `origin`** |
| **測試 log** | `logs/*.log` 含 DUT 序號、MAC、韌體版本等可識別資訊 → gitignore,只保留目錄結構(`.gitkeep` | | **測試 log** | `logs/*.log` 含 DUT 序號、MAC、韌體版本等可識別資訊 → gitignore,只保留目錄結構(`.gitkeep` |
| **破壞性硬體指令** | `margin_save` / `STORE_USER_ALL` 為永久 NVM 寫入,工具預設不呼叫;批次腳本 `margin_save_all.sh` 在 README 與腳本頂端都有警語 | | **破壞性硬體指令** | `margin_save` / `margin_save all` / `margin_save blanton` / `STORE_USER_ALL` 為永久 NVM 寫入,工具預設不呼叫;`margin_save all`批次腳本 `margin_save_all.sh` 在 README 與程式頂端都有警語 |
| **root 權限** | 全流程在 `root@sonic:~#` 下執行(pcimem / i2c 需要);腳本不留任何 `chmod 777` 或放寬權限的動作 | | **root 權限** | 全流程在 `root@sonic:~#` 下執行(pcimem / i2c 需要);腳本不留任何 `chmod 777` 或放寬權限的動作 |
| **For_AI/** | AI 協作素材(截圖、草稿筆記)整個資料夾 gitignore,避免不小心把客戶圖面帶進 repo | | **For_AI/** | AI 協作素材(截圖、草稿筆記)整個資料夾 gitignore,避免不小心把客戶圖面帶進 repo |
@@ -371,22 +536,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 +574,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 +601,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 +628,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` 以外的異常。
@@ -454,18 +641,26 @@ margin status、全部 VRM 軌的 Vin/Vout/Iout/Temp、CB + ICB 溫感讀值,
**目標:** 壓力程序起得來、起不來看得出來,soak 迴圈有時戳、可控週期。 **目標:** 壓力程序起得來、起不來看得出來,soak 迴圈有時戳、可控週期。
**包含:** **包含:**
- [ ] 部署 `~/hammer/tools/` 前置檢查:新增 `utils/check_hammer_tools.ttl` - [ ] 前置檢查:`~/hammer/tools/amd/mlucas-avx2` 仍是唯一還依賴 hammer 的項目,缺檔就別往下跑
`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), - [x] 壓力層改用平台的 `bgctl``memtester` / `qfx5252-stress-ssd` / `qfx5252-stress-usb`),
數量不符時 `messagebox` 提示 取代 `~/hammer/tools/stress_*.py`Script B 以 `jobs` + `bgctl list` 兩者並列確認
- [ ] `bgctl list` 之後加數量驗證,不符時 `messagebox` 提示(目前只印出來給人看)
**← V1.0.10 起 `bgctl list` + `jobs` 改為每輪都印,中途死掉的 job 看得到,但仍要靠人眼比對**
- [ ] 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 **← V1.0.10 已補上 `pause 60`(迴圈不再全速空轉),但 60 秒是寫死的、也還沒有每輪 `date`
距 Status 表的 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 +669,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` 時整段不執行且不報錯。
--- ---
@@ -501,7 +709,13 @@ counter 與 error-counter,兩者可直接相減;`EN_Traffic=0` 時整段不
UE > 0 或 fatal/nonfatal > 0 直接標 FAIL UE > 0 或 fatal/nonfatal > 0 直接標 FAIL
- [ ] 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``bgctl` 各工作的 log
`/host/hw-eval/.../qfx5252-stress-{ssd,usb}.log`)的錯誤關鍵字掃描
- [ ] Traffic 判定:**直接複用 `tools/bcm_mibpair_report_V1.1.0.py`**(它已能吃 console log 的
`show c` 段落並輸出 PASS/FAIL),不要重寫一套解析
- [ ] 管理網路判定:抓 `mgmt_ping_monitor.sh``RESULT` 行(已是單行可 grep 格式),
並檢查 `nic_tx`/`nic_rx` 不為 0 —— 否則 PASS 也不能歸屬到那張卡
- [ ] NVMe / EDAC`nvme smart-log``smartctl -x``ras-mc-ctl --summary` 的前後差值
- [ ] 產出 `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 +728,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 +751,63 @@ 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` 提示。
- **腳本的執行權**`mgmt_ping_monitor.sh``bmc_monitor.sh`(以及已停用的 `bmc_monitor_ddr.sh`)需人工 `chmod +x`
可在 Script B 起它之前補一行(比照既有的 `chmod +x ~/hammer/tools/amd/mlucas-avx2`),或在 publish 時處理。
- **`bmc_monitor_ddr.sh` 已停用**:B/C 內的呼叫已註解,DDR 壓力改由 `bgctl run bmc-manager run memtester`
取代。檔案還留在 repo,待確認不再需要後刪除,或在檔頭標註停用。
`bmc_monitor.sh` 已於 2026-08-25 重新啟用,並加上 I2C 寫入/讀回圖樣測試。)
- **`rm /host/hw-eval/jobs/*` 清不掉子目錄**:Script A 用的是不帶 `-r``rm`,但 Script C
`cp -r` 那個目錄,代表裡面可能有子目錄。清不乾淨的話,上一輪的 job log 會被當成這輪的一起
複製到 USB。要確認 `bgctl` 的產出結構,必要時改成 `rm -rf .../jobs/*`
- **100G PRBS 尚未接進 A/B/C**`port_prbs_monitor.sh` 已隨包交付、可手動執行,但三支腳本裡的呼叫
都還註解著(bring-up 中)。要啟用時記得 PRBS 期間 link 會顯示 down`stop` 之後才會回來。
- **Script C 的 USB 掛載走 `mount` 而非 `usb_target.sh -o mnt`**:目前是 `-o dev` 取節點再自己
`mount <dev> /mnt/usb`,繞過了工具內建的 `mkdir -p`、exfat/ntfs `modprobe` 與可寫性驗證。
掛載點不存在、檔案系統模組沒載、或媒體唯讀時只會失敗一行,job log 就沒帶出來。
- **風扇確認提示是無限等待**`wait "Set all configured fan channels to"` 之後才 `sendln "y"`
若該版 `fan-speed-control.sh` 不再詢問(或字串改了),這個 `wait` 會永遠等下去,Script A 停在那裡
且不報錯。加個 `timeout` + 分支會比較保險(記得還原 `timeout = 0`)。
-**TR518 已腳本化** —— `blanton_tr518.sh` V1.0.12026-08-28)。仍與 `blanton_traffic_linespeed`
互斥(兩者都會動 `port cd` loopback 與 L2 學習),檔頭與 help 都標了。剩下三件事:
`stop` 沒把 `l2 learn` / `test mode` 放回去(照 `TR518.txt` 的定義,旗標可開);
**`sleep 120` 的方向還沒驗證** —— 若 `tr 518` 的流量是有時限的(參數含 `runtime=1`
`CheckDataTime=200`),等 120 秒可能量到已經降下來的值,要上機比對 0s / 30s / 120s 三次;
TTL 尚未呼叫它。
- **`blanton_ber.sh``sleep 30``BER_INTERVAL` 是兩處要同步改的數字**:Script C 寫死
`sendln "sleep 30"`,腳本預設 `BER_INTERVAL=30`。改一邊沒改另一邊,report 會讀到還沒累積完
一個 interval 的數字,而且不會有任何警告。
- **`ce0` 的 ingress mirror 尚未上機驗證**`mirror port ce0 Mode=Ingress DestPort=ce0` 這條路是
推論出來的(鏡像複本不受 egress filter 的 source-port knockout 限制),語法取自 DUT 的
`help mirror`,但**還沒證明硬體真的接受 destport == srcport**。驗證方式是隔 10 秒看兩次
`show c | grep POK.ce0`,數字有沒有繼續長 —— 光看 report 的 PASS 分不出來,因為一次性 burst
也是 TX==RX。不通的話備案是 `tr 518 PortList=268`
- **SWB 的 LTC2977 有兩條路可以到,目前工具兩條都在用**:`cb_pmbus_*`CB FPGA F3 → pcimem
`margin_init` / `margin_status` / `margin_set` 走的路;原生 i2c busCh+7 = bus 13/14/15/16
`margin_save blanton` 走的路。後者短很多 —— 不需要通道初始化、不需要 pcimem,
單一 `i2cset` 就到。若原生 bus 在這平台夠穩,整個 `TRANSPORT="swb"` 層其實可以退成
「bus 號不同的 i2c 路徑」,`_swb_*`、probe、semaphore、prescale 那整套都能拿掉。
要動之前得先確認:讀取(`margin_status` 的 LINEAR16 word read)是否也同樣可靠,
以及 kernel driver 有沒有搶著存取(`margin_save blanton``-f` 就是為了繞過這件事)。
- **`settings/` 裡 SWB0 與 SWB1 是兩份相同的資料**:8 個 `.conf` 中,SWB1 的四個除了 `CHIPS[]`
bus 與 `CB_I2C_CH` 之外,net name 與 `VNOM` 與 SWB0 逐欄相同(見 `docs/LTC2980_channel_map.csv`)。
改一邊忘了改另一邊不會有任何警告。可考慮改成「共用 net 定義 + 各自的 transport 覆寫」兩層檔案。
- **Script C 沒有倒出 `mgmt_ping.log`**C 會 `stop` 管理網路監控並印 `ip -s link show`
但沒有 `cat ~/Blanton_Script/log/mgmt_ping.log`,所以整個 soak 期間累積的 per-leg `RESULT`
留在 DUT 上、沒進 master log。Script A 那輪基線有 `cat`
+203 -15
View File
@@ -9,6 +9,23 @@ DUT 端的取數能力由一組 **bash 工具庫** 提供(CB FPGA `pcimem` →
完整架構見 [ARCHITECTURE.md](ARCHITECTURE.md)。 完整架構見 [ARCHITECTURE.md](ARCHITECTURE.md)。
**目前狀態(2026-08-28**Script A 為 **V1.1.2**(A 集中記錄 A/B/C 三支的變更)。涵蓋
PCIe AER / DDR EDAC / PMON+TPM / 電壓 margin9 顆 LTC2980/ SWB loopback 線速流量 /
10G+1G 管理網路 ping / BMC DDR 與 USB / NVMe 健康,壓力層走平台的 `bgctl`
switch unit 數量由 `config.ttl``SWB_UNIT0` / `SWB_UNIT1` 宣告,風扇轉速由 `FAN_SPEED` 指定
(預設 100 = 全速;soak 是熱測試,散熱不該是變因)。
測試 job log 收在 DUT 的 `/host/hw-eval/jobs/`Script C 結束時掛好 USB 再帶時戳複製到 `/mnt/usb/`
100G PRBS`port_prbs_monitor.sh`)已隨包交付,但 **A/B/C 的呼叫仍註解掉**,還在 bring-up。
Script B 的 soak 迴圈每輪印 `bgctl list` + `jobs`,並 `pause 60`V1.0.10 之前完全沒有 pause)。
V1.1.0 新增全機 SerDes BER 掃描(`blanton_ber.sh`**Script C 已接**,依 `SWB_UNIT` 分支)、
TR518 負載(`blanton_tr518.sh`**TTL 尚未接**)、SWB 軌功耗(`TH6_SWB{0,1}_power_readback.sh`),
線速流量也納入 100G `ce0`VLAN 138,靠 ingress mirror 維持循環)。
Script A 開頭會先收掉前一輪殘留(`hw-test-session finish` + `bgctl reset/stop --all`),
`bgctl list` 確認沒有 job 留著佔資源。
NFC tag 讀取由 `nfc_polling.sh` 背景輪詢(A 做基線、B 起、C 停並 report)。
最後一次發布是 **V1.1.1**;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 +34,12 @@ 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 工具鏈)
- **背景工作**:平台的 `bgctl run/list/stop --all/reset`;長時間 ping 用 `systemd-run --unit=` 掛 transient unit
- **管理網路**iproute2`ip link set` / `address replace` / `route replace ... metric`+ `ping -I`
- **風扇**max31790 driver rebind + `fan-speed-control.sh <%>`(轉速讀 `config.ttl``FAN_SPEED`
- **判定資料源**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 +49,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 +70,105 @@ 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
# ── 電壓 margin 寫 NVM(⚠️ 永久,測試中絕不執行)─────
margin_save # 只寫當前 margin_init 載入的那顆(2 個 LTC2977
margin_save all # settings/ 全部 9 顆;跑完自動切回原本載入的那顆
margin_save blanton # Blanton SONiC 捷徑:18 顆全走原生 i2c,不用 margin_init
# 0x15 是 send-byte,不帶 data bytei2cset -f -y <bus> <addr> 0x15
# bus = FPGA F3 通道 + 7 → Ch6=13, Ch7=14, Ch8=15, Ch9=16CB 本來就是 bus 4
# ⚠️ 沒裝滿的機器:改 margin.sh 頂端的 MARGIN_BLANTON_STORE,把沒有的板子註解掉
# ── 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
# ── 管理網路 ping 監控(需先 chmod +x!)──────────────
chmod +x ~/Blanton_Script/*.sh # 佈署後必做一次
~/Blanton_Script/mgmt_ping_monitor.sh fg # 前景試一輪(約 30 秒),確認 IP/對端/網卡
~/Blanton_Script/mgmt_ping_monitor.sh start # 背景,log/mgmt_ping.logstart 會先清空)
~/Blanton_Script/mgmt_ping_monitor.sh status
~/Blanton_Script/mgmt_ping_monitor.sh stop
~/Blanton_Script/mgmt_ping_monitor.sh summary # 只看每段的 RESULT 行
# ⚠️ tail 子命令是 tail -f,會卡住不返回;TTL 裡要倒 log 請用 cat
# bmc_monitor.sh start/stop # free -m + BMC I2C 寫入/讀回圖樣測試,log/bmc_poll.log
# ⚠️ bmc_monitor_ddr.sh 已停用(DDR 壓力改走 bgctl + bmc-manager),檔案仍在
# ── 100G PRBS(目前 TTL 未啟用,手動測用)────────────
bash ~/Blanton_Script/port_prbs_monitor.sh start # 兩個 port 同時
bash ~/Blanton_Script/port_prbs_monitor.sh start a # 只跑 Ethernet513
bash ~/Blanton_Script/port_prbs_monitor.sh start b # 只跑 Ethernet514
bash ~/Blanton_Script/port_prbs_monitor.sh report # PASS/FAIL 統計(沒跑的顯示 SKIP
bash ~/Blanton_Script/port_prbs_monitor.sh stop # 收尾會跑 prbsstat STOp + prbs clear
# ⚠️ PRBS 打起來時 link 會顯示 down,這是正常的 —— 所以 start 刻意不印 port status
# ── 全機 SerDes BERPRBS,期間 link 會顯示 DOWN)────
./Blanton_Script/blanton_ber.sh init # prbs set p=3 + prbs get(檢查每條 lane 鎖定)
./Blanton_Script/blanton_ber.sh start # prbsstat STArt Interval=30
sleep 30 # 至少等一個 interval 再 report
./Blanton_Script/blanton_ber.sh report # 每 lane 一列,BER < 1e-6 才 PASS,最後 summary
./Blanton_Script/blanton_ber.sh stop # prbsstat STOp
./Blanton_Script/blanton_ber.sh clear # prbs clear(會解除 PRBS,要重測得再 init)
# -u 0 / -u 1 單邊;-c 1e-9 換門檻;-F 只列失敗;-f <log> 離線重解
# ⚠️ 不能跟 blanton_traffic_linespeed / blanton_tr518 同時跑(都會接管同一批 port)
# ── TR518 負載 + 量功耗(TTL 尚未接)─────────────────
./Blanton_Script/blanton_tr518.sh start # 兩 unit,跑完 settle 120s
./Blanton_Script/TH6_SWB0_power_readback.sh # 19 條軌 V×I + TOTAL POWER
./Blanton_Script/TH6_SWB1_power_readback.sh
./Blanton_Script/blanton_tr518.sh stop # 只送 port cd lb=none
# ⚠️ stop 不還原 l2 learn / test modeTR_RESTORE_LEARN=1 / _TESTMODE=1 才會)
# ── NFC tag 輪詢 ────────────────────────────────────
~/Blanton_Script/nfc_polling.sh start # 背景,start 會先清空 log
~/Blanton_Script/nfc_polling.sh status # pid、已跑幾輪、最後一筆結果
~/Blanton_Script/nfc_polling.sh report # PASS/FAIL/ERROR 統計,polling 中也能跑
~/Blanton_Script/nfc_polling.sh tail 50 # 最後 50 行(不 follow,不會卡住)
~/Blanton_Script/nfc_polling.sh stop
# log: ~/Blanton_Script/log/nfc_poll.log
# ERROR = 工具沒印出 [PASS]/[FAIL](或逾時),跟「tag 讀不到」分開算
# NFC_INTERVAL_SEC / NFC_TIMEOUT_SEC / NFC_ARGS 可調(要每個子指令都帶)
# ── USB 目標偵測 ────────────────────────────────────
bash ~/Blanton_Script/usb_target.sh -o dev # /dev/sdX1
bash ~/Blanton_Script/usb_target.sh -o mnt # 掛好並回傳掛載點
# ── 背景壓力(平台工具)──────────────────────────────
bgctl run /usr/sbin/memtester 1G 100
bgctl list
bgctl stop --all ; bgctl reset --yes
# ── 通道對照表(開發機上跑,改完 settings/*.conf 一定要重產)──
./tools/gen_channel_map.sh # → docs/LTC2980_channel_map.csv144 列)
./tools/gen_channel_map.sh - # 只印到 stdout
# ⚠️ 需要 gawk(用到 3 參數的 match());mawk 會直接擋下並提示
# ── 各工具的 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 +188,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 +207,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
@@ -91,6 +220,11 @@ Tera Term 連 COM port (115200-8-N-1)
- ⚠️ **I2C 讀一律 Repeated START**(不是 STOP + START),否則 PMBus/SMBus 裝置(VRM、EFUSE)不理。 - ⚠️ **I2C 讀一律 Repeated START**(不是 STOP + START),否則 PMBus/SMBus 裝置(VRM、EFUSE)不理。
- ⚠️ **`margin_save` / `STORE_USER_ALL` 會永久寫 NVM**,斷電保留 → **測試流程中絕不執行** - ⚠️ **`margin_save` / `STORE_USER_ALL` 會永久寫 NVM**,斷電保留 → **測試流程中絕不執行**
以免覆蓋客戶出廠 config。`margin.sh` 的 auto-enable`ON_OFF_CONFIG=0x1a`)只改 RAM,斷電復原。 以免覆蓋客戶出廠 config。`margin.sh` 的 auto-enable`ON_OFF_CONFIG=0x1a`)只改 RAM,斷電復原。
v2.7.0 新增的 **`margin_save all` / `margin_save blanton` 一次寫 18 個 LTC2977**
同一個警告放大 9 倍;另外 `0x15` 現在送 send-byte,不再帶多餘的 `0x00` data byte。
- ⚠️ **SWB 的 LTC2977 有兩條路可以到**`cb_pmbus_*`CB FPGA F3 Ch6/7/8/9 → pcimem)與
原生 i2c bus`i2cset -f -y 13/14/15/16`Ch+7 = bus)。`margin_save blanton` 走後者,
其餘功能走前者。同一顆晶片,**不要同時用兩條路**。
- ⚠️ **改電壓會弄壞 DUT**:套 profile 前確認該軌 OV/UV limit,且 servo DAC 注入電阻有 populate - ⚠️ **改電壓會弄壞 DUT**:套 profile 前確認該軌 OV/UV limit,且 servo DAC 注入電阻有 populate
(本板部分 margin 電阻標 PROTO,沒 populate 的軌電壓不會動,不是腳本壞掉)。 (本板部分 margin 電阻標 PROTO,沒 populate 的軌電壓不會動,不是腳本壞掉)。
- ⚠️ **`ARB_LOST` ≠ 有裝置**`vi2c_scan` 只看 `RX_ACK`,仲裁失敗會被誤報成裝置存在。 - ⚠️ **`ARB_LOST` ≠ 有裝置**`vi2c_scan` 只看 `RX_ACK`,仲裁失敗會被誤報成裝置存在。
@@ -99,12 +233,66 @@ 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>`
- ⚠️ **TTL 的 `timeout` 是全域的**`timeout = N` 之後**每一個** `wait` 都被限制在 N 秒。逾時的 `wait`
直接返回而沒吃到 prompt,下一個指令就在前一個還沒跑完時送出,之後整輪錯開一拍。**設了一定要
配一個 `timeout = 0` 還原**`wait_init.ttl` 60→0Script B 的 cdc_ncm 探測 15→0 在 `:skip_ping`
- ⚠️ **兩張管理網卡同網段會 ARP flux**`mgmt_ping_monitor.sh` 讓 eth0/eth1 同時 up,同一個 /24 下
對端 ARP 可能由任一張卡回應,回包落在沒送封包的那張。`ARP_STRICT=1` 擋掉;判讀時看 `RESULT` 行的
`nic_tx`/`nic_rx`(該卡自己的 packet delta)—— **ping 成功但 `nic_tx` 接近 0 = 封包從另一張卡出去的**
- ⚠️ **PRBS 跑起來時 `show interfaces status` 會顯示 DOWN**,那是 link 離開正常運作模式,不是故障。
`port_prbs_monitor.sh``start` 因此刻意不印 port status(避免測試員誤判),只在 `prbs clear` 之後的報表裡印
- ⚠️ **`ip -s link` 的欄位順序是 `bytes packets errors ...`**,要 packet 數取的是**第 2 欄**不是第 1 欄
- ⚠️ **`mgmt_ping_monitor.sh` / `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`
沒做的話 start 會 Permission denied、後面的 `cat` 撲空,而且**不會報錯**,只會得到一份全空的 log
- ⚠️ **這幾支 monitor 的 `tail` 子命令是 `tail -n 50 -f`,永遠不返回**。TTL 裡絕對不要呼叫它
Script C 曾因此卡死,`18ce7a1` 改成直接 `cat log/*.log`
- ⚠️ **背景 monitor 的 `start` 只等 1 秒就返回**,不是跑完一輪。Script A 要做一輪基線的話,
`start``stop` 中間必須 `pause`(目前是 45 秒,一輪約 30 秒)—— 少了它 log 只會有 START banner
- ⚠️ **風扇那段設完會 `wait "Set all configured fan channels to"` 等確認提示再送 `y`**。該提示沒出現
(版本改了、字串換了)就會永遠卡住且不報錯 —— 現場看起來像 Script A 當掉
- ⚠️ **`show_dmesg.ttl` 結尾會 `dmesg -C` 清空 kernel ring buffer**。這是刻意的(讓 Script C 只看到
soak 期間新產生的訊息),但代表事後在 DUT 上 `dmesg` 撈不到舊訊息 —— 內容只存在 master log 裡
- ⚠️ **`bcmcmd` 的輸出是 CRLF**。`show c` 的計數器後面還跟著 delta,所以「砍掉第一個空白之後」
順便把 CR 砍掉了;但 `prbsstat Ber` 的 BER 是**整行最後一個 token**,CR 直接黏在數字上 ——
數字驗證會失敗(全變 NA),而且印出來時 CR 把游標拉回行首,後面的欄位會**覆蓋掉前面的欄位**。
寫新的 awk 解析器時,第一件事就是 `{ gsub(/\r/, "") }``blanton_ber.sh` V1.0.1 的教訓)
- ⚠️ **`bcmcmd` 的 exit code 兩邊都不能信**BCM shell 拒絕指令時它回 **0**
`blanton_traffic_linespeed.sh` V0.3.1),而 `tr 518` 這種跑很久的指令會讓它 poll 逾時而回
**非 0**,但指令其實已經在跑(`polling socket timeout: Success`)。可靠的作法是**找成功的證據**
——例如 `tr 518` 印出 `TR 518 TEST PARAMETERS` 才算收下(`blanton_tr518.sh` V1.0.1
- ⚠️ **交換晶片不會把封包從進來的那個 port 送回去**source-port knockout)。所以單一 port 的
VLAN 打不出持續流量:`tx 100` 就是一趟來回然後結束。`ce0` 靠 **ingress mirror 打回自己**繞過
這個限制(鏡像複本不經過 egress filter),**且不 stop 就不會自己停**
- ⚠️ **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 解析、報表產生、打包) `LTC2980_channel_map.csv`9 個 `settings/*.conf` 合併成 Board,CONN,Ch,NetName,Vnom 144 列,
- `publish/` — 交付用打包輸出(`Blanton_Script_ABC_v<ver>_<date>/` + zip 查 net 對應哪片哪個 channel 用;**改 `.conf` 後要重跑產生**
- `tools/` — Host / 離線端:`bcm_mibpair_report_V1.1.0.py`drivshell log → per-pair 報表)、
`traffic_loopback_*.txt`bcmcmd 原始指令表,`blanton_traffic_linespeed.sh` 是其 bash 版,
**改 loopback 線路時兩邊要同步**)、`100G_PRBS.txt` / `TR518.txt` / `fan_ctrl.txt`
(上機手貼用的原始指令;PRBS 已有 `.sh`TR518 尚未腳本化)
- `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 協作素材(波形截圖、草稿筆記)
+145
View File
@@ -0,0 +1,145 @@
Board,CONN,Ch,NetName,Vnom
CB,CONN13,CH0,V5P0_ALW,5.0
CB,CONN13,CH1,V3P3_ALW,3.3
CB,CONN13,CH2,PWR_VDD_MISC_ALW,0.75
CB,CONN13,CH3,V1P8_ALW,1.8
CB,CONN13,CH4,PWR_APU_VDDIO_SUS,1.1
CB,CONN13,CH5,PWR_VDD_MISC_RUN,0.75
CB,CONN13,CH6,PWR_APU_VDD_MEM_RUN,0.78
CB,CONN13,CH7,V0P8_PHY2_DVDD,0.8
CB,CONN13,CH8,V0P8_AVDD,0.8
CB,CONN13,CH9,V0P8_PHY,0.8
CB,CONN13,CH10,V1P8_FPGA,1.8
CB,CONN13,CH11,V2P5_FPGA,2.5
CB,CONN13,CH12,V1P1_FPGA,1.1
CB,CONN13,CH13,P5V_STBY,5.0
CB,CONN13,CH14,P3V3_STBY,3.3
CB,CONN13,CH15,V0P8_PHY2_AVDD,0.8
SWB0,CONN13,CH0,P0V75_DVDD_47,0.75
SWB0,CONN13,CH1,P0V75_AVDD_47,0.75
SWB0,CONN13,CH2,P1V5_AVDD_47,1.5
SWB0,CONN13,CH3,P0V9_AVDD_47,0.9
SWB0,CONN13,CH4,P1V8_1,1.8
SWB0,CONN13,CH5,PVDD3_3V_SYNTH_LDO_A3,3.3
SWB0,CONN13,CH6,PVDD3_3V_SYNTH_LDO_B1,3.3
SWB0,CONN13,CH7,P1V8_3,1.8
SWB0,CONN13,CH8,P1V8_DVDDIO_3,1.8
SWB0,CONN13,CH9,P0V75_DVDD_39,0.75
SWB0,CONN13,CH10,P0V75_AVDD_39,0.75
SWB0,CONN13,CH11,P1V5_AVDD_39,1.5
SWB0,CONN13,CH12,P0V9_AVDD_39,0.9
SWB0,CONN13,CH13,PVDD1V5_2,1.5
SWB0,CONN13,CH14,PVDD1V8_DUT,1.8
SWB0,CONN13,CH15,PVDD_MDIO,1.2
SWB0,CONN14,CH0,P0V75_AVDD_7,0.75
SWB0,CONN14,CH1,P1V5_AVDD_7,1.5
SWB0,CONN14,CH2,P0V9_AVDD_7,0.9
SWB0,CONN14,CH3,P1V8_5,1.8
SWB0,CONN14,CH4,P1V8_2,1.8
SWB0,CONN14,CH5,PVDD3_3V_SYNTH_LDO_B2,3.3
SWB0,CONN14,CH6,PVDD3_3V_SYNTH_LDO_A2,3.3
SWB0,CONN14,CH7,PVDD3_3V_SYNTH_LDO_A1,3.3
SWB0,CONN14,CH8,PVDD1V5_TSC,1.5
SWB0,CONN14,CH9,PVDD1V5_ANLG,1.5
SWB0,CONN14,CH10,NC,-
SWB0,CONN14,CH11,NC,-
SWB0,CONN14,CH12,PVDD1V5_1,1.5
SWB0,CONN14,CH13,NC,-
SWB0,CONN14,CH14,P1V8_DVDDIO_1,1.8
SWB0,CONN14,CH15,P0V75_DVDD_7,0.75
SWB0,CONN15,CH0,P1V5_AVDD_55,1.5
SWB0,CONN15,CH1,P0V9_AVDD_55,0.9
SWB0,CONN15,CH2,P0V75_DVDD_63,0.75
SWB0,CONN15,CH3,P0V75_AVDD_63,0.75
SWB0,CONN15,CH4,P1V5_AVDD_63,1.5
SWB0,CONN15,CH5,P0V9_AVDD_63,0.9
SWB0,CONN15,CH6,PVDD3_3V_SYNTH_LDO_B3,3.3
SWB0,CONN15,CH7,PVDD1V5_3,1.5
SWB0,CONN15,CH8,P0V85_STBY,0.85
SWB0,CONN15,CH9,P1V8_STBY,1.8
SWB0,CONN15,CH10,NC,-
SWB0,CONN15,CH11,P3V3_STBY,3.3
SWB0,CONN15,CH12,PVDD0V9_2,0.9
SWB0,CONN15,CH13,P0V75_DVDD_55,0.75
SWB0,CONN15,CH14,P0V75_AVDD_55,0.75
SWB0,CONN15,CH15,P1V8_DVDDIO_4,1.8
SWB0,CONN16,CH0,PVDD1V5_0,1.5
SWB0,CONN16,CH1,P1V8_4,1.8
SWB0,CONN16,CH2,PVDD3_3V_SYNTH_LDO_B5,3.3
SWB0,CONN16,CH3,PVDD3_3V_SYNTH_LDO_A5,3.3
SWB0,CONN16,CH4,PVDD3_3V_SYNTH_LDO_B4,3.3
SWB0,CONN16,CH5,PVDD3_3V_SYNTH_LDO_A4,3.3
SWB0,CONN16,CH6,P3V3,3.3
SWB0,CONN16,CH7,PVDD0_8V_T3,0.8
SWB0,CONN16,CH8,P0V75_DVDD_1,0.75
SWB0,CONN16,CH9,P0V75_AVDD_1,0.75
SWB0,CONN16,CH10,P1V5_AVDD_1,1.5
SWB0,CONN16,CH11,P0V9_AVDD_1,0.9
SWB0,CONN16,CH12,PVDD0V9_0,0.9
SWB0,CONN16,CH13,PVDD0V9_1,0.9
SWB0,CONN16,CH14,PVDD0V9_3,0.9
SWB0,CONN16,CH15,PVDD1V2_T3,1.2
SWB1,CONN13,CH0,P0V75_DVDD_47,0.75
SWB1,CONN13,CH1,P0V75_AVDD_47,0.75
SWB1,CONN13,CH2,P1V5_AVDD_47,1.5
SWB1,CONN13,CH3,P0V9_AVDD_47,0.9
SWB1,CONN13,CH4,P1V8_1,1.8
SWB1,CONN13,CH5,PVDD3_3V_SYNTH_LDO_A3,3.3
SWB1,CONN13,CH6,PVDD3_3V_SYNTH_LDO_B1,3.3
SWB1,CONN13,CH7,P1V8_3,1.8
SWB1,CONN13,CH8,P1V8_DVDDIO_3,1.8
SWB1,CONN13,CH9,P0V75_DVDD_39,0.75
SWB1,CONN13,CH10,P0V75_AVDD_39,0.75
SWB1,CONN13,CH11,P1V5_AVDD_39,1.5
SWB1,CONN13,CH12,P0V9_AVDD_39,0.9
SWB1,CONN13,CH13,PVDD1V5_2,1.5
SWB1,CONN13,CH14,PVDD1V8_DUT,1.8
SWB1,CONN13,CH15,PVDD_MDIO,1.2
SWB1,CONN14,CH0,P0V75_AVDD_7,0.75
SWB1,CONN14,CH1,P1V5_AVDD_7,1.5
SWB1,CONN14,CH2,P0V9_AVDD_7,0.9
SWB1,CONN14,CH3,P1V8_5,1.8
SWB1,CONN14,CH4,P1V8_2,1.8
SWB1,CONN14,CH5,PVDD3_3V_SYNTH_LDO_B2,3.3
SWB1,CONN14,CH6,PVDD3_3V_SYNTH_LDO_A2,3.3
SWB1,CONN14,CH7,PVDD3_3V_SYNTH_LDO_A1,3.3
SWB1,CONN14,CH8,PVDD1V5_TSC,1.5
SWB1,CONN14,CH9,PVDD1V5_ANLG,1.5
SWB1,CONN14,CH10,NC,-
SWB1,CONN14,CH11,NC,-
SWB1,CONN14,CH12,PVDD1V5_1,1.5
SWB1,CONN14,CH13,NC,-
SWB1,CONN14,CH14,P1V8_DVDDIO_1,1.8
SWB1,CONN14,CH15,P0V75_DVDD_7,0.75
SWB1,CONN15,CH0,P1V5_AVDD_55,1.5
SWB1,CONN15,CH1,P0V9_AVDD_55,0.9
SWB1,CONN15,CH2,P0V75_DVDD_63,0.75
SWB1,CONN15,CH3,P0V75_AVDD_63,0.75
SWB1,CONN15,CH4,P1V5_AVDD_63,1.5
SWB1,CONN15,CH5,P0V9_AVDD_63,0.9
SWB1,CONN15,CH6,PVDD3_3V_SYNTH_LDO_B3,3.3
SWB1,CONN15,CH7,PVDD1V5_3,1.5
SWB1,CONN15,CH8,P0V85_STBY,0.85
SWB1,CONN15,CH9,P1V8_STBY,1.8
SWB1,CONN15,CH10,NC,-
SWB1,CONN15,CH11,P3V3_STBY,3.3
SWB1,CONN15,CH12,PVDD0V9_2,0.9
SWB1,CONN15,CH13,P0V75_DVDD_55,0.75
SWB1,CONN15,CH14,P0V75_AVDD_55,0.75
SWB1,CONN15,CH15,P1V8_DVDDIO_4,1.8
SWB1,CONN16,CH0,PVDD1V5_0,1.5
SWB1,CONN16,CH1,P1V8_4,1.8
SWB1,CONN16,CH2,PVDD3_3V_SYNTH_LDO_B5,3.3
SWB1,CONN16,CH3,PVDD3_3V_SYNTH_LDO_A5,3.3
SWB1,CONN16,CH4,PVDD3_3V_SYNTH_LDO_B4,3.3
SWB1,CONN16,CH5,PVDD3_3V_SYNTH_LDO_A4,3.3
SWB1,CONN16,CH6,P3V3,3.3
SWB1,CONN16,CH7,PVDD0_8V_T3,0.8
SWB1,CONN16,CH8,P0V75_DVDD_1,0.75
SWB1,CONN16,CH9,P0V75_AVDD_1,0.75
SWB1,CONN16,CH10,P1V5_AVDD_1,1.5
SWB1,CONN16,CH11,P0V9_AVDD_1,0.9
SWB1,CONN16,CH12,PVDD0V9_0,0.9
SWB1,CONN16,CH13,PVDD0V9_1,0.9
SWB1,CONN16,CH14,PVDD0V9_3,0.9
SWB1,CONN16,CH15,PVDD1V2_T3,1.2
1 Board CONN Ch NetName Vnom
2 CB CONN13 CH0 V5P0_ALW 5.0
3 CB CONN13 CH1 V3P3_ALW 3.3
4 CB CONN13 CH2 PWR_VDD_MISC_ALW 0.75
5 CB CONN13 CH3 V1P8_ALW 1.8
6 CB CONN13 CH4 PWR_APU_VDDIO_SUS 1.1
7 CB CONN13 CH5 PWR_VDD_MISC_RUN 0.75
8 CB CONN13 CH6 PWR_APU_VDD_MEM_RUN 0.78
9 CB CONN13 CH7 V0P8_PHY2_DVDD 0.8
10 CB CONN13 CH8 V0P8_AVDD 0.8
11 CB CONN13 CH9 V0P8_PHY 0.8
12 CB CONN13 CH10 V1P8_FPGA 1.8
13 CB CONN13 CH11 V2P5_FPGA 2.5
14 CB CONN13 CH12 V1P1_FPGA 1.1
15 CB CONN13 CH13 P5V_STBY 5.0
16 CB CONN13 CH14 P3V3_STBY 3.3
17 CB CONN13 CH15 V0P8_PHY2_AVDD 0.8
18 SWB0 CONN13 CH0 P0V75_DVDD_47 0.75
19 SWB0 CONN13 CH1 P0V75_AVDD_47 0.75
20 SWB0 CONN13 CH2 P1V5_AVDD_47 1.5
21 SWB0 CONN13 CH3 P0V9_AVDD_47 0.9
22 SWB0 CONN13 CH4 P1V8_1 1.8
23 SWB0 CONN13 CH5 PVDD3_3V_SYNTH_LDO_A3 3.3
24 SWB0 CONN13 CH6 PVDD3_3V_SYNTH_LDO_B1 3.3
25 SWB0 CONN13 CH7 P1V8_3 1.8
26 SWB0 CONN13 CH8 P1V8_DVDDIO_3 1.8
27 SWB0 CONN13 CH9 P0V75_DVDD_39 0.75
28 SWB0 CONN13 CH10 P0V75_AVDD_39 0.75
29 SWB0 CONN13 CH11 P1V5_AVDD_39 1.5
30 SWB0 CONN13 CH12 P0V9_AVDD_39 0.9
31 SWB0 CONN13 CH13 PVDD1V5_2 1.5
32 SWB0 CONN13 CH14 PVDD1V8_DUT 1.8
33 SWB0 CONN13 CH15 PVDD_MDIO 1.2
34 SWB0 CONN14 CH0 P0V75_AVDD_7 0.75
35 SWB0 CONN14 CH1 P1V5_AVDD_7 1.5
36 SWB0 CONN14 CH2 P0V9_AVDD_7 0.9
37 SWB0 CONN14 CH3 P1V8_5 1.8
38 SWB0 CONN14 CH4 P1V8_2 1.8
39 SWB0 CONN14 CH5 PVDD3_3V_SYNTH_LDO_B2 3.3
40 SWB0 CONN14 CH6 PVDD3_3V_SYNTH_LDO_A2 3.3
41 SWB0 CONN14 CH7 PVDD3_3V_SYNTH_LDO_A1 3.3
42 SWB0 CONN14 CH8 PVDD1V5_TSC 1.5
43 SWB0 CONN14 CH9 PVDD1V5_ANLG 1.5
44 SWB0 CONN14 CH10 NC -
45 SWB0 CONN14 CH11 NC -
46 SWB0 CONN14 CH12 PVDD1V5_1 1.5
47 SWB0 CONN14 CH13 NC -
48 SWB0 CONN14 CH14 P1V8_DVDDIO_1 1.8
49 SWB0 CONN14 CH15 P0V75_DVDD_7 0.75
50 SWB0 CONN15 CH0 P1V5_AVDD_55 1.5
51 SWB0 CONN15 CH1 P0V9_AVDD_55 0.9
52 SWB0 CONN15 CH2 P0V75_DVDD_63 0.75
53 SWB0 CONN15 CH3 P0V75_AVDD_63 0.75
54 SWB0 CONN15 CH4 P1V5_AVDD_63 1.5
55 SWB0 CONN15 CH5 P0V9_AVDD_63 0.9
56 SWB0 CONN15 CH6 PVDD3_3V_SYNTH_LDO_B3 3.3
57 SWB0 CONN15 CH7 PVDD1V5_3 1.5
58 SWB0 CONN15 CH8 P0V85_STBY 0.85
59 SWB0 CONN15 CH9 P1V8_STBY 1.8
60 SWB0 CONN15 CH10 NC -
61 SWB0 CONN15 CH11 P3V3_STBY 3.3
62 SWB0 CONN15 CH12 PVDD0V9_2 0.9
63 SWB0 CONN15 CH13 P0V75_DVDD_55 0.75
64 SWB0 CONN15 CH14 P0V75_AVDD_55 0.75
65 SWB0 CONN15 CH15 P1V8_DVDDIO_4 1.8
66 SWB0 CONN16 CH0 PVDD1V5_0 1.5
67 SWB0 CONN16 CH1 P1V8_4 1.8
68 SWB0 CONN16 CH2 PVDD3_3V_SYNTH_LDO_B5 3.3
69 SWB0 CONN16 CH3 PVDD3_3V_SYNTH_LDO_A5 3.3
70 SWB0 CONN16 CH4 PVDD3_3V_SYNTH_LDO_B4 3.3
71 SWB0 CONN16 CH5 PVDD3_3V_SYNTH_LDO_A4 3.3
72 SWB0 CONN16 CH6 P3V3 3.3
73 SWB0 CONN16 CH7 PVDD0_8V_T3 0.8
74 SWB0 CONN16 CH8 P0V75_DVDD_1 0.75
75 SWB0 CONN16 CH9 P0V75_AVDD_1 0.75
76 SWB0 CONN16 CH10 P1V5_AVDD_1 1.5
77 SWB0 CONN16 CH11 P0V9_AVDD_1 0.9
78 SWB0 CONN16 CH12 PVDD0V9_0 0.9
79 SWB0 CONN16 CH13 PVDD0V9_1 0.9
80 SWB0 CONN16 CH14 PVDD0V9_3 0.9
81 SWB0 CONN16 CH15 PVDD1V2_T3 1.2
82 SWB1 CONN13 CH0 P0V75_DVDD_47 0.75
83 SWB1 CONN13 CH1 P0V75_AVDD_47 0.75
84 SWB1 CONN13 CH2 P1V5_AVDD_47 1.5
85 SWB1 CONN13 CH3 P0V9_AVDD_47 0.9
86 SWB1 CONN13 CH4 P1V8_1 1.8
87 SWB1 CONN13 CH5 PVDD3_3V_SYNTH_LDO_A3 3.3
88 SWB1 CONN13 CH6 PVDD3_3V_SYNTH_LDO_B1 3.3
89 SWB1 CONN13 CH7 P1V8_3 1.8
90 SWB1 CONN13 CH8 P1V8_DVDDIO_3 1.8
91 SWB1 CONN13 CH9 P0V75_DVDD_39 0.75
92 SWB1 CONN13 CH10 P0V75_AVDD_39 0.75
93 SWB1 CONN13 CH11 P1V5_AVDD_39 1.5
94 SWB1 CONN13 CH12 P0V9_AVDD_39 0.9
95 SWB1 CONN13 CH13 PVDD1V5_2 1.5
96 SWB1 CONN13 CH14 PVDD1V8_DUT 1.8
97 SWB1 CONN13 CH15 PVDD_MDIO 1.2
98 SWB1 CONN14 CH0 P0V75_AVDD_7 0.75
99 SWB1 CONN14 CH1 P1V5_AVDD_7 1.5
100 SWB1 CONN14 CH2 P0V9_AVDD_7 0.9
101 SWB1 CONN14 CH3 P1V8_5 1.8
102 SWB1 CONN14 CH4 P1V8_2 1.8
103 SWB1 CONN14 CH5 PVDD3_3V_SYNTH_LDO_B2 3.3
104 SWB1 CONN14 CH6 PVDD3_3V_SYNTH_LDO_A2 3.3
105 SWB1 CONN14 CH7 PVDD3_3V_SYNTH_LDO_A1 3.3
106 SWB1 CONN14 CH8 PVDD1V5_TSC 1.5
107 SWB1 CONN14 CH9 PVDD1V5_ANLG 1.5
108 SWB1 CONN14 CH10 NC -
109 SWB1 CONN14 CH11 NC -
110 SWB1 CONN14 CH12 PVDD1V5_1 1.5
111 SWB1 CONN14 CH13 NC -
112 SWB1 CONN14 CH14 P1V8_DVDDIO_1 1.8
113 SWB1 CONN14 CH15 P0V75_DVDD_7 0.75
114 SWB1 CONN15 CH0 P1V5_AVDD_55 1.5
115 SWB1 CONN15 CH1 P0V9_AVDD_55 0.9
116 SWB1 CONN15 CH2 P0V75_DVDD_63 0.75
117 SWB1 CONN15 CH3 P0V75_AVDD_63 0.75
118 SWB1 CONN15 CH4 P1V5_AVDD_63 1.5
119 SWB1 CONN15 CH5 P0V9_AVDD_63 0.9
120 SWB1 CONN15 CH6 PVDD3_3V_SYNTH_LDO_B3 3.3
121 SWB1 CONN15 CH7 PVDD1V5_3 1.5
122 SWB1 CONN15 CH8 P0V85_STBY 0.85
123 SWB1 CONN15 CH9 P1V8_STBY 1.8
124 SWB1 CONN15 CH10 NC -
125 SWB1 CONN15 CH11 P3V3_STBY 3.3
126 SWB1 CONN15 CH12 PVDD0V9_2 0.9
127 SWB1 CONN15 CH13 P0V75_DVDD_55 0.75
128 SWB1 CONN15 CH14 P0V75_AVDD_55 0.75
129 SWB1 CONN15 CH15 P1V8_DVDDIO_4 1.8
130 SWB1 CONN16 CH0 PVDD1V5_0 1.5
131 SWB1 CONN16 CH1 P1V8_4 1.8
132 SWB1 CONN16 CH2 PVDD3_3V_SYNTH_LDO_B5 3.3
133 SWB1 CONN16 CH3 PVDD3_3V_SYNTH_LDO_A5 3.3
134 SWB1 CONN16 CH4 PVDD3_3V_SYNTH_LDO_B4 3.3
135 SWB1 CONN16 CH5 PVDD3_3V_SYNTH_LDO_A4 3.3
136 SWB1 CONN16 CH6 P3V3 3.3
137 SWB1 CONN16 CH7 PVDD0_8V_T3 0.8
138 SWB1 CONN16 CH8 P0V75_DVDD_1 0.75
139 SWB1 CONN16 CH9 P0V75_AVDD_1 0.75
140 SWB1 CONN16 CH10 P1V5_AVDD_1 1.5
141 SWB1 CONN16 CH11 P0V9_AVDD_1 0.9
142 SWB1 CONN16 CH12 PVDD0V9_0 0.9
143 SWB1 CONN16 CH13 PVDD0V9_1 0.9
144 SWB1 CONN16 CH14 PVDD0V9_3 0.9
145 SWB1 CONN16 CH15 PVDD1V2_T3 1.2
Binary file not shown.
+56
View File
@@ -0,0 +1,56 @@
# v1.0.10 — The soak loop stops spinning flat out, and shows you what is still running
## ✨ New features
**Every soak round now reports the background jobs**
* Script B's monitoring loop prints `bgctl list` (platform jobs) and `jobs` (the login shell's own background jobs) on every pass, right after the platform data. A stress job that dies three hours into an overnight soak now shows up as a shorter list on the next round, instead of only being noticed at the end — or not at all.
* The two lists answer different questions and are both worth having: `bgctl` knows about the platform's job runner, `jobs` knows about anything the macro backgrounded in that shell.
**A single table for all 144 margin channels**
* `docs/LTC2980_channel_map.csv` flattens the nine `settings/*.conf` files into one sheet — `Board,CONN,Ch,NetName,Vnom` — so "which board and channel is `PVDD1V5_TSC` on?" is one search instead of opening nine files.
* `tools/gen_channel_map.sh` regenerates it. **The CSV is output, not source**: edit the `.conf` files and re-run the script, never the other way round.
* Unused channels stay in the table as `NC` / `-` rather than being dropped, because the channel number is also the PMBus page (`ch / 8` picks the chip, `ch % 8` picks the page) — filtering the gaps out would shift everything after them.
**The bench command lists that these scripts came from**
* `tools/100G_PRBS.txt`, `tools/TR518.txt` and `tools/fan_ctrl.txt` join the existing `traffic_loopback_*.txt` set: the raw `bcmcmd` / sysfs sequences, kept in the form you can paste into a console when a script is misbehaving and you want to drive the hardware directly.
## 🐛 Bug fixes
**The soak loop ran as fast as the DUT could answer**
* `while 1` had no pause at all, so each round started the moment the previous one finished. Over a long soak that means constant console traffic and a log full of near-identical samples taken seconds apart. The loop now ends each round with `pause 60`.
* Note the gap this leaves: the Status sheet asks for a 10-minute sampling interval, and 60 seconds is still hard-coded rather than configurable. This makes the loop sane, it does not finish the job.
**A comment promised a cadence the code never had**
* The loop was labelled `Get data every 10mins` while running with no delay whatsoever. It now says what it does.
## 📦 Downloads
| File | Contents |
|---|---|
| `Script_ABC_Blanton_V1.0.10.zip` | The full Tera Term working directory, including `Blanton_Script/` to copy onto the DUT |
**After copying to the DUT:**
```bash
chmod +x ~/Blanton_Script/*.sh # see below
grep -rlU $'\r' ~/Blanton_Script # expect no output
```
`mgmt_ping_monitor.sh` and `port_prbs_monitor.sh` launch their workers through `bash`, so those two run without the execute bit. `bmc_monitor.sh` and `usb_target.sh` still need it, and neither git (mode 100644) nor a Windows/USB copy carries it. Without the `chmod`, `start` fails with `Permission denied` and the later `cat` finds nothing: **the section ends up empty and nothing reports an error.**
## ⚠️ Before you run
* **Declare the switch population.** `SWB_UNIT0` / `SWB_UNIT1` in `config.ttl` say which switch units this DUT has; traffic and the readiness gate both follow them.
* **Set `FAN_SPEED`.** It is applied at the start of every run.
* **Management connectivity is in use, not preserved.** Both management NICs are up and pinging — drive the run from the serial console.
* **PRBS is still not wired into the macros.** Run `port_prbs_monitor.sh` by hand if you want it; the calls in A/B/C remain commented out, unchanged from v1.0.9.
* **TR518 and the loopback traffic test cannot both run.** `tools/TR518.txt` sets `port all lb=mac` and `l2 learn off` across the whole unit, which is the same hardware `blanton_traffic_linespeed` is using. Pick one.
* **Two settings persist to `config_db.json`** and survive a reboot: LLDP is left disabled and the 100G uplinks are left configured. Restore them before the DUT moves on.
## 🔗 Links
* [ARCHITECTURE.md](https://etgit.et-wen.com/etwen/Blanton_TTL_Script/src/branch/main/ARCHITECTURE.md) — test flow, data models, constraints
* [CLAUDE.md](https://etgit.et-wen.com/etwen/Blanton_TTL_Script/src/branch/main/CLAUDE.md) — commands and the bench gotchas
* [LTC2980_channel_map.csv](https://etgit.et-wen.com/etwen/Blanton_TTL_Script/src/branch/main/docs/LTC2980_channel_map.csv) — all 144 margin channels
**Full changelog:** [V1.0.9...V1.0.10](https://etgit.et-wen.com/etwen/Blanton_TTL_Script/compare/V1.0.9...V1.0.10)
+59
View File
@@ -0,0 +1,59 @@
# v1.0.11 — Saving the margin settings to NVM is now one command, and the command is correct
## ✨ New features
**`margin_save blanton` — all 18 LTC2977 in one go**
* On this platform the CB FPGA's I2C channels are also exposed as ordinary Linux i2c buses, so every margin controller on the box — CB and both switch boards — can be reached with plain `i2cset`. `margin_save blanton` walks all 18 of them from one flat list: no `margin_init`, no FPGA channel setup, no `pcimem`.
* The channel-to-bus mapping it relies on (F3 Ch6 → bus 13, Ch7 → 14, Ch8 → 15, Ch9 → 16; CB is bus 4 as before) is now written down in `ARCHITECTURE.md` and the margin README, not just known at the bench.
* **A partly-populated DUT is a normal build.** Comment out the lines you do not have in `MARGIN_BLANTON_STORE` at the top of `margin.sh` rather than letting the tool talk to boards that are not there.
* Missing bus and unresponsive chip are reported as two different failures. `/dev/i2c-16 does not exist` means the FPGA's i2c adapters did not enumerate; `i2cset failed (NACK/busy)` means the board is absent or the address is wrong. Chasing the second when you have the first wastes an afternoon.
**`margin_save all` — every board in `settings/`**
* Runs `margin_init` + `margin_save` across all nine boards, then puts back whichever board you had loaded, so a following `margin_status` still reports the board you were looking at.
* It does not stop at the first failure: a half-saved set is harder to reason about than a fully-attempted one. The summary names the boards that failed.
Both print a per-chip line and a `N saved, M failed` tally, and both return a non-zero exit code when anything failed.
**The fans now default to full speed**
* `FAN_SPEED` in `config.ttl` is 100 instead of 30. A soak is a thermal test of everything *except* the cooling, so the cooling should not be a variable in it — and a DUT that throttles halfway through a run produces results that look like a different fault. Lower it deliberately if quiet operation is what you are measuring.
## 🐛 Bug fixes
**A failed NVM write still reported success**
* `margin_save` discarded the result of every write, so a NACK printed `Change Saved` exactly like a successful store. For the one operation in this tool that is permanent, that is the worst place to stay quiet. Each store is now checked and named if it fails.
**`STORE_USER_ALL` was sent with a data byte it should not have**
* The command went out as `0x15` followed by a dummy `0x00` — an SMBus write-byte, where `STORE_USER_ALL` is a send-byte. The LTC2977 tolerated it, but tolerance is not correctness. It is now `i2cset ... 0x15 c` on the native path and a no-data `cb_pmbus_write` on the FPGA path.
## 📦 Downloads
| File | Contents |
|---|---|
| `Script_ABC_Blanton_V1.0.11.zip` | The full Tera Term working directory, including `Blanton_Script/` to copy onto the DUT |
**After copying to the DUT:**
```bash
chmod +x ~/Blanton_Script/*.sh # see below
grep -rlU $'\r' ~/Blanton_Script # expect no output
```
`mgmt_ping_monitor.sh` and `port_prbs_monitor.sh` launch their workers through `bash`, so those two run without the execute bit. `bmc_monitor.sh` and `usb_target.sh` still need it, and neither git (mode 100644) nor a Windows/USB copy carries it. Without the `chmod`, `start` fails with `Permission denied` and the later `cat` finds nothing: **the section ends up empty and nothing reports an error.**
## ⚠️ Before you run
* **`margin_save` in any form writes NVM permanently.** A power cycle does not undo it. `margin_save all` and `margin_save blanton` do it to all 18 controllers at once. Run `margin_status_all.sh` first and never call any of them during a test run — the macros do not, and should not be made to.
* **The 18 raw commands were verified on the DUT; the wrapper was not.** `margin_save blanton` issues exactly the sequence that was run by hand at the bench, and the packaging was tested against stubbed hardware, but the function itself has not been run on a DUT yet. Watch the per-chip lines the first time.
* **Declare the switch population.** `SWB_UNIT0` / `SWB_UNIT1` in `config.ttl` say which switch units this DUT has; traffic and the readiness gate both follow them.
* **`FAN_SPEED` is now 100.** It is applied at the start of every run, so this build runs the fans flat out unless you change `config.ttl`. Expect the noise.
* **Management connectivity is in use, not preserved.** Both management NICs are up and pinging — drive the run from the serial console.
* **PRBS is still not wired into the macros.** Run `port_prbs_monitor.sh` by hand if you want it; the calls in A/B/C remain commented out.
* **Two settings persist to `config_db.json`** and survive a reboot: LLDP is left disabled and the 100G uplinks are left configured. Restore them before the DUT moves on.
## 🔗 Links
* [ARCHITECTURE.md](https://etgit.et-wen.com/etwen/Blanton_TTL_Script/src/branch/main/ARCHITECTURE.md) — test flow, data models, constraints
* [CLAUDE.md](https://etgit.et-wen.com/etwen/Blanton_TTL_Script/src/branch/main/CLAUDE.md) — commands and the bench gotchas
* [LTC2980_Margin_Script/README.md](https://etgit.et-wen.com/etwen/Blanton_TTL_Script/src/branch/main/src/Script_ABC_Blanton/Blanton_Script/LTC2980_Margin_Script/README.md) — the three `margin_save` variants and what each one sends
**Full changelog:** [V1.0.10...V1.0.11](https://etgit.et-wen.com/etwen/Blanton_TTL_Script/compare/V1.0.10...V1.0.11)
+65
View File
@@ -0,0 +1,65 @@
# v1.0.7 — Management-link ping test, and the fans start where you set them
## ✨ New features
**10G / 1G management port ping test**
* New `mgmt_ping_monitor.sh` exercises both management NICs in one loop: it brings each up with iproute2, waits for the link to settle, then pings that NIC's own target — `eth0``192.168.1.30`, `eth1``192.168.1.31`.
* Runs detached like the other monitors: `start` / `stop` / `status` / `fg` / `summary`. `start` clears the previous log, so one run means one log.
* Every leg writes a single greppable line — `RESULT 10G eth0 -> 192.168.1.30 tx=10 rx=10 loss=0% rtt_avg=1.743ms nic_tx=10 nic_rx=10 PASS` — so a whole soak can be read with `summary` instead of scrolling.
* `nic_tx` / `nic_rx` are that interface's own counter delta across the burst. If a ping passes but they stay near zero, the traffic left on the *other* NIC and the number does not belong to this one.
* Script A runs one round as a baseline; Script B leaves it running through the soak.
**Fan speed is set at the start of every run**
* `FAN_SPEED` in `config.ttl` (default 30) is applied by Script A, so a run no longer inherits whatever the previous test left the fans doing.
* Both MAX31790 controllers are rebound first, which clears a controller left in an odd state.
**Stress load moved onto the platform's own job runner**
* CPU, DDR, SSD and USB stress now go through `bgctl`, replacing the `~/hammer/tools/stress_*.py` scripts. `bgctl list` shows what is running; Script C stops everything with `bgctl stop --all`.
* BMC DDR stress runs through `bmc-manager`, and a new BMC USB test discovers the `cdc_ncm` interface and pings across it for four hours under `systemd-run`.
**More of the DUT on record**
* Script A now captures the boot image, `show version`, firmware status, system EEPROM, SSD health, TPM version and NVMe SMART data before the test starts.
* PCIe AER now sits next to `ras-mc-ctl --summary`, and Script C closes with NVMe health so a disk that degraded during the soak is visible.
* `dmesg` capture uses human-readable timestamps and one combined error pattern, then clears the ring buffer — so Script C's dmesg shows only what the soak produced, not everything since boot.
* Each run opens with `hw-test-session start` and closes with `finish`; Script C copies the job logs to `/mnt/usb/jobs-<date>_<time>` so they leave the DUT with the run they belong to.
## 🐛 Bug fixes
**Script A no longer hangs waiting for ports that are already up**
* The readiness gate required *more than* 216 ports up, but a fully cabled unit reports exactly 216 — 108 loopback pairs times two. Script A sat in the poll loop forever on the very state it was waiting for. It now proceeds at 216.
* The comments in `wait_init.ttl` still described the old "more than" rule; they now match the code, so the off-by-one cannot be reintroduced by reading the wrong line.
**The macro no longer runs ahead of the DUT during the soak**
* Script B set a 15-second wait cap for the `cdc_ncm` probe and never restored it. In Tera Term that cap is global, so it stayed in force for everything after — including the traffic setup, which walks 108 VLANs per switch unit and takes far longer. A timed-out wait returns without the prompt, leaving every later command issued a step early.
**Fan controller is no longer bound twice**
* One of the two MAX31790 controllers received a second `bind` immediately after the first, which could only fail because the driver was already attached.
## 📦 Downloads
| File | Contents |
|---|---|
| `Script_ABC_Blanton_V1.0.7.zip` | The full Tera Term working directory, including `Blanton_Script/` to copy onto the DUT |
**After copying to the DUT:**
```bash
chmod +x ~/Blanton_Script/*.sh # required -- see below
grep -rlU $'\r' ~/Blanton_Script # expect no output
```
The `chmod +x` is not optional. `mgmt_ping_monitor.sh` is executed rather than sourced and re-execs its own path to daemonise, and neither git (mode 100644) nor a Windows/USB copy carries the execute bit. Without it `start` fails with `Permission denied` and the later `cat` finds nothing — **the section ends up empty and nothing reports an error**.
## ⚠️ Before you run
* **Declare the switch population.** `SWB_UNIT0` / `SWB_UNIT1` in `config.ttl` say which switch units this DUT has. Traffic and the readiness gate both follow them: a single unit gets `-u 0` or `-u 1`, and with neither set the traffic stage is skipped instead of failing against hardware that is not there.
* **Both management NICs stay up.** If they share a subnet, keep `ARP_STRICT=1` — otherwise the target's ARP can be answered by either NIC and a reply may arrive on the one that did not send.
* **Management connectivity is exercised, not preserved.** Drive the run from the serial console.
* **Two settings persist to `config_db.json`** and survive a reboot: LLDP is left disabled, and the 100G uplinks are left configured. Restore them before the DUT moves on.
## 🔗 Links
* [ARCHITECTURE.md](https://etgit.et-wen.com/etwen/Blanton_TTL_Script/src/branch/main/ARCHITECTURE.md) — test flow, data models, constraints
* [CLAUDE.md](https://etgit.et-wen.com/etwen/Blanton_TTL_Script/src/branch/main/CLAUDE.md) — commands and the bench gotchas
**Full changelog:** [V1.0.5...V1.0.7](https://etgit.et-wen.com/etwen/Blanton_TTL_Script/compare/V1.0.5...V1.0.7)
+61
View File
@@ -0,0 +1,61 @@
# v1.0.8 — Both management links tested at once, and ARP stops looking like packet loss
## ✨ New features
**Both management NICs are pinged simultaneously and continuously**
* `mgmt_ping_monitor.sh` no longer alternates fixed bursts. `start` configures both NICs, then pings from each at the same time and keeps accumulating until stopped — so a soak-long run is one continuous measurement rather than a series of snapshots.
* `stop` renders a report into the log: the last 20 entries per NIC, each one's ping statistics and PASS/FAIL, then `ip -s link show` for both interfaces.
* Every line is timestamped, and a request that got no reply prints a marker instead of merely being absent — a drop is visible in the tail, not inferred from a gap in the sequence numbers.
* `status` shows both pids and how many replies each NIC has received so far, which is the quick way to see one side is dead without waiting for the report.
**The USB stress target is detected, not assumed**
* New `usb_target.sh` reports an inserted USB device's node, mount point or by-id path. Script B asks it for the node and builds the stress command from the answer, so a stick that enumerates as `sdb` no longer sends a write test at whatever `/dev/sda1` happens to be.
* It excludes whatever disk backs `/` and `/host` — this platform can boot from a USB DOM, which also reports as USB — refuses to guess when several USB disks are present, and when it mounts, verifies the result is actually writable rather than trusting that `mount` succeeded.
**BMC I2C integrity is exercised through the soak**
* `bmc_monitor.sh` is back in the run and now writes two complementary patterns to a BMC scratch register and reads each back. One pattern alone cannot catch a bit stuck the same way it was written; repeating the pair through the soak turns an intermittent I2C fault into something the log records rather than something the tester has to witness.
**Test artefacts leave the DUT with the run**
* Script C copies `bmc_poll.log` and `mgmt_ping.log` into the job directory before it is archived to USB, so the monitors' output travels with the `bgctl` job logs.
* DDR stress now runs continuously instead of stopping after 100 passes, matching the other soak loads.
## 🐛 Bug fixes
**Three FAILs that were not link faults**
* A bench run reported `FAIL=3` with zero NIC errors, zero drops and sub-millisecond replies. Every failure was missing exactly `icmp_seq=1` and nothing else: once the neighbour entry for the target expires, the first echo request is spent resolving ARP and `ping` counts it as loss. A discarded warm-up ping per NIC now absorbs that, which is what lets the loss threshold stay at zero and still mean something. Raising the threshold instead would have hidden genuine single-packet loss.
**Job logs were being cleared before they were collected**
* Script C ran `bgctl reset --yes` while killing processes — before the job directory is copied to USB. Moved to after the archive, so a run's own logs are collected before anything clears them.
**The fan controller was bound twice**
* One of the two MAX31790 controllers received a second `bind` immediately after the first, which could only fail because the driver was already attached.
## 📦 Downloads
| File | Contents |
|---|---|
| `Script_ABC_Blanton_V1.0.8.zip` | The full Tera Term working directory, including `Blanton_Script/` to copy onto the DUT |
**After copying to the DUT:**
```bash
chmod +x ~/Blanton_Script/*.sh # see below
grep -rlU $'\r' ~/Blanton_Script # expect no output
```
`mgmt_ping_monitor.sh` no longer re-execs itself, so `bash mgmt_ping_monitor.sh start` works without the execute bit. `bmc_monitor.sh` and `usb_target.sh` still need it — and neither git (mode 100644) nor a Windows/USB copy carries it. Without the `chmod`, `start` fails with `Permission denied` and the later `cat` finds nothing: **the section ends up empty and nothing reports an error.**
## ⚠️ Before you run
* **Declare the switch population.** `SWB_UNIT0` / `SWB_UNIT1` in `config.ttl` say which switch units this DUT has. Traffic and the readiness gate both follow them; with neither set the traffic stage is skipped rather than failing against hardware that is not there.
* **Set the fan speed you want.** `FAN_SPEED` in `config.ttl` is applied at the start of every run.
* **Management connectivity is in use, not preserved.** Both NICs are up and pinging — drive the run from the serial console.
* **If the two management NICs share a subnet, keep `ARP_STRICT=1`.** Otherwise the target's ARP can be answered by either NIC and a reply may arrive on the one that did not send. Check `nic_tx` / `nic_rx` on the RESULT line: they are that interface's own counter delta.
* **Two settings persist to `config_db.json`** and survive a reboot: LLDP is left disabled and the 100G uplinks are left configured. Restore them before the DUT moves on.
## 🔗 Links
* [ARCHITECTURE.md](https://etgit.et-wen.com/etwen/Blanton_TTL_Script/src/branch/main/ARCHITECTURE.md) — test flow, data models, constraints
* [CLAUDE.md](https://etgit.et-wen.com/etwen/Blanton_TTL_Script/src/branch/main/CLAUDE.md) — commands and the bench gotchas
**Full changelog:** [V1.0.7...V1.0.8](https://etgit.et-wen.com/etwen/Blanton_TTL_Script/compare/V1.0.7...V1.0.8)
+63
View File
@@ -0,0 +1,63 @@
# v1.0.9 — 100G PRBS testing, and the job logs actually reach the USB stick
## ✨ New features
**100G uplink PRBS test**
* New `port_prbs_monitor.sh` drives a PRBS test on both 100G uplinks: it clears low-power mode, arms the pattern, then polls `phy diag <phy> prbs get` in the background until stopped. A poll counts as PASS only when the output says `PRBS OK!`; `prbsstat Ber` is captured alongside every poll for the record.
* `stop` runs `prbsstat STOp` and `prbs clear` on each port before rendering the report, so the test does not leave PRBS armed behind it.
* `report` gives the tally the bench actually wants:
```
PORT POLLS PASS FAIL RESULT
Ethernet513 42 42 0 PASS
Ethernet514 42 40 2 FAIL
```
* **One port at a time is a first-class mode.** `start a` or `start b` runs a single uplink, which is how you separate a genuine port fault from the DUT not coping with two PRBS streams at once. A port that was not run reports `SKIP`, not `FAIL`.
* Every command and its full output — setup, each poll, teardown — goes to that port's raw log.
> ⚠️ The script ships in this build and can be run by hand, but **the calls in Script A, B and C are commented out**: PRBS is still under bring-up on this platform.
**USB target detection**
* `usb_target.sh` reports an inserted USB device's node, mount point or by-id path. It excludes whatever disk backs `/` and `/host` — this platform can boot from a USB DOM, which also reports as USB — refuses to guess when several USB disks are present, and when mounting, verifies the result is writable rather than trusting that `mount` succeeded.
* Script B builds the USB stress command from the detected node instead of a hard-coded `/dev/sda1`.
## 🐛 Bug fixes
**The job logs were being written to a path nobody collected**
* Stress output, the monitors' logs and the USB archive disagreed about where the job directory lives. Everything now uses `/host/hw-eval/jobs/`, so what Script B writes is what Script C copies out.
**Script C copied to a USB that was never mounted**
* The archive step assumed `/mnt/usb` was ready. Script C now finds the device and mounts it first, so a run's logs leave the DUT instead of being written into an empty mount point.
**`show interfaces status` right after starting PRBS reads as a failure**
* With PRBS armed the link is out of normal operation and reports DOWN. `port_prbs_monitor.sh` deliberately does not print the port status at `start` — it appears in the report instead, after `prbs clear`, labelled as the recovered state.
## 📦 Downloads
| File | Contents |
|---|---|
| `Script_ABC_Blanton_V1.0.9.zip` | The full Tera Term working directory, including `Blanton_Script/` to copy onto the DUT |
**After copying to the DUT:**
```bash
chmod +x ~/Blanton_Script/*.sh # see below
grep -rlU $'\r' ~/Blanton_Script # expect no output
```
`mgmt_ping_monitor.sh` and `port_prbs_monitor.sh` launch their workers through `bash`, so those two run without the execute bit. `bmc_monitor.sh` and `usb_target.sh` still need it, and neither git (mode 100644) nor a Windows/USB copy carries it. Without the `chmod`, `start` fails with `Permission denied` and the later `cat` finds nothing: **the section ends up empty and nothing reports an error.**
## ⚠️ Before you run
* **Declare the switch population.** `SWB_UNIT0` / `SWB_UNIT1` in `config.ttl` say which switch units this DUT has; traffic and the readiness gate both follow them.
* **Set `FAN_SPEED`.** It is applied at the start of every run.
* **Management connectivity is in use, not preserved.** Both management NICs are up and pinging — drive the run from the serial console.
* **PRBS is not wired into the macros yet.** Run `port_prbs_monitor.sh` by hand if you want it; uncommenting the calls in A/B/C is not supported in this build.
* **Two settings persist to `config_db.json`** and survive a reboot: LLDP is left disabled and the 100G uplinks are left configured. Restore them before the DUT moves on.
## 🔗 Links
* [ARCHITECTURE.md](https://etgit.et-wen.com/etwen/Blanton_TTL_Script/src/branch/main/ARCHITECTURE.md) — test flow, data models, constraints
* [CLAUDE.md](https://etgit.et-wen.com/etwen/Blanton_TTL_Script/src/branch/main/CLAUDE.md) — commands and the bench gotchas
**Full changelog:** [V1.0.8...V1.0.9](https://etgit.et-wen.com/etwen/Blanton_TTL_Script/compare/V1.0.8...V1.0.9)
+81
View File
@@ -0,0 +1,81 @@
# v1.1.0 — BER, power under load, and a 100G port that actually keeps sending
## ✨ New features
**Whole-switch SerDes BER sweep**
* New `blanton_ber.sh` runs PRBS across every cabled port and reports the bit error rate lane by lane. `init``start` → wait → `report``stop``clear`, with `-u 0` / `-u 1` for a single switch unit.
* **A lane passes only when its BER is below 1e-6.** Equal to the threshold is a FAIL, not a pass. `-c <ber>` sets a different bar.
* `report` ends with a summary across both units, and each unit's row carries its **worst lane** — so a run where everything passes still tells you how much margin there was:
```
Unit LANES PASS FAIL NA WORST BER WORST LANE RESULT
unit 0 432 432 0 0 1.98e-08 203[0] PASS
unit 1 432 432 0 0 6.34e-10 366[1] PASS
```
* `init` also checks that every lane locked. This matters: an unlocked lane still reports a BER later, and it is a meaningless one. Without the check, a dead lane can report a beautiful number and pass.
* A lane whose reading is not a number is `NA` — counted separately from `FAIL`, because "not measured" and "measured and bad" are different problems.
* Script C runs the sweep after the traffic report, following `SWB_UNIT0` / `SWB_UNIT1` the same way the traffic block does.
**TR518 load test, for measuring power under traffic**
* New `blanton_tr518.sh` puts the cd ports in loopback and runs the built-in `tr 518` packet test, then waits `TR_SETTLE_SEC` (120s by default) before handing back — so the power figures you take afterwards are loaded and settled, not caught mid-ramp.
* `-u 0` / `-u 1` for one unit; no flag runs both.
* `stop` sends `port cd lb=none` and nothing else, which is exactly what the bench procedure does. **L2 learning and test mode are left as `start` set them** — the tool says so on every `stop` rather than quietly leaving the box in a state you did not ask about. `TR_RESTORE_LEARN=1` / `TR_RESTORE_TESTMODE=1` if you want a true inverse.
**Per-rail power readback for both switch boards**
* `TH6_SWB0_power_readback.sh` / `TH6_SWB1_power_readback.sh` report V × I and total power for the 19 rails on each board.
## 🐛 Bug fixes
**The power readback measured every rail at a different moment**
* It ran `show platform voltage` and `show platform current` once *per rail* — 38 invocations of a slow CLI, and 38 different instants. Under load the currents move a lot (`TH6_CORE` was seen going from 186 A to 636 A), so the "total" was a sum of readings taken seconds apart. Both tables are now read once, up front. Same output, one moment.
* A rail missing from the sensor table used to contribute a silent `0.000 V / 0.00 W` to the total. It now says so on stderr; the printed report format is unchanged.
**The 100G port stopped sending after one round trip**
* `ce0` has no loopback partner, and a switch never forwards a frame back out the port it arrived on — so a `tx` burst went out, came back, and was dropped. TX 100, RX 100, silence. That measures the link; it does not exercise it.
* `init` now sets an ingress mirror of `ce0` back to itself. The mirrored copy is not egress-filtered, so each packet keeps lapping and the load is set by how much you inject (`-tx`). **`stop` is what ends it** — removing the VLAN membership does not, because the mirrored copy never consulted the VLAN.
**The 100G verdict was being decided the wrong way**
* `ce0` was treated as a cross-unit check. The bench says otherwise: there is one `ce0` per unit and a burst sent on unit 0 alone came back on unit 0. It is now judged within its own unit. The distinction is invisible while everything works and decisive when it does not — read as cross-unit, *both* links dying still leaves `unit0.TX == unit1.RX` (0 == 0) and reports PASS.
**Every BER lane reported `NA`, and the Port column printed `NA` too**
* `bcmcmd` returns CRLF, and the BER is the last token on its line, so the carriage return stayed glued to the number: it failed the numeric check, and printing it sent the cursor back to column 1 so the RESULT column overwrote the start of the row. Both symptoms, one stray `\r`.
## 📦 Downloads
| File | Contents |
|---|---|
| `Script_ABC_Blanton_V1.1.0.zip` | The full Tera Term working directory, including `Blanton_Script/` to copy onto the DUT |
**After copying to the DUT:**
```bash
chmod +x ~/Blanton_Script/*.sh # see below
grep -rlU $'\r' ~/Blanton_Script # expect no output
```
`blanton_ber.sh` is invoked by Script C as `./Blanton_Script/blanton_ber.sh`, so it joins `bmc_monitor.sh` and `usb_target.sh` in needing the execute bit — and neither git nor a Windows/USB copy carries it reliably. Without the `chmod` the BER section fails with `Permission denied` and the log simply has no BER in it.
## ⚠️ Before you run
* **These three tests cannot share the switch.** `blanton_ber.sh`, `blanton_tr518.sh` and `blanton_traffic_linespeed` all take over port loopback and L2 learning. Run one at a time; Script C already orders BER after the traffic report.
* **PRBS takes the links down.** While `blanton_ber.sh` is armed the ports report DOWN and no traffic passes. That is the test working, not a fault. `clear` releases them.
* **`ce0` keeps running until you stop it.** If a run is interrupted between `start` and `stop`, the 100G port stays saturated with nothing in `show interfaces status` to hint at it. `blanton_traffic_linespeed stop` (or `bcmcmd -n <u> -c 'mirror port ce0 Mode=Off'`) ends it.
* **Two numbers must stay in step**: Script C's `sleep 30` and `blanton_ber.sh`'s `BER_INTERVAL`. Change one without the other and `report` reads an interval that has not finished accumulating — with no warning.
* **`FAN_SPEED` is 100.** This build runs the fans flat out unless you change `config.ttl`.
* **Declare the switch population** with `SWB_UNIT0` / `SWB_UNIT1`; traffic, the readiness gate and now the BER sweep all follow it.
* **PRBS on the two 100G uplinks (`port_prbs_monitor.sh`) is still not wired into the macros.** Unchanged from v1.0.9.
## 🔬 Not yet verified on hardware
Three things in this build were reasoned out rather than measured, and each has a one-command check:
* **The `ce0` ingress mirror.** `mirror port ce0 Mode=Ingress DestPort=ce0` should make the port circulate, but the hardware may refuse a mirror destination equal to its source. Check `show c | grep POK.ce0` twice ten seconds apart — the number must climb. The report cannot tell you: a one-shot burst is also `TX == RX` and also says PASS. Fallback is `tr 518 PortList=268`.
* **The 120s settle in `blanton_tr518.sh`.** If `tr 518`'s traffic is time-limited (its parameters include `runtime=1`, `CheckDataTime=200`), waiting 120s could mean measuring after the load has already dropped. Compare a power readback at 0s, 30s and 120s.
* **`test mode nr=no`** in `blanton_tr518.sh` is the derived inverse of the `nr=yes` that `start` sends; it is not part of the bench-verified sequence, and is off by default.
## 🔗 Links
* [ARCHITECTURE.md](https://etgit.et-wen.com/etwen/Blanton_TTL_Script/src/branch/main/ARCHITECTURE.md) — test flow, data models, constraints
* [CLAUDE.md](https://etgit.et-wen.com/etwen/Blanton_TTL_Script/src/branch/main/CLAUDE.md) — commands and the bench gotchas
**Full changelog:** [V1.0.11...V1.1.0](https://etgit.et-wen.com/etwen/Blanton_TTL_Script/compare/V1.0.11...V1.1.0)
+42
View File
@@ -0,0 +1,42 @@
# v1.1.1 — A run no longer inherits the last one's leftovers
## 🐛 Bug fixes
**A previous run's jobs and session survived into the next one**
* Script A now clears the box before it starts: `hw-test-session finish`, `bgctl reset --yes`, `bgctl stop --all`, then `bgctl list` so you can see it came back empty.
* This matters after a run that ended badly — a closed Tera Term window, a DUT reboot mid-soak, a macro stopped by hand. The stress jobs kept running and the session stayed open, so the next run started on a box that was already loaded. Nothing failed; the numbers were just quietly wrong, and the only hint was a `bgctl list` nobody was reading.
* The `bgctl list` at the end is the point of the sequence, not decoration: it is the one line in the log that proves the DUT was idle when the run began.
## 📦 Downloads
| File | Contents |
|---|---|
| `Script_ABC_Blanton_V1.1.1.zip` | The full Tera Term working directory, including `Blanton_Script/` to copy onto the DUT |
**After copying to the DUT:**
```bash
chmod +x ~/Blanton_Script/*.sh # see below
grep -rlU $'\r' ~/Blanton_Script # expect no output
```
`blanton_ber.sh` is invoked by Script C as `./Blanton_Script/blanton_ber.sh`, so it needs the execute bit along with `bmc_monitor.sh` and `usb_target.sh` — and neither git nor a Windows/USB copy carries it reliably. Without the `chmod` the BER section fails with `Permission denied` and the log simply has no BER in it.
## ⚠️ Before you run
* **`hw-test-session finish` at the start will complain if no session is open.** That is expected on a fresh DUT and does not stop the run.
* **These three tests cannot share the switch.** `blanton_ber.sh`, `blanton_tr518.sh` and `blanton_traffic_linespeed` all take over port loopback and L2 learning. Script C already orders BER after the traffic report.
* **PRBS takes the links down** while `blanton_ber.sh` is armed. That is the test working, not a fault.
* **`ce0` keeps running until you stop it.** If a run is interrupted between `start` and `stop`, the 100G port stays saturated with nothing in `show interfaces status` to hint at it.
* **Two numbers must stay in step**: Script C's `sleep 30` and `blanton_ber.sh`'s `BER_INTERVAL`.
* **`FAN_SPEED` is 100** — the fans run flat out unless you change `config.ttl`.
* **Declare the switch population** with `SWB_UNIT0` / `SWB_UNIT1`.
* **PRBS on the two 100G uplinks (`port_prbs_monitor.sh`) is still not wired into the macros.**
* **The three items listed as unverified in v1.1.0 are still unverified**: the `ce0` ingress mirror, the 120s settle in `blanton_tr518.sh`, and `test mode nr=no` on stop. See [v1.1.0](https://etgit.et-wen.com/etwen/Blanton_TTL_Script/releases/tag/V1.1.0) for the one-command check on each.
## 🔗 Links
* [ARCHITECTURE.md](https://etgit.et-wen.com/etwen/Blanton_TTL_Script/src/branch/main/ARCHITECTURE.md) — test flow, data models, constraints
* [CLAUDE.md](https://etgit.et-wen.com/etwen/Blanton_TTL_Script/src/branch/main/CLAUDE.md) — commands and the bench gotchas
**Full changelog:** [V1.1.0...V1.1.1](https://etgit.et-wen.com/etwen/Blanton_TTL_Script/compare/V1.1.0...V1.1.1)
+63
View File
@@ -0,0 +1,63 @@
# v1.1.2 — NFC tag reads are now watched for the whole soak, not sampled once
## ✨ New features
**Background NFC polling**
* New `nfc_polling.sh` runs `blantons_nfc_validate.py` over and over in the background and keeps score. `start` / `stop` / `status` / `tail` / `report` / `clear` / `fg`.
* **`report` works while the poll is still running.** It only reads the log, so it never disturbs the run — and it labels itself `RUNNING` so a mid-soak snapshot is not mistaken for the final answer:
```
state : RUNNING (pid=2240323) -- this is a snapshot, the count is still moving
rounds : 240
PASS : 238
FAIL : 2
ERROR : 0 (tool did not produce a verdict)
RESULT : FAIL
--- tags seen ---
046F7BD2142290 238
```
* **`ERROR` is counted apart from `FAIL`.** A round where the tool printed neither `[PASS]` nor `[FAIL]` — it crashed, the file was missing, it hung past the timeout — did not measure anything. That is a different problem from a tag that would not read, and rolling the two together hides whichever one you are not looking for.
* `report` also lists which NFCID1s were seen and how often, and the last ten failures with their timestamps and reasons.
* `start` clears the log first, so a report always describes the run you just did rather than everything since the DUT was built.
* Script A takes a 30-second NFC baseline, Script B starts the poll for the soak, Script C stops it with the other monitors and reports.
## 🐛 Bug fixes
**The NFC log never reached the USB stick**
* Script C copied `nfc_polling.log`; the file is `nfc_poll.log`. The `cp` failed, nothing checked it, and the run finished looking fine — you would only notice back at the desk with a job archive that had no NFC log in it.
## 📦 Downloads
| File | Contents |
|---|---|
| `Script_ABC_Blanton_V1.1.2.zip` | The full Tera Term working directory, including `Blanton_Script/` to copy onto the DUT |
**After copying to the DUT:**
```bash
chmod +x ~/Blanton_Script/*.sh # see below
grep -rlU $'\r' ~/Blanton_Script # expect no output
```
`blanton_ber.sh` and `nfc_polling.sh` are invoked by the macros through `./` or `~/`, so they need the execute bit along with `bmc_monitor.sh` and `usb_target.sh` — neither git nor a Windows/USB copy carries it reliably. `blantons_nfc_validate.py` does **not** need it: `nfc_polling.sh` calls it through `python3` precisely so one more `chmod` cannot be forgotten.
## ⚠️ Before you run
* **`nfc_polling.sh start` clears the log.** Script B starts a fresh poll for the soak, which wipes Script A's baseline log — the baseline numbers are already in the master console log by then, but the file is gone.
* **NFC log path is `~/Blanton_Script/log/nfc_poll.log`** and it is copied into the job directory by Script C for the USB archive.
* **These three tests cannot share the switch.** `blanton_ber.sh`, `blanton_tr518.sh` and `blanton_traffic_linespeed` all take over port loopback and L2 learning. Script C already orders BER after the traffic report.
* **PRBS takes the links down** while `blanton_ber.sh` is armed. That is the test working, not a fault.
* **`ce0` keeps running until you stop it.** An interrupted run leaves the 100G port saturated with nothing in `show interfaces status` to hint at it.
* **Two numbers must stay in step**: Script C's `sleep 30` and `blanton_ber.sh`'s `BER_INTERVAL`.
* **`FAN_SPEED` is 100** — the fans run flat out unless you change `config.ttl`.
* **Declare the switch population** with `SWB_UNIT0` / `SWB_UNIT1`.
* **PRBS on the two 100G uplinks (`port_prbs_monitor.sh`) is still not wired into the macros.**
* **The three items listed as unverified in v1.1.0 are still unverified**: the `ce0` ingress mirror, the 120s settle in `blanton_tr518.sh`, and `test mode nr=no` on stop. See [v1.1.0](https://etgit.et-wen.com/etwen/Blanton_TTL_Script/releases/tag/V1.1.0) for the one-command check on each.
## 🔗 Links
* [ARCHITECTURE.md](https://etgit.et-wen.com/etwen/Blanton_TTL_Script/src/branch/main/ARCHITECTURE.md) — test flow, data models, constraints
* [CLAUDE.md](https://etgit.et-wen.com/etwen/Blanton_TTL_Script/src/branch/main/CLAUDE.md) — commands and the bench gotchas
**Full changelog:** [V1.1.1...V1.1.2](https://etgit.et-wen.com/etwen/Blanton_TTL_Script/compare/V1.1.1...V1.1.2)
+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
+376 -11
View File
@@ -1,13 +1,166 @@
; ============================================================================= ; =============================================================================
; Script A for Blanton ; Script A for Blanton
; Version : V1.0.2 ; Version : V1.1.2
; Date : 2026-08-17 ; Date : 2026-08-28
; Author : ETWen ; Author : ETWen
; ============================================================================= ; =============================================================================
; Version History: ; Version History:
; V1.0.0 2026-08-13 Initial Version ; V1.0.0 2026-08-13 Initial Version
; V1.0.1 2026-08-14 Add CLEAR PCIE Error ; V1.0.1 2026-08-14 Add CLEAR PCIE Error
; V1.0.2 2026-08-17 utils/setup_pmon.ttl add Leakage Status ; V1.0.2 2026-08-17 utils/setup_pmon.ttl Add Leakage Status
; utils/traffic_loopback_vlan_setting.ttl Created
; utils/traffic_loopback_vlan_remove.ttl Created
; utils/traffic_loopback_start.ttl Created
; ScriptB Remove stress_hhmd, stress_pcie
; Blanton_Script Add blanton_traffic_linespeed.sh
; ScriptB/C Add Traffic Test
; V1.0.3 2026-08-21 utils/wait_init.ttl Created
; ScriptA Add Wait DUT Ready
; V1.0.4 2026-08-21 utils/wait_init.ttl Wait on bcmcmd port-up, count (both units > 216)
; utils/wait_init.ttl Add WT_UNIT0/WT_UNIT1, skip absent units
; ScriptA Add EEPROM info (hpe-eeprom-tlv --bus 4 --addr 0x50)
; ScriptA Remove empty Check History block
; ScriptA/B Disable lldp + config save before traffic (silent MAC)
; ScriptB Add sfputil lpmode off Ethernet513/514
; V1.0.5 2026-08-21 config.ttl Add SWB_UNIT0/SWB_UNIT1
; utils/wait_init.ttl Follow config.ttl SWB_UNIT0/SWB_UNIT1
; ScriptA/B/C Traffic follows SWB_UNIT: -u 0 / -u 1 / skip
; V1.0.6 2026-08-24 utils/show_dmesg.ttl Edited dmesg grep i2c & clear event
; ScriptA HW Test Session
; ScriptC HW Test Session Finish
; ScriptA bmc-first-enroll, bmc version/status
; ScriptB Stress test DDR, SSD, USB, BMC DDR, BMC USB Stress test
; ScriptC BMC USB Stress test result
; ScriptA DUT Info
; ScriptAC 10G/1G MGMT ping
; ScriptA Edited 100G Port Status
; ScriptA, utils/setup_pmon.ttl Add TPM
; ScriptAC NVME Error Info
; ScriptAC Add PCIe Summary
; V1.0.7 2026-08-25 config.ttl Add FAN_SPEED
; ScriptA Add Fan Ctrl (max31790 rebind + fan-speed-control.sh)
; ScriptA Clear /host/hw-eval/current/jobs before the run
; ScriptC Copy stress jobs log to USB, timestamped
; utils/wait_init.ttl WT_INTERVAL 10 -> 60, fix WT_MIN comments
; ScriptC Disable BMC USB journalctl dump
; V1.0.8 2026-08-25 Blanton_Script/bmc_monitor.sh Add BMC I2C write/read-back pattern test
; ScriptB Re-enable bmc_monitor.sh start
; ScriptC Re-enable bmc_monitor.sh stop + cat bmc_poll.log
; ScriptC Copy bmc_poll.log + mgmt_ping.log into jobs/ before the USB archive
; Blanton_Script/usb_target.sh Created - auto-detect USB device node / mount point
; ScriptB USB stress uses the detected node, not a hard-coded /dev/sda1
; ScriptB DDR memtester runs continuously (drop the 100-pass limit)
; ScriptC Move bgctl reset --yes after the USB archive
; Blanton_Script/mgmt_ping_monitor.sh V3.0.0 - both NICs ping simultaneously, accumulating
; + ARP warm-up so a stale neighbour entry is not counted as loss
; + stop renders last-N per NIC, stats, and both ip -s link show
; .gitignore Ignore monitor *.raw / *.pid / *.state
; ScriptA MGMT ping baseline 45s -> 30s, clear the log afterwards
; ScriptB Show mgmt_ping status after start
; ScriptC Show mgmt_ping status before stop, cat the report after
; V1.0.9 2026-08-26 Blanton_Script/port_prbs_monitor.sh Add 100G Port PRBS test
; start [both|a|b] - single port isolates a port fault from
; the DUT not coping with two PRBS streams at once
; !! TTL calls are commented out in A/B/C - still under bring-up
; ScriptA/B/C Job log path /host/hw-eval/current/jobs -> /host/hw-eval/jobs
; ScriptC Mount the USB (usb_target.sh) before copying the job logs
; ScriptC Reorder results: stress logs, then MGMT ping, then 100G status
; V1.0.10 2026-08-27 ScriptB Monitor loop lists the background jobs every round
; + bgctl list - platform jobs
; + jobs - shell jobs of the login shell
; + pause 60 - the loop used to run with no pause at all
; docs/LTC2980_channel_map.csv Channel / net / Vnom map built from settings/*.conf
; tools/gen_channel_map.sh Regenerates that CSV - the CSV is output, not source
; tools/100G_PRBS.txt Bench command references, kept for hand-run debug
; tools/TR518.txt TR518 = built-in packet test (swutil / bcmcmd tr 518)
; tools/fan_ctrl.txt MAX31790 rebind + fan-speed-control.sh
; V1.0.11 2026-08-28 LTC2980_Margin_Script/margin.sh v2.7.0 - margin_save fixes + two new subcommands
; STORE_USER_ALL (0x15) is now a PMBus send-byte, not
; 0x15 followed by a dummy 0x00 data byte. The LTC2977
; tolerated the old form, but it was the wrong protocol
; i2c: i2cset .. 0x15 c swb: cb_pmbus_write <ch> <addr> 0x15
; + margin_save all - every board in settings/ (9 boards,
; 18 chips), then re-init the one loaded beforehand
; + margin_save blanton - Blanton SONiC shortcut: all 18
; LTC2977 straight over the native i2c buses - no
; margin_init, no FPGA channel setup, no pcimem
; F3 Ch6->bus 13, Ch7->14, Ch8->15, Ch9->16
; board list = MARGIN_BLANTON_STORE at the top of margin.sh
; The store result is now checked - a NACK used to still
; print "Change Saved"
; MARGIN_STORE_SETTLE (0.5s) between stores; nothing in this
; tool polls the part's busy bit
; LTC2980_Margin_Script/README.md What each margin_save variant sends, and the
; FPGA-channel -> i2c-bus map
; LTC2980_Margin_Script/Ref/*.md 0x15 trace note corrected to send-byte
; LTC2980_Margin_Script/script/margin_save_all.sh Point at margin_save all; keep it for subsets
; config.ttl FAN_SPEED 30 -> 100 (full speed by default)
; V1.1.0 2026-08-28 Blanton_Script/blanton_ber.sh Add whole-switch SerDes BER test (PRBS via phydiag)
; init / start / report / stop / clear, -u 0|1|all
; report: one row per lane, PASS when BER < 1e-6, then
; a summary across units carrying the worst lane
; init also checks every lane locked - an unlocked lane
; still reports a BER, and it is a meaningless one
; !! PRBS takes the SerDes out of normal operation:
; links show DOWN, no traffic passes while it runs
; Blanton_Script/blanton_tr518.sh Add TR518 packet test - loads the switch so the rails
; can be measured under traffic
; start [-u 0|1] then settles 120s (TR_SETTLE_SEC), so
; the power read afterwards is loaded, not ramping
; stop = port cd lb=none, exactly what TR518.txt does;
; l2 learn / test mode are NOT put back - stop says so
; Blanton_Script/TH6_SWB{0,1}_power_readback.sh Read the sensor tables ONCE, not once per rail
; 38 'show platform' calls -> 2. The old loop sampled
; each rail at a different instant, so the total was a
; sum of readings seconds apart. Output format unchanged
; A rail missing from the table now warns on stderr
; instead of silently counting as 0 W
; Blanton_Script/blanton_traffic_linespeed.sh Add the 100G uplink (ce0, VLAN 138) to the test
; ce0 has no loopback partner and a switch will not send
; a frame back out its ingress port, so a tx burst was
; one round trip. An INGRESS MIRROR of ce0 back to ce0
; keeps it circulating until stop turns the mirror off
; !! stop MUST run - the loop does not end on its own
; report: ce0 is the last row; a summary at the bottom
; carries every unit's PASS / FAIL / NA totals
; ScriptC Run the BER sweep, after the traffic report so the
; two do not fight over the same ports
; Follows SWB_UNIT0/SWB_UNIT1 like the traffic block:
; -u 0 / -u 1 / skip, so a single-unit DUT is not asked
; for counters from a board it does not have
; tools/BER_Test.txt Bench command references for the two new scripts
; tools/100G_Traffic.txt
; tools/TR518.txt Corrected on the bench: port cd (not *:port all),
; testphase=1, and an explicit stop
; V1.1.1 2026-08-28 ScriptA Clear a leftover session before starting a new run
; hw-test-session finish -> bgctl reset --yes ->
; bgctl stop --all -> bgctl list (must come back empty)
; A run that ended badly leaves jobs holding CPU and a
; session open; the next run used to inherit both, and
; the stress numbers were quietly wrong because of it
; V1.1.2 2026-08-28 Blanton_Script/nfc_polling.sh Add background NFC tag polling
; start / stop / status / tail / report / clear / fg
; wraps blantons_nfc_validate.py; start clears the log
; first, so a report covers this run only
; report works WHILE polling - it only reads the log,
; and says RUNNING so a snapshot is not read as final
; Each round appends a machine-readable RESULT line, so
; the tally never depends on the tool's wording
; PASS / FAIL come from the tool's own summary; ERROR
; means it produced neither, which is a different fault
; from a tag that would not read - counted separately
; tail does NOT follow (tail -f never returns, and a
; macro waiting on the prompt would hang there)
; Runs the .py through python3, not ./ - the file is
; mode 100644 and a USB copy carries no execute bit
; Blanton_Script/blantons_nfc_validate.py Vendor NFC/NCI validation tool (unmodified)
; ScriptA NFC baseline: start -> status -> 30s -> stop -> report
; ScriptB Start NFC polling for the soak, then status
; ScriptC Stop NFC polling with the other monitors, report it
; next to the mgmt ping log, and copy nfc_poll.log to
; the job dir for the USB archive
; Fix: that cp said nfc_polling.log - the file is
; nfc_poll.log, so the NFC log never reached the USB
; ============================================================================= ; =============================================================================
include "config.ttl" include "config.ttl"
@@ -42,6 +195,86 @@ gettime timestr
sprintf2 cmd 'sudo date -s "%s %s"' datestr timestr sprintf2 cmd 'sudo date -s "%s %s"' datestr timestr
sendln cmd sendln cmd
;Stress log clear
wait prompt_sonic_root
sendln "rm /host/hw-eval/jobs/*"
; ========== HW Test Session ==========
; Clear session
wait prompt_sonic_root
sendln "hw-test-session finish"
wait prompt_sonic_root
sendln "bgctl reset --yes"
wait prompt_sonic_root
sendln "bgctl stop --all"
wait prompt_sonic_root
sendln "bgctl list"
wait prompt_sonic_root
sendln "hw-test-session start"
wait prompt_sonic_root
sendln "hw-test-session log"
wait prompt_sonic_root
sendln "hw-test-session status"
; ========== FAN SPEED ==========
wait prompt_sonic_root
sendln "echo 18-0020 > /sys/bus/i2c/drivers/max31790/unbind"
wait prompt_sonic_root
sendln "sleep .5"
wait prompt_sonic_root
sendln "echo 18-0020 > /sys/bus/i2c/drivers/max31790/bind"
wait prompt_sonic_root
sendln "echo 25-0020 > /sys/bus/i2c/drivers/max31790/unbind"
wait prompt_sonic_root
sendln "sleep .5"
wait prompt_sonic_root
sendln "echo 25-0020 > /sys/bus/i2c/drivers/max31790/bind"
wait prompt_sonic_root
sprintf2 fan_ctrl_cmd 'fan-speed-control.sh %d' FAN_SPEED
sendln fan_ctrl_cmd
wait "Set all configured fan channels to"
sendln "y"
; ========== Wait DUT Init ==========
include "utils/wait_init.ttl"
; ========== DUT Info ==========
wait prompt_sonic_root
sendln "show boot"
wait prompt_sonic_root
sendln "show version"
wait prompt_sonic_root
sendln "fwutil show status"
wait prompt_sonic_root
sendln "show system-memory"
wait prompt_sonic_root
sendln "show service"
wait prompt_sonic_root
sendln "show platform syseeprom"
wait prompt_sonic_root
sendln "show platform ssdhealth"
wait prompt_sonic_root
sendln "qfx5252-tpm-version"
wait prompt_sonic_root
sendln "/usr/sbin/nvme smart-log -H /dev/nvme0"
wait prompt_sonic_root
sendln "/usr/sbin/smartctl -x /dev/nvme0"
; ========== BMC version ==========
;wait prompt_sonic_root
;sendln "bmc-manager run 'cat /etc/issue'"
wait prompt_sonic_root
sendln "bmc-first-enroll"
wait prompt_sonic_root
sendln "bmc-manager version"
wait prompt_sonic_root
sendln "bmc-manager status"
; ========== 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"
@@ -52,6 +285,8 @@ sendln "source ~/Blanton_Script/blanton_icb_vi2c.sh"
wait prompt_sonic_root wait prompt_sonic_root
sendln "source ~/Blanton_Script/blanton_pwr_data.sh" sendln "source ~/Blanton_Script/blanton_pwr_data.sh"
wait prompt_sonic_root wait prompt_sonic_root
sendln "source ~/Blanton_Script/blanton_traffic_linespeed.sh"
wait prompt_sonic_root
sendln "source ~/Blanton_Script/LTC2980_Margin_Script/margin.sh" sendln "source ~/Blanton_Script/LTC2980_Margin_Script/margin.sh"
; ========== CLEAR PCIE Error ========== ; ========== CLEAR PCIE Error ==========
@@ -63,14 +298,13 @@ 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"
wait prompt_sonic_root
sendln "/usr/sbin/ras-mc-ctl --summary"
; ========== Check PCIE tree + PCIE link status + GET PCIE bandwidth Test ========== ; ========== Check PCIE tree + PCIE link status + GET PCIE bandwidth Test ==========
include "utils/pcie_bus.ttl" include "utils/pcie_bus.ttl"
; ========== Check History ==========
;wait prompt_sonic_root
;sendln "command"
; ========== Check Margin ========== ; ========== Check Margin ==========
if EN_Margin = 1 then if EN_Margin = 1 then
include "utils/show_margin_status.ttl" include "utils/show_margin_status.ttl"
@@ -79,16 +313,147 @@ endif
; ========== TAKE DATA ========== ; ========== TAKE DATA ==========
include "utils/setup_pmon.ttl" include "utils/setup_pmon.ttl"
include "utils/show_dmesg.ttl" include "utils/show_dmesg.ttl"
wait prompt_sonic_root
;include "utils/xxx.ttl" ;include "utils/xxx.ttl"
;wait prompt_sonic_root ;wait prompt_sonic_root
;flushrecv ;flushrecv
; ========== TRAFFIC-SETUP-STAGE ========== ; ========== 10G/1G MGMT Ping Test ==========
;sendln "command" wait prompt_sonic_root
sendln "./Blanton_Script/mgmt_ping_monitor.sh start"
wait prompt_sonic_root
pause 30
sendln "~/Blanton_Script/mgmt_ping_monitor.sh stop"
wait prompt_sonic_root
sendln "cat ~/Blanton_Script/log/mgmt_ping.log"
wait prompt_sonic_root
sendln "~/Blanton_Script/mgmt_ping_monitor.sh clear"
; ========== NFC Test ==========
wait prompt_sonic_root
sendln "~/Blanton_Script/nfc_polling.sh start"
wait prompt_sonic_root
sendln "~/Blanton_Script/nfc_polling.sh status"
wait prompt_sonic_root
sendln "sleep 30"
wait prompt_sonic_root
sendln "~/Blanton_Script/nfc_polling.sh stop"
wait prompt_sonic_root
sendln "~/Blanton_Script/nfc_polling.sh report"
; ========== 100G Port Status ==========
wait prompt_sonic_root
sendln "sudo sfputil lpmode off Ethernet513"
wait prompt_sonic_root
sendln "sudo sfputil lpmode off Ethernet514"
wait prompt_sonic_root
sendln "config interface -n asic0 startup Ethernet513"
wait prompt_sonic_root
sendln "config interface -n asic1 startup Ethernet514"
wait prompt_sonic_root
sendln "config save -y"
wait prompt_sonic_root
sendln "show interfaces status Ethernet513,Ethernet514"
;=============================================================================================================
; ========== 100G PRBS ==========
;wait prompt_sonic_root
;sendln "~/Blanton_Script/port_prbs_monitor.sh start"
;wait prompt_sonic_root
;sendln "sleep 10"
;wait prompt_sonic_root
;sendln "~/Blanton_Script/port_prbs_monitor.sh status"
;wait prompt_sonic_root
;sendln "~/Blanton_Script/port_prbs_monitor.sh stop"
;wait prompt_sonic_root
;sendln "~/Blanton_Script/port_prbs_monitor.sh report"
;wait prompt_sonic_root
;sendln "~/Blanton_Script/port_prbs_monitor.sh clear"
; ========== 100G Port Status ==========
;wait prompt_sonic_root
;sendln "sudo sfputil lpmode off Ethernet513"
;wait prompt_sonic_root
;sendln "sudo sfputil lpmode off Ethernet514"
;wait prompt_sonic_root
;sendln "config interface -n asic0 startup Ethernet513"
;wait prompt_sonic_root
;sendln "config interface -n asic1 startup Ethernet514"
;wait prompt_sonic_root
;sendln "config save -y"
;wait prompt_sonic_root
;sendln "show interfaces status Ethernet513,Ethernet514"
;=============================================================================================================
; ========== First Traffic Test ==========
; Silent MAC
wait prompt_sonic_root
sendln "config feature state lldp disabled"
wait prompt_sonic_root
sendln "config save -y"
if SWB_UNIT0 = 1 && SWB_UNIT1 = 1 then
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"
elseif SWB_UNIT0 = 1 && SWB_UNIT1 = 0 then
wait prompt_sonic_root
sendln "blanton_traffic_linespeed ps -u 0"
wait prompt_sonic_root
sendln "blanton_traffic_linespeed init -u 0"
wait prompt_sonic_root
sendln "blanton_traffic_linespeed clear -u 0"
wait prompt_sonic_root
pause 15
sendln "blanton_traffic_linespeed show -u 0"
wait prompt_sonic_root
sendln "blanton_traffic_linespeed start -u 0"
wait prompt_sonic_root
pause 15
sendln "blanton_traffic_linespeed stop -u 0"
wait prompt_sonic_root
sendln "blanton_traffic_linespeed report -u 0"
elseif SWB_UNIT0 = 0 && SWB_UNIT1 = 1 then
wait prompt_sonic_root
sendln "blanton_traffic_linespeed ps -u 1"
wait prompt_sonic_root
sendln "blanton_traffic_linespeed init -u 1"
wait prompt_sonic_root
sendln "blanton_traffic_linespeed clear -u 1"
wait prompt_sonic_root
pause 15
sendln "blanton_traffic_linespeed show -u 1"
wait prompt_sonic_root
sendln "blanton_traffic_linespeed start -u 1"
wait prompt_sonic_root
pause 15
sendln "blanton_traffic_linespeed stop -u 1"
wait prompt_sonic_root
sendln "blanton_traffic_linespeed report -u 1"
else
endif
; ========== 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'
+138 -28
View File
@@ -1,13 +1,5 @@
; ============================================================================= ; =============================================================================
; Script B for Blanton ; Script B for Blanton
; Version : V1.0.2
; Date : 2026-08-17
; Author : ETWen
; =============================================================================
; Version History:
; V1.0.0 2026-08-13 Initial Version
; V1.0.1 2026-08-14 Add AMD CPU STRESS TEST
; V1.0.2 2026-08-17 utils/setup_pmon.ttl add Leakage Status
; ============================================================================= ; =============================================================================
include "config.ttl" include "config.ttl"
@@ -28,45 +20,163 @@ 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 &"
; ========== DDR MEMORY STRESS TEST ========== ; ========== BMC DDR 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"
wait prompt_sonic_root wait prompt_sonic_root
sendln "python ~/hammer/tools/stress_mem.py &" sendln "bgctl run bmc-manager run '/usr/bin/memtester 64M 1'"
wait prompt_sonic_root
sendln "python ~/hammer/tools/stress_mem.py &"
wait prompt_sonic_root
sendln "python ~/hammer/tools/stress_mem.py &"
wait prompt_sonic_root
sendln "python ~/hammer/tools/stress_mem.py &"
wait prompt_sonic_root
sendln "python ~/hammer/tools/stress_mem.py &"
; ========== I2C, LSB and PCIE read with unlimited loop ========== ; ========== BMC Monitor Test ==========
wait prompt_sonic_root wait prompt_sonic_root
sendln "python ~/hammer/tools/stress_hhmd.py &" sendln "./Blanton_Script/bmc_monitor.sh start"
; ========== DDR MEMORY STRESS TEST ==========
;wait prompt_sonic_root
;sendln "bgctl run /usr/sbin/memtester 1G 100"
wait prompt_sonic_root
sendln "bgctl run /usr/sbin/memtester 1G" ; Continue Execture
; ========== SSD read/write ========== ; ========== SSD read/write ==========
wait prompt_sonic_root wait prompt_sonic_root
sendln "python ~/hammer/tools/stress_ssd.py &" sendln "bgctl run qfx5252-stress-ssd 1G --runtime 3600 --passes 5 --write --force --log /host/hw-eval/jobs/qfx5252-stress-ssd.log"
; ========== PCIe CPU <- PCIe -> ASIC by dumping portStatus ==========
wait prompt_sonic_root
sendln "python ~/hammer/tools/stress_pcie.py &"
; ========== USB read/write ========== ; ========== USB read/write ==========
; ---- Get USB partition node -> usb_dev ----
usb_dev = ''
wait prompt_sonic_root wait prompt_sonic_root
sendln "python ~/hammer/tools/stress_usb.py &" sendln 'D=$(bash ~/Blanton_Script/usb_target.sh -o dev 2>/dev/null); echo "<<D""EV=${D:-NONE}>>"'
waitregex '<<DEV=([^>]*)>>'
usb_dev = groupmatchstr1
wait prompt_sonic_root
sprintf2 cmd "bgctl run qfx5252-stress-usb %s 256M --runtime 60 --passes 1 --write --log /host/hw-eval/jobs/qfx5252-stress-usb.log" usb_dev
sendln cmd
;wait prompt_sonic_root
;sendln "bgctl run qfx5252-stress-usb /dev/sda1 256M --runtime 60 --passes 1 --write --log /host/hw-eval/qfx5252-stress-usb.log"
; ========== SHOW Background job ========== ; ========== SHOW Background job ==========
wait prompt_sonic_root wait prompt_sonic_root
sendln "jobs" sendln "jobs"
wait prompt_sonic_root
sendln "bgctl list"
; ========== Take one round of eye measurement on uplink ports ========== ; ========== BMC USB Test ==========
wait prompt_sonic_root
sendln 'echo "NCMIF=$(ls -d /sys/bus/usb/drivers/cdc_ncm/*/net/* 2>/dev/null | head -n 1 | xargs -r basename)"'
timeout = 15
waitregex 'NCMIF=[A-Za-z0-9_-]+'
if result = 0 then
messagebox 'cdc_ncm interface not found. Ping test skipped.' 'ERROR'
goto skip_ping
endif
strlen matchstr
cut_len = result - 6 ; 'NCMIF=' = 6 chars
strcopy matchstr 7 cut_len ncm_if ; -> ncm_if = "eth2" / "eth3"
; ========== Get data every 10mins ========== sprintf2 cmd 'ip -br link show %s' ncm_if
wait prompt_sonic_root
sendln cmd
wait prompt_sonic_root
sendln 'systemctl stop qfx5252-bmc-usb-net-test.service 2>/dev/null || true'
wait prompt_sonic_root
sendln 'systemctl reset-failed qfx5252-bmc-usb-net-test.service 2>/dev/null || true'
sprintf2 cmd 'systemd-run --unit=qfx5252-bmc-usb-net-test --property=Type=exec /usr/bin/ping -I %s -i 1 -W 2 -w 14400 192.168.200.200' ncm_if
wait prompt_sonic_root
sendln cmd
:skip_ping
; restore the default (no cap): the traffic init below walks 108 VLANs per
; unit and takes far longer than the 15 s set for the NCM probe above.
timeout = 0
; ========== 10G/1G MGMT Ping Test ==========
wait prompt_sonic_root
sendln "./Blanton_Script/mgmt_ping_monitor.sh start"
wait prompt_sonic_root
pause 3
sendln "./Blanton_Script/mgmt_ping_monitor.sh status"
; ========== NFC Test ==========
wait prompt_sonic_root
sendln "~/Blanton_Script/nfc_polling.sh start"
wait prompt_sonic_root
sendln "~/Blanton_Script/nfc_polling.sh status"
; ========== 100G Port ==========
wait prompt_sonic_root
sendln "show interfaces status Ethernet513,Ethernet514"
;=============================================================================================================
; ========== 100G PRBS ==========
;wait prompt_sonic_root
;sendln "~/Blanton_Script/port_prbs_monitor.sh start"
;=============================================================================================================
; ========== Traffic START ==========
; Silent MAC
wait prompt_sonic_root
sendln "config feature state lldp disabled"
wait prompt_sonic_root
sendln "config save -y"
if SWB_UNIT0 = 1 && SWB_UNIT1 = 1 then
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"
elseif SWB_UNIT0 = 1 && SWB_UNIT1 = 0 then
wait prompt_sonic_root
sendln "blanton_traffic_linespeed ps -u 0"
wait prompt_sonic_root
sendln "blanton_traffic_linespeed init -u 0"
wait prompt_sonic_root
sendln "blanton_traffic_linespeed clear -u 0"
wait prompt_sonic_root
pause 15
sendln "blanton_traffic_linespeed show -u 0"
wait prompt_sonic_root
sendln "blanton_traffic_linespeed start -u 0"
elseif SWB_UNIT0 = 0 && SWB_UNIT1 = 1 then
wait prompt_sonic_root
sendln "blanton_traffic_linespeed ps -u 1"
wait prompt_sonic_root
sendln "blanton_traffic_linespeed init -u 1"
wait prompt_sonic_root
sendln "blanton_traffic_linespeed clear -u 1"
wait prompt_sonic_root
pause 15
sendln "blanton_traffic_linespeed show -u 1"
wait prompt_sonic_root
sendln "blanton_traffic_linespeed start -u 1"
else
endif
; ========== Monitor loop: platform data + background jobs (pause 60s per round) ==========
while 1 while 1
include "utils/setup_pmon.ttl" include "utils/setup_pmon.ttl"
wait prompt_sonic_root
sendln "bgctl list"
wait prompt_sonic_root
sendln "jobs"
; ========== Check Margin ========== ; ========== Check Margin ==========
if EN_Margin = 1 then if EN_Margin = 1 then
include "utils/show_margin_status.ttl" include "utils/show_margin_status.ttl"
endif endif
pause 60
endwhile endwhile
+190 -11
View File
@@ -1,13 +1,5 @@
; ============================================================================= ; =============================================================================
; Script C for Blanton ; Script C for Blanton
; Version : V1.0.2
; Date : 2026-08-17
; Author : ETWen
; =============================================================================
; Version History:
; V1.0.0 2026-08-13 Initial Version
; V1.0.1 2026-08-14 Add Check PCIE error counters,CHECK Stress results
; V1.0.2 2026-08-17 utils/setup_pmon.ttl add Leakage Status
; ============================================================================= ; =============================================================================
include "config.ttl" include "config.ttl"
@@ -20,6 +12,39 @@ pause 1
; ========== Kill Process ========== ; ========== Kill Process ==========
include "utils/kill_all_process.ttl" include "utils/kill_all_process.ttl"
; bgctrl all stop
wait prompt_sonic_root
sendln "bgctl stop --all"
wait prompt_sonic_root
sendln "bgctl stop --all"
; BMC USB Test STOP
wait prompt_sonic_root
sendln "systemctl stop qfx5252-bmc-usb-net-test.service"
; 10G/1G MGMT Test STOP
wait prompt_sonic_root
sendln "~/Blanton_Script/mgmt_ping_monitor.sh status"
wait prompt_sonic_root
sendln "~/Blanton_Script/mgmt_ping_monitor.sh stop"
wait prompt_sonic_root
sendln "~/Blanton_Script/nfc_polling.sh stop"
;=============================================================================================================
; 100G PRBS STOP
;wait prompt_sonic_root
;sendln "~/Blanton_Script/port_prbs_monitor.sh stop"
;wait prompt_sonic_root
;sendln "sudo sfputil lpmode off Ethernet513"
;wait prompt_sonic_root
;sendln "sudo sfputil lpmode off Ethernet514"
;wait prompt_sonic_root
;sendln "config interface -n asic0 startup Ethernet513"
;wait prompt_sonic_root
;sendln "config interface -n asic1 startup Ethernet514"
;=============================================================================================================
; ========== TAKE DATA ========== ; ========== TAKE DATA ==========
include "utils/setup_pmon.ttl" include "utils/setup_pmon.ttl"
include "utils/show_dmesg.ttl" include "utils/show_dmesg.ttl"
@@ -33,15 +58,169 @@ 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"
wait prompt_sonic_root
sendln "/usr/sbin/ras-mc-ctl --summary"
; ========== BMC DDR 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"
; ========== BMC Monitor TAKE DATA ==========
wait prompt_sonic_root
sendln "./Blanton_Script/bmc_monitor.sh stop"
wait prompt_sonic_root
sendln "cat ./Blanton_Script/log/bmc_poll.log"
; ========== BMC USB Test result ==========
;wait prompt_sonic_root
;sendln "journalctl -u qfx5252-bmc-usb-net-test.service --no-pager"
; ========== 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"
wait prompt_sonic_root wait prompt_sonic_root
sendln "cat ~/hammer/tools/log.stress_ssd" sendln "cat /host/hw-eval/jobs/qfx5252-stress-ssd.log"
wait prompt_sonic_root
sendln "cat /host/hw-eval/jobs/qfx5252-stress-usb.log"
; ========== 10G/1G MGMT Test result ==========
wait prompt_sonic_root
sendln "cat ~/Blanton_Script/log/mgmt_ping.log"
; ========== NFC Test result ==========
wait prompt_sonic_root
sendln "~/Blanton_Script/nfc_polling.sh report"
; ========== 100G Port ==========
wait prompt_sonic_root
sendln "show interfaces status Ethernet513,Ethernet514"
;=============================================================================================================
;wait prompt_sonic_root
;sendln "cat ~/Blanton_Script/log/port_prbs.log "
;=============================================================================================================
; ========== CHECK Traffic counters ========== ; ========== CHECK Traffic counters ==========
;wait prompt_sonic_root if SWB_UNIT0 = 1 && SWB_UNIT1 = 1 then
;sendln "command" wait prompt_sonic_root
sendln "blanton_traffic_linespeed stop"
wait prompt_sonic_root
sendln "blanton_traffic_linespeed report"
elseif SWB_UNIT0 = 1 && SWB_UNIT1 = 0 then
wait prompt_sonic_root
sendln "blanton_traffic_linespeed stop -u 0"
wait prompt_sonic_root
sendln "blanton_traffic_linespeed report -u 0"
elseif SWB_UNIT0 = 0 && SWB_UNIT1 = 1 then
wait prompt_sonic_root
sendln "blanton_traffic_linespeed stop -u 1"
wait prompt_sonic_root
sendln "blanton_traffic_linespeed report -u 1"
else
endif
; ========== BER ==========
; PRBS takes the SerDes out of normal operation, so this runs AFTER the
; traffic report. sleep 30 matches blanton_ber.sh BER_INTERVAL - reporting
; earlier reads an interval that has not finished accumulating.
if SWB_UNIT0 = 1 && SWB_UNIT1 = 1 then
wait prompt_sonic_root
sendln "./Blanton_Script/blanton_ber.sh init"
wait prompt_sonic_root
sendln "./Blanton_Script/blanton_ber.sh start"
wait prompt_sonic_root
sendln "sleep 30"
wait prompt_sonic_root
sendln "./Blanton_Script/blanton_ber.sh report"
wait prompt_sonic_root
sendln "./Blanton_Script/blanton_ber.sh stop"
wait prompt_sonic_root
sendln "./Blanton_Script/blanton_ber.sh clear"
elseif SWB_UNIT0 = 1 && SWB_UNIT1 = 0 then
wait prompt_sonic_root
sendln "./Blanton_Script/blanton_ber.sh init -u 0"
wait prompt_sonic_root
sendln "./Blanton_Script/blanton_ber.sh start -u 0"
wait prompt_sonic_root
sendln "sleep 30"
wait prompt_sonic_root
sendln "./Blanton_Script/blanton_ber.sh report -u 0"
wait prompt_sonic_root
sendln "./Blanton_Script/blanton_ber.sh stop -u 0"
wait prompt_sonic_root
sendln "./Blanton_Script/blanton_ber.sh clear -u 0"
elseif SWB_UNIT0 = 0 && SWB_UNIT1 = 1 then
wait prompt_sonic_root
sendln "./Blanton_Script/blanton_ber.sh init -u 1"
wait prompt_sonic_root
sendln "./Blanton_Script/blanton_ber.sh start -u 1"
wait prompt_sonic_root
sendln "sleep 30"
wait prompt_sonic_root
sendln "./Blanton_Script/blanton_ber.sh report -u 1"
wait prompt_sonic_root
sendln "./Blanton_Script/blanton_ber.sh stop -u 1"
wait prompt_sonic_root
sendln "./Blanton_Script/blanton_ber.sh clear -u 1"
else
endif
; ========== NVME Err Info ==========
wait prompt_sonic_root
sendln "/usr/sbin/nvme smart-log -H /dev/nvme0"
wait prompt_sonic_root
sendln "/usr/sbin/smartctl -x /dev/nvme0"
wait prompt_sonic_root
sendln "show reboot-cause"
; ========== Show Power on uptime ==========
wait prompt_sonic_root
sendln "show uptime"
; ---- Get USB partition node -> usb_dev ----
usb_dev = ''
wait prompt_sonic_root
sendln 'D=$(bash ~/Blanton_Script/usb_target.sh -o dev 2>/dev/null); echo "<<D""EV=${D:-NONE}>>"'
waitregex '<<DEV=([^>]*)>>'
usb_dev = groupmatchstr1
wait prompt_sonic_root
sendln "sync"
wait prompt_sonic_root
sprintf2 cmd "mount %s /mnt/usb" usb_dev
sendln cmd
; ========== Copy Stress Log to USB ==========
wait prompt_sonic_root
sendln "cp ./Blanton_Script/log/bmc_poll.log /host/hw-eval/jobs/"
wait prompt_sonic_root
sendln "cp ./Blanton_Script/log/mgmt_ping.log /host/hw-eval/jobs/"
wait prompt_sonic_root
sendln "cp ./Blanton_Script/log/nfc_poll.log /host/hw-eval/jobs/"
wait prompt_sonic_root
getdate ts_date "%Y%m%d"
gettime ts_time "%H%M"
sprintf2 usb_dst "/mnt/usb/jobs-%s_%s" ts_date ts_time
sprintf2 cmd "cp -r /host/hw-eval/jobs/ %s" usb_dst
sendln cmd
; ========== HW Test Session ==========
wait prompt_sonic_root
sendln "hw-test-session finish"
wait prompt_sonic_root
sendln "bgctl reset --yes"
messagebox 'GOOD JOB! Test Case DONE' 'teraterm' messagebox 'GOOD JOB! Test Case DONE' 'teraterm'
@@ -14,6 +14,8 @@
| `margin_operation <ch> <op>` | 只切 OPERATION register | | `margin_operation <ch> <op>` | 只切 OPERATION register |
| `margin_apply_profile <file>` | 批次套用 profile(套用在當前載入的 LTC2980 | | `margin_apply_profile <file>` | 批次套用 profile(套用在當前載入的 LTC2980 |
| `margin_save` | 寫入 NVM(當前 LTC2980 的兩個 LTC2977 都寫) | | `margin_save` | 寫入 NVM(當前 LTC2980 的兩個 LTC2977 都寫) |
| `margin_save all` | 對 `settings/` 底下**全部** LTC2980 寫入 NVM,結束後回到原本載入的那顆 |
| `margin_save blanton` | **Blanton SONiC 專用**18 顆 LTC2977 全部走原生 i2c bus 寫入,不需要 `margin_init` |
| `margin_debug [on\|off]` | 開關 debug 模式(不帶參數則 toggle | | `margin_debug [on\|off]` | 開關 debug 模式(不帶參數則 toggle |
| `margin_log [on\|off]` | 開關 log 記錄(不帶參數則 toggle,預設開啟) | | `margin_log [on\|off]` | 開關 log 記錄(不帶參數則 toggle,預設開啟) |
@@ -79,7 +81,9 @@ margin_init Blanton_SWB1_CONN16
margin_apply_profile profiles/comboA.conf margin_apply_profile profiles/comboA.conf
# 儲存到 NVM(斷電後保留) # 儲存到 NVM(斷電後保留)
margin_save margin_save # 只寫當前載入的那顆
margin_save all # settings/ 底下 9 顆全部寫,最後自動切回原本那顆
margin_save blanton # Blanton SONiC 專用捷徑,18 顆一次寫完(見下)
``` ```
## 一次掃所有 LTC2980 ## 一次掃所有 LTC2980
@@ -131,16 +135,87 @@ PROFILES=(
## 一次把所有 LTC2980 寫入 NVM ## 一次把所有 LTC2980 寫入 NVM
`script/margin_save_all.sh``BOARDS` 列出的每顆 LTC2980 跑 `margin_save`
> ⚠️ `margin_save` 會把當前 margin / OV / UV 設定**永久寫入 NVM** > ⚠️ `margin_save` 會把當前 margin / OV / UV 設定**永久寫入 NVM**
> 斷電後保留。執行前先用 `margin_status_all.sh` 確認狀態正確。 > 斷電後保留。執行前先用 `margin_status_all.sh` 確認狀態正確。
**全部 9 顆**直接用內建的:
```bash
margin_save all
```
`settings/*.conf` 的檔名順序跑,中途某顆失敗**不會停**(半套完成的狀態比全部試完更難查),
最後印出「幾顆成功 / 哪幾顆失敗」的總結,並把 `margin_init` 切回你呼叫前載入的那顆。
**只寫其中幾顆**才需要 `script/margin_save_all.sh`,編 script 頂端的 `BOARDS` 陣列:
```bash ```bash
source script/margin_save_all.sh source script/margin_save_all.sh
``` ```
要改寫哪幾顆,編 script 頂端的 `BOARDS` 陣列即可(同前述)。 ### 送出去的是什麼
`margin_save` 對每顆 LTC2977 送一次 `STORE_USER_ALL`PMBus command `0x15`),
**不分 page**(這是整顆晶片的命令),也**不帶 data byte** —— 它是 SMBus send-byte
```bash
i2cset -y 4 0x5C 0x15 c # CB CONN13(原生 bus 4
cb_pmbus_write 8 0x5C 0x15 # SWB0 CONN13CB FPGA F3 Ch8
```
> v2.7.0 之前送的是 `0x15 0x00`write-byte)。LTC2977 在 CB 上吃得下去,但那是零件寬容,
> 不是規範。同時 v2.7.0 起會檢查回傳值 —— 以前 NACK 了還是印 `Change Saved`。
每顆之間會 `sleep $MARGIN_STORE_SETTLE`(預設 0.5 秒)。這不是量測值,是保守的保護:
NVM 寫入需要時間,而這支工具**沒有**去 poll 零件的 busy bit。`margin_save all` 連續送 18 次,
這個間隔就是唯一的緩衝。要關掉設成空字串即可。
## `margin_save blanton` — Blanton SONiC 專用
Blanton 這台把 CB FPGA F3 的 I2C 通道**同時掛成了原生 Linux i2c adapter**
所以 SWB 上的 LTC2977 不必走 `cb_pmbus_*` / pcimem,用 `i2cset` 直接打得到:
| FPGA F3 通道 | Linux bus | 板 / 接頭 |
|---|---|---|
| Ch6 | 13 | SWB0 CONN14 / CONN16 |
| Ch8 | 15 | SWB0 CONN13 / CONN15 |
| Ch7 | 14 | SWB1 CONN14 / CONN16 |
| Ch9 | 16 | SWB1 CONN13 / CONN15 |
| —(原生) | 4 | CB CONN13 |
因此 `margin_save blanton` 就是一張平表跑完 18 顆,**不需要 `margin_init`**、
不做 FPGA 通道初始化、不做 probe,也不會動到你當前載入的那顆板子:
```bash
margin_save blanton
```
```
CB CONN13 bus 4 0x5C : saved
SWB0 CONN13 bus 15 0x5C : saved
...
margin_save blanton: 18 saved, 0 failed
```
送出去的就是這 18 行(2026-08-27 上機驗證):
```bash
i2cset -f -y 4 0x5C 0x15 ; i2cset -f -y 4 0x5E 0x15 # CB CONN13
i2cset -f -y 15 0x5C 0x15 ; i2cset -f -y 15 0x5E 0x15 # SWB0 CONN13
i2cset -f -y 13 0x5C 0x15 ; i2cset -f -y 13 0x5E 0x15 # SWB0 CONN14
i2cset -f -y 15 0x62 0x15 ; i2cset -f -y 15 0x64 0x15 # SWB0 CONN15
i2cset -f -y 13 0x62 0x15 ; i2cset -f -y 13 0x64 0x15 # SWB0 CONN16
i2cset -f -y 16 0x5C 0x15 ; i2cset -f -y 16 0x5E 0x15 # SWB1 CONN13
i2cset -f -y 14 0x5C 0x15 ; i2cset -f -y 14 0x5E 0x15 # SWB1 CONN14
i2cset -f -y 16 0x62 0x15 ; i2cset -f -y 16 0x64 0x15 # SWB1 CONN15
i2cset -f -y 14 0x62 0x15 ; i2cset -f -y 14 0x64 0x15 # SWB1 CONN16
```
`-f` 是因為這些 bus 上的 LTC2977 可能已被 kernel driver 認走;上機驗證過的指令帶 `-f`,就照原樣保留。
**這台沒裝滿的話**,把 `margin.sh` 頂端 `MARGIN_BLANTON_STORE` 陣列裡對應的行註解掉即可,
不要讓它去打不存在的板子。bus 節點不存在(`/dev/i2c-N` 沒有)與晶片不回應(NACK
會分別報不同訊息 —— 前者代表 FPGA 的 i2c adapter 根本沒 enumerate,後者才是板子的問題。
## Operation 對應 ## Operation 對應
@@ -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,38 @@
# ============================================================================= # =============================================================================
# Version Control # Version Control
# ----------------------------------------------------------------------------- # -----------------------------------------------------------------------------
# v2.7.0 margin_save fixes and batching:
# * STORE_USER_ALL (0x15) is now issued as an SMBus send-byte instead
# of a write-byte with a dummy 0x00 data byte. The LTC2977 accepted
# the old form on CB, but it was the wrong protocol.
# i2c path: i2cset ... 0x15 c swb path: cb_pmbus_write ch addr 0x15
# * margin_save all -- STORE_USER_ALL on every board in settings/,
# then re-init whichever board was loaded beforehand. Does not stop
# on the first failure; prints a per-board summary at the end.
# * The store result is now checked. It used to be discarded, so a
# NACK still printed "Change Saved".
# * MARGIN_STORE_SETTLE (default 0.5s) between stores - nothing polls
# the part's busy bit, and `all` issues 18 of them in a row.
# * margin_save blanton -- Blanton SONiC shortcut. On this platform the
# CB FPGA F3 I2C channels also appear as native Linux i2c adapters
# (Ch6->bus 13, Ch7->14, Ch8->15, Ch9->16), so all 18 LTC2977 can be
# stored with plain i2cset: no margin_init, no FPGA channel setup,
# no pcimem. Bench-verified on the DUT 2026-08-27. The board list is
# MARGIN_BLANTON_STORE at the top of this file.
# 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 +87,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)
@@ -70,6 +133,45 @@ ONOFF_ENABLE_VALUE="0x1a" # controlled_on=1, use_pmbus=1, use_control=0, b1=1
# margin_status after all channels are set. Bump this if you want margin_set's own # margin_status after all channels are set. Bump this if you want margin_set's own
# read-back to reflect the settled value. # read-back to reflect the settled value.
MARGIN_SETTLE="0.3" MARGIN_SETTLE="0.3"
# Pause after each STORE_USER_ALL. The LTC2977 is busy committing NVM once the
# command lands and nothing here polls a busy bit, so this is a plain guard
# rather than a measured value - it matters most for `margin_save all`, which
# fires 18 stores back to back. Set to "" to disable.
MARGIN_STORE_SETTLE="${MARGIN_STORE_SETTLE:-0.5}"
# --- `margin_save blanton`: the bench-verified Blanton SONiC store list -------
# On this platform the CB FPGA F3 I2C channels are ALSO exposed as native Linux
# i2c adapters, so every LTC2977 - CB and SWB alike - can be reached with plain
# i2cset. That path needs no FPGA channel setup, no pcimem, and no margin_init:
# one flat list, 18 commands, done. Verified on the DUT 2026-08-27.
#
# FPGA F3 channel -> Linux bus: Ch6->13 Ch7->14 Ch8->15 Ch9->16
#
# Entry format: "bus:addr:label". Comment out the lines for boards this DUT does
# not have rather than letting them fail - a single-SWB unit is a normal build.
MARGIN_BLANTON_STORE=(
"4:0x5C:CB CONN13"
"4:0x5E:CB CONN13"
"15:0x5C:SWB0 CONN13"
"15:0x5E:SWB0 CONN13"
"13:0x5C:SWB0 CONN14"
"13:0x5E:SWB0 CONN14"
"15:0x62:SWB0 CONN15"
"15:0x64:SWB0 CONN15"
"13:0x62:SWB0 CONN16"
"13:0x64:SWB0 CONN16"
"16:0x5C:SWB1 CONN13"
"16:0x5E:SWB1 CONN13"
"14:0x5C:SWB1 CONN14"
"14:0x5E:SWB1 CONN14"
"16:0x62:SWB1 CONN15"
"16:0x64:SWB1 CONN15"
"14:0x62:SWB1 CONN16"
"14:0x64:SWB1 CONN16"
)
# -f because the LTC2977s may be claimed by a kernel driver on these buses; the
# command that was verified on the bench carries it, so it stays.
MARGIN_BLANTON_I2CSET_FLAGS="${MARGIN_BLANTON_I2CSET_FLAGS:--f -y}"
_BUS="" _BUS=""
_ADDR="" _ADDR=""
_PRE_SCOPE="" # set while inside a PRE/POST wrapped operation; prevents nested re-entry _PRE_SCOPE="" # set while inside a PRE/POST wrapped operation; prevents nested re-entry
@@ -131,6 +233,247 @@ 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_send <cmd> -> cb_pmbus_write with NO data byte (SMBus send-byte).
# cb_pmbus_write routes a no-data call to _cbi2c_xfer_cmd_only: START, slave+W,
# command byte, STOP. That is what STORE_USER_ALL expects.
_swb_send() {
local reg=$1
[ "$DEBUG_MODE" = "1" ] && echo -e "\033[90m[DEBG] cb_pmbus_write $CB_I2C_CH $_ADDR $reg (send-byte)\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 (send-byte) failed (NACK/timeout)" >&2
_log_detail "ERRO cb_pmbus_write ch=$CB_I2C_CH $_ADDR $reg send-byte"
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 +493,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 +562,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 +573,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,24 +584,31 @@ _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"
i2cset -y "$_BUS" "$_ADDR" "$1" "$2" b >/dev/null 2>&1 i2cset -y "$_BUS" "$_ADDR" "$1" "$2" b >/dev/null 2>&1
} }
# Send a PMBus command with NO data byte (SMBus send-byte).
# STORE_USER_ALL (0x15) and its relatives are send-byte commands: the command
# byte alone triggers them. Appending a dummy 0x00 makes it a write-byte, which
# is a different SMBus protocol - the LTC2977 happened to accept it, but that is
# tolerance, not correctness. i2cset's "c" mode is the send-byte form.
_i2c_send_byte() {
if [ "$TRANSPORT" = "swb" ]; then
_swb_send "$1"
return
fi
[ "$DEBUG_MODE" = "1" ] && echo -e "\033[90m[DEBG] i2cset -y $_BUS $_ADDR $1 c\033[0m"
i2cset -y "$_BUS" "$_ADDR" "$1" c >/dev/null 2>&1
}
# 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
@@ -469,20 +815,171 @@ margin_apply_profile() {
} }
# --- Save to NVM --- # --- Save to NVM ---
# margin_save -- STORE_USER_ALL on the currently initialised board
# margin_save all -- ... on every board in settings/, then re-init the one
# that was loaded when you called it
# margin_save blanton -- Blanton SONiC shortcut: all 18 LTC2977 straight over
# the native i2c buses, no margin_init needed
#
# ⚠️ This is the only permanent write in the tool: it commits the current
# margin / OV / UV registers to NVM, and a power cycle will NOT undo it.
# Run margin_status first and never call it during a test run.
margin_save() { margin_save() {
if [ "${1:-}" = "all" ]; then
_margin_save_every_board
return
fi
if [ "${1:-}" = "blanton" ]; then
_margin_save_blanton
return
fi
local _own=0 local _own=0
_pre_enter_own && _own=1 _pre_enter_own && _own=1
local chip fails=0
for chip in "${CHIPS[@]}"; do for chip in "${CHIPS[@]}"; do
IFS=':' read -r _BUS _ADDR <<< "$chip" IFS=':' read -r _BUS _ADDR <<< "$chip"
_i2c_write_byte 0x15 0x00 # STORE_USER_ALL is chip-wide (all 8 pages), so no _open_page here.
if _i2c_send_byte 0x15; then
_log_detail "STORE_USER_ALL $PART_NUMBER $_ADDR ok"
else
fails=$(( fails + 1 ))
echo -e "[\033[31mERRO\033[0m] STORE_USER_ALL failed on $_ADDR" >&2
_log_detail "ERRO STORE_USER_ALL $PART_NUMBER $_ADDR"
fi
# The part is busy writing NVM after this; nothing here polls a busy bit,
# so leave a gap before touching it again. See MARGIN_STORE_SETTLE.
[ -n "$MARGIN_STORE_SETTLE" ] && sleep "$MARGIN_STORE_SETTLE"
done done
echo "Change Saved (${#CHIPS[@]} chips)"
_log_label "margin_save $PART_NUMBER (${#CHIPS[@]} chips)" if [ "$fails" -eq 0 ]; then
echo "Change Saved (${#CHIPS[@]} chips)"
else
echo -e "[\033[31mERRO\033[0m] $fails of ${#CHIPS[@]} chips did NOT save"
fi
_log_label "margin_save $PART_NUMBER (${#CHIPS[@]} chips, $fails failed)"
[ "$_own" = "1" ] && _pre_exit [ "$_own" = "1" ] && _pre_exit
return $(( fails > 0 ))
} }
# margin_save all -- every .conf in settings/, in filename order.
# Deliberately does NOT stop on the first failure: a half-saved set is worse to
# diagnose than a fully-attempted one, and the summary tells you which boards
# to redo. margin_init is re-run per board because CHIPS / TRANSPORT /
# CB_I2C_CH are global state.
_margin_save_every_board() {
local files=( "$SETTINGS_DIR"/*.conf )
if [ ! -e "${files[0]}" ]; then
echo -e "[\033[31mERRO\033[0m] no .conf found in $SETTINGS_DIR"
return 1
fi
local restore="$PART_NUMBER" # empty if margin_init was never run
local board f ok=0 bad=0 failed=""
echo "-------------"
echo "margin_save all -- ${#files[@]} boards, permanent NVM write"
echo "-------------"
_log_label "margin_save all (${#files[@]} boards)"
for f in "${files[@]}"; do
board=$(basename "$f" .conf)
echo ""
if ! margin_init "$board"; then
bad=$(( bad + 1 )); failed="$failed $board(init)"
continue
fi
if margin_save; then
ok=$(( ok + 1 ))
else
bad=$(( bad + 1 )); failed="$failed $board"
fi
done
echo ""
echo "-------------"
printf "margin_save all: %d saved, %d failed\n" "$ok" "$bad"
[ "$bad" -gt 0 ] && echo -e "[\033[31mERRO\033[0m] failed:$failed"
echo "-------------"
_log_label "margin_save all done: $ok ok, $bad failed$failed"
# Put back whatever board the caller had loaded, so a following
# margin_status does not silently report the last board in the list.
if [ -n "$restore" ] && [ "$restore" != "$board" ]; then
echo ""
echo -e "[\033[34mINFO\033[0m] restoring previous board: $restore"
margin_init "$restore" >/dev/null
fi
return $(( bad > 0 ))
}
# margin_save blanton -- Blanton SONiC only. Walks MARGIN_BLANTON_STORE and
# sends STORE_USER_ALL to each LTC2977 over the native i2c buses.
#
# Deliberately does NOT use margin_init / CHIPS / TRANSPORT: the whole point is
# that on this platform every LTC2977 is reachable with plain i2cset, so the
# FPGA channel setup and the per-board probe are not needed. That also means it
# does not touch the loaded board - PART_NUMBER is the same afterwards.
_margin_save_blanton() {
local n=${#MARGIN_BLANTON_STORE[@]}
if [ "$n" -eq 0 ]; then
echo -e "[\033[31mERRO\033[0m] MARGIN_BLANTON_STORE is empty"
return 1
fi
echo "-------------"
echo "margin_save blanton -- $n LTC2977, permanent NVM write"
echo "-------------"
_log_label "margin_save blanton ($n chips)"
local entry bus addr label ok=0 bad=0 failed=""
for entry in "${MARGIN_BLANTON_STORE[@]}"; do
IFS=':' read -r bus addr label <<< "$entry"
# "no such bus" and "chip did not answer" are different faults: the first
# means the FPGA i2c adapters did not enumerate, the second means the
# board is absent or the address is wrong. Do not blur them into one.
if [ ! -e "/dev/i2c-$bus" ]; then
echo -e "[\033[31mERRO\033[0m] $label bus $bus addr $addr : /dev/i2c-$bus does not exist"
_log_detail "ERRO STORE_USER_ALL $label bus=$bus addr=$addr no-such-bus"
bad=$(( bad + 1 )); failed="$failed [$label $addr]"
continue
fi
[ "$DEBUG_MODE" = "1" ] && echo -e "\033[90m[DEBG] i2cset $MARGIN_BLANTON_I2CSET_FLAGS $bus $addr 0x15\033[0m"
if i2cset $MARGIN_BLANTON_I2CSET_FLAGS "$bus" "$addr" 0x15 >/dev/null 2>&1; then
printf " %-12s bus %-2s %s : saved\n" "$label" "$bus" "$addr"
ok=$(( ok + 1 ))
_log_detail "STORE_USER_ALL $label bus=$bus addr=$addr ok"
else
echo -e "[\033[31mERRO\033[0m] $label bus $bus addr $addr : i2cset failed (NACK/busy)"
_log_detail "ERRO STORE_USER_ALL $label bus=$bus addr=$addr"
bad=$(( bad + 1 )); failed="$failed [$label $addr]"
fi
# No busy-bit poll anywhere in this tool; this gap is the only buffer.
[ -n "$MARGIN_STORE_SETTLE" ] && sleep "$MARGIN_STORE_SETTLE"
done
echo "-------------"
printf "margin_save blanton: %d saved, %d failed\n" "$ok" "$bad"
[ "$bad" -gt 0 ] && echo -e "[\033[31mERRO\033[0m] failed:$failed"
echo "-------------"
_log_label "margin_save blanton done: $ok ok, $bad failed$failed"
return $(( bad > 0 ))
}
# --- Tab completion for margin_save ---
_margin_save_completions() {
[ "$COMP_CWORD" -ne 1 ] && return
COMPREPLY=($(compgen -W "all blanton" -- "${COMP_WORDS[COMP_CWORD]}"))
}
complete -F _margin_save_completions margin_save
if [ -z "$_LOG_FILE" ]; then if [ -z "$_LOG_FILE" ]; then
_LOG_FILE="$LOGS_DIR/margin_$(date '+%Y%m%d_%H%M%S').log" _LOG_FILE="$LOGS_DIR/margin_$(date '+%Y%m%d_%H%M%S').log"
_LOG_DETAIL_FILE="${_LOG_FILE%.log}_detail.log" _LOG_DETAIL_FILE="${_LOG_FILE%.log}_detail.log"
@@ -4,6 +4,10 @@
# ⚠️ margin_save 會把目前的 margin / OV / UV 設定**永久寫入 NVM** # ⚠️ margin_save 會把目前的 margin / OV / UV 設定**永久寫入 NVM**
# 斷電後仍保留。執行前請務必先 margin_status 確認各 channel 狀態正確。 # 斷電後仍保留。執行前請務必先 margin_status 確認各 channel 狀態正確。
# #
# 全部 9 顆請直接用 margin.sh v2.7.0 內建的 `margin_save all` —— 它有成功/失敗總結,
# 跑完還會把 margin_init 切回你原本載入的那顆。這支只在「只想寫其中幾顆」時才需要,
# 編下面的 BOARDS 陣列。
#
# Usage: # Usage:
# source script/margin_save_all.sh # source script/margin_save_all.sh
@@ -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)
@@ -0,0 +1,95 @@
#!/bin/bash
# =============================================================================
# TH6_SWB0_power_readback.sh -- SWB0 rail power from the platform sensors
# =============================================================================
# Version History:
# V1.1.0 2026-08-28 'show platform voltage' and 'show platform current' are
# now read ONCE each instead of once per rail. The old
# loop ran them 19 times apiece (38 invocations of a slow
# Python CLI) and every sample came from a different
# instant, so the rails were never actually measured
# together. Output format is unchanged.
# Rails missing from the tables are reported on stderr
# instead of silently contributing 0.000 V / 0.00 W.
# V1.0.0 2026-08-28 First version.
# =============================================================================
#
# Reads the two platform sensor tables:
# 'show platform voltage' -> <RAIL> in mV
# 'show platform current' -> <RAIL>_CURRENT in mA
# and reports V x I per rail plus the total.
#
# For a loaded reading, start traffic first: blanton_tr518.sh start
# =============================================================================
# --- Rails to report, in output order (edit this list) ---
rails=(
"SWB0_0V9_ANLG0"
"SWB0_0V9_ANLG1"
"SWB0_0V75_ANLG0"
"SWB0_0V75_ANLG1"
"SWB0_PHYTILE_P0"
"SWB0_PHYTILE_P1"
"SWB0_PHYTILE_P2"
"SWB0_PHYTILE_P3"
"SWB0_PHYTILE_P4"
"SWB0_PHYTILE_P5"
"SWB0_PHYTILE_P6"
"SWB0_PHYTILE_P7"
"SWB0_PVDD1V5_CNDR0"
"SWB0_PVDD1V5_CNDR1"
"SWB0_PVDDA_PHYT0"
"SWB0_PVDDA_PHYT1"
"SWB0_PVDDA_PHYT2"
"SWB0_PVDDA_PHYT3"
"SWB0_TH6_CORE"
)
# --- One snapshot of each table ---------------------------------------------
# Both are taken up front, back to back, so every rail below comes from the same
# moment. Under tr 518 the currents move a lot; re-reading per rail meant the
# total was a sum of readings taken seconds apart.
V_TABLE="$(show platform voltage 2>/dev/null)"
C_TABLE="$(show platform current 2>/dev/null)"
if [ -z "$V_TABLE" ] || [ -z "$C_TABLE" ]; then
echo "ERROR: 'show platform voltage/current' returned nothing" >&2
exit 1
fi
# --- Join and report ---------------------------------------------------------
# Sensor name is field 1 and the value field 2 in both tables; header and rule
# lines drop out because their field 2 is not numeric.
{
printf '%s\n' "$V_TABLE"
echo "@@@SPLIT@@@"
printf '%s\n' "$C_TABLE"
} | awk -v rails="${rails[*]}" '
BEGIN { n = split(rails, R, " "); sec = 1 }
/^@@@SPLIT@@@$/ { sec = 2; next }
{
if (NF < 2) next
name = $1; val = $2
if (val !~ /^[0-9]+(\.[0-9]+)?$/) next # header / ---- / blank
if (sec == 2) CUR[name] = val + 0
else VOLT[name] = val + 0
}
END {
total = 0
for (i = 1; i <= n; i++) {
rail = R[i]; cname = rail "_CURRENT"
haveV = (rail in VOLT); haveC = (cname in CUR)
if (!haveV || !haveC)
printf "WARN: %s missing from the sensor table (%s%s%s) -- counted as 0 W\n", \
rail, (haveV ? "" : "no voltage"), \
((!haveV && !haveC) ? ", " : ""), \
(haveC ? "" : "no current") > "/dev/stderr"
v = haveV ? VOLT[rail] : 0
c = haveC ? CUR[cname] : 0
p = sprintf("%.2f", (v/1000)*(c/1000))
printf "%s: Voltage=%.3f V, Current=%.3f A, Power=%.2f W\n", rail, v/1000, c/1000, p
total = sprintf("%.2f", total + p)
}
print "----------------------------------------"
printf "TOTAL POWER: %.2f W\n", total
}'
@@ -0,0 +1,95 @@
#!/bin/bash
# =============================================================================
# TH6_SWB1_power_readback.sh -- SWB1 rail power from the platform sensors
# =============================================================================
# Version History:
# V1.1.0 2026-08-28 'show platform voltage' and 'show platform current' are
# now read ONCE each instead of once per rail. The old
# loop ran them 19 times apiece (38 invocations of a slow
# Python CLI) and every sample came from a different
# instant, so the rails were never actually measured
# together. Output format is unchanged.
# Rails missing from the tables are reported on stderr
# instead of silently contributing 0.000 V / 0.00 W.
# V1.0.0 2026-08-28 First version.
# =============================================================================
#
# Reads the two platform sensor tables:
# 'show platform voltage' -> <RAIL> in mV
# 'show platform current' -> <RAIL>_CURRENT in mA
# and reports V x I per rail plus the total.
#
# For a loaded reading, start traffic first: blanton_tr518.sh start
# =============================================================================
# --- Rails to report, in output order (edit this list) ---
rails=(
"SWB1_0V9_ANLG0"
"SWB1_0V9_ANLG1"
"SWB1_0V75_ANLG0"
"SWB1_0V75_ANLG1"
"SWB1_PHYTILE_P0"
"SWB1_PHYTILE_P1"
"SWB1_PHYTILE_P2"
"SWB1_PHYTILE_P3"
"SWB1_PHYTILE_P4"
"SWB1_PHYTILE_P5"
"SWB1_PHYTILE_P6"
"SWB1_PHYTILE_P7"
"SWB1_PVDD1V5_CNDR0"
"SWB1_PVDD1V5_CNDR1"
"SWB1_PVDDA_PHYT0"
"SWB1_PVDDA_PHYT1"
"SWB1_PVDDA_PHYT2"
"SWB1_PVDDA_PHYT3"
"SWB1_TH6_CORE"
)
# --- One snapshot of each table ---------------------------------------------
# Both are taken up front, back to back, so every rail below comes from the same
# moment. Under tr 518 the currents move a lot; re-reading per rail meant the
# total was a sum of readings taken seconds apart.
V_TABLE="$(show platform voltage 2>/dev/null)"
C_TABLE="$(show platform current 2>/dev/null)"
if [ -z "$V_TABLE" ] || [ -z "$C_TABLE" ]; then
echo "ERROR: 'show platform voltage/current' returned nothing" >&2
exit 1
fi
# --- Join and report ---------------------------------------------------------
# Sensor name is field 1 and the value field 2 in both tables; header and rule
# lines drop out because their field 2 is not numeric.
{
printf '%s\n' "$V_TABLE"
echo "@@@SPLIT@@@"
printf '%s\n' "$C_TABLE"
} | awk -v rails="${rails[*]}" '
BEGIN { n = split(rails, R, " "); sec = 1 }
/^@@@SPLIT@@@$/ { sec = 2; next }
{
if (NF < 2) next
name = $1; val = $2
if (val !~ /^[0-9]+(\.[0-9]+)?$/) next # header / ---- / blank
if (sec == 2) CUR[name] = val + 0
else VOLT[name] = val + 0
}
END {
total = 0
for (i = 1; i <= n; i++) {
rail = R[i]; cname = rail "_CURRENT"
haveV = (rail in VOLT); haveC = (cname in CUR)
if (!haveV || !haveC)
printf "WARN: %s missing from the sensor table (%s%s%s) -- counted as 0 W\n", \
rail, (haveV ? "" : "no voltage"), \
((!haveV && !haveC) ? ", " : ""), \
(haveC ? "" : "no current") > "/dev/stderr"
v = haveV ? VOLT[rail] : 0
c = haveC ? CUR[cname] : 0
p = sprintf("%.2f", (v/1000)*(c/1000))
printf "%s: Voltage=%.3f V, Current=%.3f A, Power=%.2f W\n", rail, v/1000, c/1000, p
total = sprintf("%.2f", total + p)
}
print "----------------------------------------"
printf "TOTAL POWER: %.2f W\n", total
}'
@@ -0,0 +1,495 @@
#!/usr/bin/env bash
# =============================================================================
# blanton_ber.sh -- SerDes BER test over every cabled port (PRBS via phydiag)
# =============================================================================
# Version History:
# V1.0.1 2026-08-28 Fix: every lane reported NA and the Port column showed
# "NA". bcmcmd returns CRLF, and the BER is the last token
# on its line, so the CR stayed glued to the number: it
# failed the numeric test (-> NA) and printing it sent the
# cursor back to column 1, so the RESULT column overwrote
# the start of the row. The parser now strips CR first.
# (The traffic report never hit this -- there the counter
# is followed by a delta, so the CR was trimmed with it.)
# V1.0.0 2026-08-28 First version. Bash port of tools/BER_Test.txt.
# init/clear/start/stop/report, -u 0|1|all (default both).
# report parses 'prbsstat Ber' into a per-lane table with a
# PASS/FAIL against BER_MAX (default 1e-6) and a summary
# across units at the very bottom.
# =============================================================================
#
# This is the WHOLE-SWITCH BER sweep: every cabled port, both lanes, PRBS-31
# through the SerDes. Not to be confused with port_prbs_monitor.sh, which drives
# the two 100G uplinks only and polls them continuously.
#
# Order of operations (matches tools/BER_Test.txt):
# init phydiag <ports> prbs set p=3 arm PRBS on every lane
# phydiag <ports> prbs get confirm every lane locked
# start phydiag <ports> prbsstat STArt Interval=<n>
# ...wait at least one Interval...
# report phydiag <ports> prbsstat Ber read + judge
# stop phydiag <ports> prbsstat STOp
# clear phydiag <ports> prbs clear disarm, release the lanes
#
# ⚠️ 'clear' disarms PRBS -- run 'init' again before another measurement.
# ⚠️ PRBS takes the SerDes out of normal operation: the links report DOWN and no
# traffic passes while it is armed. Do not run this alongside
# blanton_traffic_linespeed or blanton_tr518.
#
# Usage (run directly):
# ./blanton_ber.sh init # both units
# ./blanton_ber.sh start -u 0 # unit 0 only
# ./blanton_ber.sh report
# ./blanton_ber.sh stop
# ./blanton_ber.sh clear
#
# Usage (sourced, like the other blanton_*.sh tools):
# source blanton_ber.sh
# blanton_ber report -u 1
#
# Options:
# -u|--unit <0|1|all|0,1> bcmcmd unit(s). Default: $BER_UNITS ("0 1")
# -c|--criteria <ber> report: PASS threshold (default $BER_MAX)
# -i|--interval <sec> start: prbsstat Interval (default $BER_INTERVAL)
# -p|--ports <list> override the port list
# -F|--fail-only report: list only the lanes that FAIL
# --tsv report: tab-separated (paste into Excel)
# --raw report: also print the raw 'prbsstat Ber' output
# --color|--no-color report: force / disable colours
# -f|--file <log> report: parse a saved console log, do not touch DUT
# -d|--debug print every bcmcmd before running it
# -n|--dry-run print commands only, do not touch the DUT
# -h|--help help
#
# Environment overrides:
# BCMCMD_CMD=bcmcmd BER_UNITS="0 1" BER_MAX=1e-6 BER_INTERVAL=30
# BER_PORTS="1-24,..." BER_POLY=p=3 BER_LOG_DIR=/tmp DEBUG_MODE=1
# =============================================================================
# === Config ==================================================================
BCMCMD_CMD="${BCMCMD_CMD:-bcmcmd}"
BER_UNITS="${BER_UNITS:-0 1}" # one DUT = two switch boards (0 and 1)
# The cabled ports, in bcm logical-port numbering. Same list as
# blanton_traffic_linespeed.sh's TL_PS_PORTS -- kept as one string so it can be
# pasted straight from/into a manual phydiag command.
BER_PORTS="${BER_PORTS:-1-24,40-96,112-168,184-240,256-259,270-308,310-327,342-380,382-399,414-452,454-471,486-524,526-543,558-565}"
BER_POLY="${BER_POLY:-p=3}" # passed to 'prbs set'
BER_INTERVAL="${BER_INTERVAL:-30}" # passed to 'prbsstat STArt Interval='
# PASS threshold. A lane PASSES when its BER is strictly BELOW this.
# 1e-6 is the acceptance figure for this project; the measured lanes sit around
# 1e-13..1e-8, so anything near the threshold is already a real finding.
BER_MAX="${BER_MAX:-1e-6}"
BER_LOG_DIR="${BER_LOG_DIR:-/tmp}" # where the raw 'prbsstat Ber' capture goes
BER_FAIL_ONLY=${BER_FAIL_ONLY:-0}
BER_TSV=${BER_TSV:-0}
BER_RAW=${BER_RAW:-0}
BER_REPORT_FILE="" # -f <log>: parse this instead of the DUT
BER_COLOR="${BER_COLOR:-auto}" # auto|always|never
DEBUG_MODE=${DEBUG_MODE:-0}
BER_DRY_RUN=${BER_DRY_RUN:-0}
# === Logging =================================================================
_ber_info() { echo -e "[\033[34mINFO\033[0m] $*"; }
_ber_ok() { echo -e "[\033[32m OK \033[0m] $*"; }
_ber_warn() { echo -e "[\033[33mWARN\033[0m] $*" >&2; }
_ber_err() { echo -e "[\033[31mERR \033[0m] $*" >&2; }
_ber_dbg() { [ "$DEBUG_MODE" = "1" ] && echo -e "\033[90m[DEBG] $*\033[0m" >&2; return 0; }
# === bcmcmd wrapper ==========================================================
# stdin closed for the same reason as blanton_traffic_linespeed.sh: bcmcmd
# otherwise inherits the caller's stdin and eats what a surrounding loop reads.
#
# bcmcmd exits 0 even when the BCM shell refused the command, so the output is
# pattern-matched as well. And it exits NON-zero on long-running commands whose
# response poll times out while the command is in fact fine -- that one is
# tolerated (the blanton_tr518.sh lesson).
BER_ERR_PAT="${BER_ERR_PAT:-ERROR|Failed to execute|Invalid|Syntax error|Unavailable|Unknown command}"
BER_BENIGN_PAT="${BER_BENIGN_PAT:-polling socket timeout}"
# _ber_bcm <unit> <phydiag args...> -- wraps in dsh -c 'phydiag <ports> ...'
# echoes the DUT output on stdout, returns 0 ok / 1 failed
_ber_bcm() {
local unit="$1"; shift
local inner="phydiag ${BER_PORTS} $*"
local cmd="dsh -c '${inner}'"
_ber_dbg "$BCMCMD_CMD -n $unit -c \"${cmd}\""
if [ "$BER_DRY_RUN" = "1" ]; then
echo "[DRY ] $BCMCMD_CMD -n $unit -c \" ${cmd} \"" >&2
return 0
fi
local out rc
out=$("$BCMCMD_CMD" -n "$unit" -c "$cmd" </dev/null 2>&1); rc=$?
printf '%s\n' "$out"
if echo "$out" | grep -qiE "$BER_ERR_PAT"; then
_ber_err "unit $unit: the BCM shell rejected 'phydiag ... $*'"
return 1
fi
if [ "$rc" -ne 0 ] && ! echo "$out" | grep -qiE "$BER_BENIGN_PAT"; then
_ber_err "unit $unit: bcmcmd exited $rc on 'phydiag ... $*'"
return 1
fi
return 0
}
_ber_units_check() {
local u ok=""
for u in $BER_UNITS; do
case "$u" in
0|1) ok="$ok $u" ;;
*) _ber_err "invalid unit '$u' (use 0, 1 or all)"; return 1 ;;
esac
done
[ -n "$ok" ] || { _ber_err "no unit selected"; return 1; }
BER_UNITS="${ok# }"
return 0
}
_ber_use_color() {
case "$BER_COLOR" in
always|1|yes) return 0 ;;
never|0|no) return 1 ;;
*) [ -t 1 ] && [ "$BER_TSV" != "1" ] ;;
esac
}
# === init ====================================================================
# prbs set + prbs get. The 'get' is not decoration: a lane that did not lock
# still reports a BER later, and it is a meaningless one.
blanton_ber_init() {
_ber_units_check || return 1
local unit out total ok rc=0
for unit in $BER_UNITS; do
_ber_info "unit $unit: prbs set ${BER_POLY} on ${BER_PORTS}"
_ber_bcm "$unit" "prbs set ${BER_POLY}" >/dev/null || { rc=1; continue; }
out=$(_ber_bcm "$unit" "prbs get") || { rc=1; continue; }
[ "$BER_DRY_RUN" = "1" ] && continue
# lines look like: " 185 : PRBS OK!has been unlocked since last call"
total=$(printf '%s\n' "$out" | grep -cE '^[ \t]*[0-9]+[ \t]*:')
ok=$(printf '%s\n' "$out" | grep -cE '^[ \t]*[0-9]+[ \t]*:.*PRBS OK')
if [ "$total" -eq 0 ]; then
_ber_err "unit $unit: 'prbs get' reported no lanes at all"
rc=1
elif [ "$ok" -eq "$total" ]; then
_ber_ok "unit $unit: $ok/$total ports locked"
else
_ber_warn "unit $unit: only $ok/$total ports locked -- the rest will report a meaningless BER:"
printf '%s\n' "$out" | grep -E '^[ \t]*[0-9]+[ \t]*:' | grep -v 'PRBS OK' >&2
rc=2
fi
done
return $rc
}
# === clear ===================================================================
blanton_ber_clear() {
_ber_units_check || return 1
local unit rc=0
for unit in $BER_UNITS; do
_ber_info "unit $unit: prbs clear"
_ber_bcm "$unit" "prbs clear" >/dev/null || rc=1
done
[ "$rc" -eq 0 ] && _ber_ok "PRBS disarmed on unit(s): $BER_UNITS -- run 'init' before measuring again"
return $rc
}
# === start ===================================================================
blanton_ber_start() {
_ber_units_check || return 1
local unit rc=0
for unit in $BER_UNITS; do
_ber_info "unit $unit: prbsstat STArt Interval=${BER_INTERVAL}"
_ber_bcm "$unit" "prbsstat STArt Interval=${BER_INTERVAL}" >/dev/null || rc=1
done
if [ "$rc" -eq 0 ]; then
_ber_ok "BER accumulation running on unit(s): $BER_UNITS"
_ber_info "wait at least ${BER_INTERVAL}s before 'report' -- a shorter run has not accumulated a full interval"
fi
return $rc
}
# === stop ====================================================================
blanton_ber_stop() {
_ber_units_check || return 1
local unit rc=0
for unit in $BER_UNITS; do
_ber_info "unit $unit: prbsstat STOp"
_ber_bcm "$unit" "prbsstat STOp" >/dev/null || rc=1
done
if [ "$rc" -eq 0 ]; then
_ber_ok "BER accumulation stopped on unit(s): $BER_UNITS"
_ber_info "PRBS is still armed -- 'clear' releases the lanes and restores normal operation"
fi
return $rc
}
# === report ==================================================================
# Parses 'prbsstat Ber' output:
# port BER
# ====
# 1[0] : 3.53e-11
# 1[1] : 4.00e-11
# into <port> <lane> <ber> <verdict>, PASS when ber < BER_MAX.
# _ber_render <logfile> [side_file] -> 0 all PASS, 2 any FAIL/NA, 1 parse error
_ber_render() {
local log="$1" side="${2:-/dev/null}" color=0
_ber_use_color && color=1
awk -v MAXBER="$BER_MAX" \
-v FAILONLY="$BER_FAIL_ONLY" \
-v TSV="$BER_TSV" \
-v COLOR="$color" \
-v SIDE="$side" '
function vc(v) {
if (!COLOR) return v
if (v == "PASS") return "\033[32m" v "\033[0m"
if (v == "FAIL") return "\033[31m" v "\033[0m"
return "\033[33m" v "\033[0m"
}
BEGIN {
LIM = MAXBER + 0
NC = 4
HDR[1] = "Port"; AL[1] = 1
HDR[2] = "Lane"; AL[2] = 1
HDR[3] = "BER"; AL[3] = 1
HDR[4] = "RESULT"; AL[4] = 1
worst = -1; worstlane = "-"
}
# bcmcmd hands back CRLF. On the cd/traffic report the counter is followed by
# a delta, so the CR got trimmed with the rest of the line; here the BER is
# the LAST token, so the CR sticks to the number -- it failed the numeric
# test (every lane NA) and, worse, printing it moved the cursor back to
# column 1 so the RESULT column overwrote the start of the row.
{ gsub(/\r/, "") }
{
# " 203[1] : 5.05e-09" (leading spaces vary, "====" separators ignored)
if ($0 !~ /^[ \t]*[0-9]+\[[0-9]+\][ \t]*:/) next
line = $0
sub(/^[ \t]+/, "", line)
p = index(line, "[")
q = index(line, "]")
c = index(line, ":")
port = substr(line, 1, p - 1)
lane = substr(line, p + 1, q - p - 1)
ber = substr(line, c + 1)
gsub(/^[ \t\r]+|[ \t\r]+$/, "", ber)
sub(/[ \t\r].*$/, "", ber)
seen++
n++
PORT[n] = port; LANE[n] = lane; BERS[n] = ber
# a lane that reported something non-numeric has no usable measurement
if (ber ~ /^[0-9]+(\.[0-9]+)?([eE][-+]?[0-9]+)?$/) {
v = ber + 0
if (v < LIM) { V[n] = "PASS"; npass++ }
else { V[n] = "FAIL"; nfail++ }
if (v > worst) { worst = v; worstlane = port "[" lane "]"; worststr = ber }
} else {
V[n] = "NA"; nna++
}
}
END {
if (seen == 0) {
print "error: no \"<port>[<lane>] : <ber>\" line found in the log" > "/dev/stderr"
exit 1
}
# column widths over the rows that will actually be printed
for (c2 = 1; c2 <= NC; c2++) w[c2] = length(HDR[c2])
for (i = 1; i <= n; i++) {
if (FAILONLY && V[i] == "PASS") continue
if (length(PORT[i]) > w[1]) w[1] = length(PORT[i])
if (length(LANE[i]) > w[2]) w[2] = length(LANE[i])
if (length(BERS[i]) > w[3]) w[3] = length(BERS[i])
if (length(V[i]) > w[4]) w[4] = length(V[i])
}
for (c2 = 1; c2 <= NC; c2++) w[c2] += 2
if (TSV) {
print HDR[1] "\t" HDR[2] "\t" HDR[3] "\t" HDR[4]
for (i = 1; i <= n; i++) {
if (FAILONLY && V[i] == "PASS") continue
print PORT[i] "\t" LANE[i] "\t" BERS[i] "\t" V[i]
}
} else {
printf "%*s%*s%*s%*s\n", w[1], HDR[1], w[2], HDR[2], w[3], HDR[3], w[4], HDR[4]
for (i = 1; i <= n; i++) {
if (FAILONLY && V[i] == "PASS") continue
printf "%*s%*s%*s", w[1], PORT[i], w[2], LANE[i], w[3], BERS[i]
# pad on the plain text, then colour, so the columns stay aligned
printf "%*s%s\n", w[4] - length(V[i]), "", vc(V[i])
}
if (FAILONLY && nfail == 0 && nna == 0)
print " (no failing lane)"
}
print ""
printf "# lanes measured : %d\n", n
printf "# %s %d %s %d %s %d (criteria BER < %s)\n", \
vc("PASS"), npass + 0, vc("FAIL"), nfail + 0, vc("NA"), nna + 0, MAXBER
if (worst >= 0)
printf "# worst lane : %s %s\n", worstlane, worststr
if (SIDE != "" && SIDE != "/dev/null")
printf "SUM %d %d %d %d %s %s\n", n, npass + 0, nfail + 0, nna + 0, \
(worst >= 0 ? worststr : "-"), worstlane > SIDE
exit (nfail || nna) ? 2 : 0
}' "$log"
}
# _ber_summary <stamp> -- one table for every unit, printed last
_ber_summary() {
local stamp="$1" unit side g r y n worst=0
g=$'\033[32m'; r=$'\033[31m'; y=$'\033[33m'; n=$'\033[0m'
_ber_use_color || { g=""; r=""; y=""; n=""; }
echo ""
echo "===== summary : all units ====="
printf '%-8s %8s %8s %8s %8s %-12s %-10s %s\n' \
"Unit" "LANES" "PASS" "FAIL" "NA" "WORST BER" "WORST LANE" "RESULT"
local tag lanes pass fail na wber wlane v any=0
for unit in $BER_UNITS; do
side="$BER_LOG_DIR/berside_unit${unit}_${stamp}"
[ -r "$side" ] || continue
read -r tag lanes pass fail na wber wlane < "$side" 2>/dev/null
[ "$tag" = "SUM" ] || continue
any=1
if [ "${fail:-0}" -gt 0 ] || [ "${na:-0}" -gt 0 ]; then
v="${r}FAIL${n}"; worst=2
else
v="${g}PASS${n}"
fi
printf '%-8s %8s %8s %8s %8s %-12s %-10s %b\n' \
"unit $unit" "$lanes" "$pass" "$fail" "$na" "$wber" "$wlane" "$v"
done
[ "$any" = "1" ] || echo " (no unit produced a result)"
echo ""
printf 'criteria: BER < %s\n' "$BER_MAX"
echo ""
return "$worst"
}
blanton_ber_report() {
local unit log side rc worst=0 stamp
# -f <log>: offline, no DUT access, no unit loop -> no cross-unit summary
if [ -n "$BER_REPORT_FILE" ]; then
[ -r "$BER_REPORT_FILE" ] || { _ber_err "cannot read log '$BER_REPORT_FILE'"; return 1; }
_ber_info "report from log: $BER_REPORT_FILE (criteria BER < $BER_MAX)" >&2
_ber_render "$BER_REPORT_FILE"; return $?
fi
_ber_units_check || return 1
if [ "$BER_DRY_RUN" = "1" ]; then
for unit in $BER_UNITS; do _ber_bcm "$unit" "prbsstat Ber" >/dev/null; done
return 0
fi
stamp=$(date +%Y%m%d_%H%M%S 2>/dev/null || echo now)
for unit in $BER_UNITS; do
log="$BER_LOG_DIR/prbsber_unit${unit}_${stamp}.log"
side="$BER_LOG_DIR/berside_unit${unit}_${stamp}"
: > "$side"
if ! _ber_bcm "$unit" "prbsstat Ber" > "$log" 2>&1; then
_ber_err "unit $unit: 'prbsstat Ber' failed (see $log)"; worst=1; continue
fi
[ "$BER_RAW" = "1" ] && { echo "----- unit $unit : raw prbsstat Ber -----"; cat "$log"; }
echo "===== unit $unit : PRBS BER report (raw: $log) ====="
_ber_render "$log" "$side"; rc=$?
[ "$rc" -gt "$worst" ] && worst=$rc
case "$rc" in
0) _ber_ok "unit $unit: every lane below $BER_MAX" ;;
2) _ber_warn "unit $unit: at least one lane FAIL/NA" ;;
*) _ber_err "unit $unit: report parse error" ;;
esac
done
_ber_summary "$stamp"; rc=$?
[ "$rc" -gt "$worst" ] && worst=$rc
rm -f "$BER_LOG_DIR"/berside_unit*_"${stamp}"
return "$worst"
}
# === Help ====================================================================
blanton_ber_help() {
echo -e "\033[1mblanton_ber.sh - SerDes BER test over every cabled port (PRBS via phydiag)\033[0m"
echo ""
echo -e " Units : \033[36m$BER_UNITS\033[0m poly: \033[36m$BER_POLY\033[0m interval: \033[36m${BER_INTERVAL}s\033[0m"
echo -e " Criteria : BER < \033[36m$BER_MAX\033[0m"
echo -e " Ports : \033[36m$BER_PORTS\033[0m"
echo ""
echo -e "\033[1mSubcommands\033[0m"
echo -e " \033[33minit\033[0m prbs set $BER_POLY + prbs get (checks every lane locked)"
echo -e " \033[33mstart\033[0m prbsstat STArt Interval=$BER_INTERVAL"
echo -e " \033[33mreport\033[0m prbsstat Ber -> per-lane table + PASS/FAIL + summary"
echo -e " \033[33mstop\033[0m prbsstat STOp"
echo -e " \033[33mclear\033[0m prbs clear (disarms PRBS -- 'init' again before measuring)"
echo -e " \033[33mhelp\033[0m"
echo ""
echo -e "\033[1mOptions\033[0m"
echo -e " \033[33m-u|--unit\033[0m <0|1|all> bcmcmd unit(s); 'all' = 0 1 (default: $BER_UNITS)"
echo -e " \033[33m-c|--criteria\033[0m <ber> PASS threshold (default $BER_MAX)"
echo -e " \033[33m-i|--interval\033[0m <sec> prbsstat Interval (default $BER_INTERVAL)"
echo -e " \033[33m-p|--ports\033[0m <list> override the port list"
echo -e " \033[33m-F|--fail-only\033[0m report: list only failing lanes"
echo -e " \033[33m--tsv\033[0m report: tab-separated"
echo -e " \033[33m--raw\033[0m report: also print the raw output"
echo -e " \033[33m-f|--file\033[0m <log> report: parse a saved log, do not touch the DUT"
echo -e " \033[33m-d|--debug\033[0m print every bcmcmd"
echo -e " \033[33m-n|--dry-run\033[0m print commands only"
echo ""
echo -e "\033[1mTypical run\033[0m"
echo -e " ./blanton_ber.sh init && ./blanton_ber.sh start"
echo -e " sleep $BER_INTERVAL"
echo -e " ./blanton_ber.sh report"
echo -e " ./blanton_ber.sh stop && ./blanton_ber.sh clear"
echo ""
echo -e "\033[33m PRBS takes the SerDes out of normal operation: links show DOWN and no\033[0m"
echo -e "\033[33m traffic passes. Do not run alongside blanton_traffic_linespeed / blanton_tr518.\033[0m"
}
# === Argument parsing ========================================================
blanton_ber() {
local sub="${1:-help}"; shift 2>/dev/null
while [ $# -gt 0 ]; do
case "$1" in
-u|--unit)
case "$2" in
all|ALL) BER_UNITS="0 1" ;;
*) BER_UNITS="$(echo "$2" | tr ',' ' ')" ;;
esac
shift 2 ;;
-c|--criteria) BER_MAX="$2"; shift 2 ;;
-i|--interval) BER_INTERVAL="$2"; shift 2 ;;
-p|--ports) BER_PORTS="$2"; shift 2 ;;
-f|--file) BER_REPORT_FILE="$2"; shift 2 ;;
-F|--fail-only) BER_FAIL_ONLY=1; shift ;;
--tsv) BER_TSV=1; shift ;;
--raw) BER_RAW=1; shift ;;
--color) BER_COLOR=always; shift ;;
--no-color) BER_COLOR=never; shift ;;
-d|--debug) DEBUG_MODE=1; shift ;;
-n|--dry-run) BER_DRY_RUN=1; shift ;;
-h|--help) blanton_ber_help; return 0 ;;
*) _ber_err "unknown option '$1'"; return 1 ;;
esac
done
case "$sub" in
init) blanton_ber_init ;;
clear) blanton_ber_clear ;;
start) blanton_ber_start ;;
stop) blanton_ber_stop ;;
report) blanton_ber_report ;;
help|-h|--help) blanton_ber_help ;;
*) _ber_err "unknown subcommand '$sub'"; blanton_ber_help; return 1 ;;
esac
}
# Run when executed, define-only when sourced (same as the other blanton_*.sh).
if [ "${BASH_SOURCE[0]}" = "$0" ]; then
blanton_ber "$@"
fi
@@ -17,10 +17,21 @@
# V1.4.2 2026-08-10 Change: FUNCT0..3_RES BDF updated 0000:04:00.x -> 0000:03:00.x (current unit's enumeration). # V1.4.2 2026-08-10 Change: FUNCT0..3_RES BDF updated 0000:04:00.x -> 0000:03:00.x (current unit's enumeration).
# V1.4.3 2026-08-12 Change: FUNCT0..3_RES BDF updated 0000:03:00.x -> 0000:05:00.x (current unit's enumeration). # V1.4.3 2026-08-12 Change: FUNCT0..3_RES BDF updated 0000:03:00.x -> 0000:05:00.x (current unit's enumeration).
# V1.4.4 2026-08-12 Change: FUNCT0..3_RES BDF back to 0000:03:00.x for the DUT on COM36. # V1.4.4 2026-08-12 Change: FUNCT0..3_RES BDF back to 0000:03:00.x for the DUT on COM36.
# NOTE: this BDF is per-unit. Check with # V1.5.0 2026-08-17 Change: BDF is no longer hard-coded. FUNCT0..3_RES are now
# lspci -Dnn | grep -i fpga or # auto-detected at source time by scanning sysfs for the HPE
# ls -d /sys/bus/pci/devices/0000:*:00.3 # vendor id (0x1590) with all four functions .0-.3 present,
# and edit the four lines below if it differs. # falling back to `lspci -Dnn` name/id match, then to
# FPGA_BDF_FALLBACK. No more editing this file per DUT.
# Add: fpga_rescan (re-detect after DUT swap / re-enumeration),
# FPGA_BDF / FPGA_VENDOR_ID / FPGA_LSPCI_MATCH overrides,
# FPGA_BDF_ACTIVE reporting the BDF in use.
# Note: detection runs ONCE at source time (one INFO line on
# stdout, WARN/FAIL on stderr). FPGA_QUIET=1 silences the
# INFO line. cb_fpga/pmc/icb/swb never re-scan, so there is
# no per-command output and no per-command overhead.
# NOTE: the old per-unit NOTE ("edit the four lines below")
# no longer applies - detection handles it. To pin a BDF
# manually: FPGA_BDF=0000:05:00 fpga_rescan
# ============================================================================= # =============================================================================
# #
# SONiC hardware has no memtool / devmem, but it has pcimem. pcimem mmaps the PCI # SONiC hardware has no memtool / devmem, but it has pcimem. pcimem mmaps the PCI
@@ -42,26 +53,112 @@
# icb_fpga <addr> [data] # icb_fpga <addr> [data]
# swb0_fpga <addr> [data] # swb0_fpga <addr> [data]
# swb1_fpga <addr> [data] # swb1_fpga <addr> [data]
# fpga_rescan re-detect the CB FPGA BDF (after a DUT swap)
# #
# data present = write, absent = read. # data present = write, absent = read.
# Usage: source blanton_fpga_pcimem.sh # Usage: source blanton_fpga_pcimem.sh
# === CB FPGA PCIe resource file (sysfs) === # === CB FPGA PCIe resource file (sysfs) - AUTO-DETECTED ===
# Use lspci -D to find the BDF (domain:bus:dev.fn); resource0 is the sysfs mapping of BAR0. # The CB FPGA's BDF (domain:bus:dev.fn) changes per unit / per enumeration, so it is
# e.g. lspci -Dnn | grep -i fpga -> 0000:02:00.0 ... # detected at source time instead of being hard-coded. resource0 is the sysfs mapping
# memtool variant fills in the BAR value; pcimem variant fills in the resource file # of BAR0. (memtool variant fills in the BAR value; pcimem variant fills in the
# path instead (offset becomes relative to BAR). # resource file path instead, so offsets become relative to BAR.)
FUNCT0_RES=/sys/bus/pci/devices/0000:03:00.0/resource0 #
FUNCT1_RES=/sys/bus/pci/devices/0000:03:00.1/resource0 # Detection order:
FUNCT2_RES=/sys/bus/pci/devices/0000:03:00.2/resource0 # 1. FPGA_BDF override e.g. FPGA_BDF=0000:05:00 (domain:bus:dev, WITHOUT .fn)
FUNCT3_RES=/sys/bus/pci/devices/0000:03:00.3/resource0 # 2. sysfs vendor scan /sys/bus/pci/devices/*.0 whose vendor == FPGA_VENDOR_ID
# (0x1590 = Hewlett Packard Enterprise) and that has all
# four functions .0 .1 .2 .3 exposing resource0
# 3. lspci -Dnn fallback lines matching FPGA_LSPCI_MATCH
# ("Hewlett Packard Enterprise" or vendor id "[1590:")
# 4. FPGA_BDF_FALLBACK last-known BDF, only if 1-3 all fail
#
# Overrides (export before sourcing, or set and call fpga_rescan afterwards):
# FPGA_BDF=0000:05:00 pin a specific device, skip detection
# FPGA_VENDOR_ID=0x10ee different vendor (e.g. a Xilinx-branded board)
# FPGA_LSPCI_MATCH='...' custom lspci grep -iE pattern
FPGA_VENDOR_ID="${FPGA_VENDOR_ID:-0x1590}"
FPGA_LSPCI_MATCH="${FPGA_LSPCI_MATCH:-Hewlett Packard Enterprise|\[1590:}"
FPGA_BDF_FALLBACK="${FPGA_BDF_FALLBACK:-0000:03:00}"
# Auto-detect example (uncomment to use; needs the FPGA's BDF prefix): # True if all four functions of <bdf> exist and expose resource0.
# _BDF=0000:02:00 # Usage: _fpga_bdf_ok <domain:bus:dev>
# FUNCT0_RES=/sys/bus/pci/devices/${_BDF}.0/resource0 _fpga_bdf_ok() {
# FUNCT1_RES=/sys/bus/pci/devices/${_BDF}.1/resource0 local bdf="$1" fn
# FUNCT2_RES=/sys/bus/pci/devices/${_BDF}.2/resource0 for fn in 0 1 2 3; do
# FUNCT3_RES=/sys/bus/pci/devices/${_BDF}.3/resource0 [ -e "/sys/bus/pci/devices/${bdf}.${fn}/resource0" ] || return 1
done
return 0
}
# Print the detected FPGA BDF prefix (domain:bus:dev) on stdout; non-zero if none.
_fpga_detect_bdf() {
local cands="" bdf dir vend want
# 1. explicit override wins (tolerate a trailing ".fn" being included)
if [ -n "${FPGA_BDF:-}" ]; then
bdf="$FPGA_BDF"
case "$bdf" in *.[0-7]) bdf="${bdf%.*}" ;; esac
echo "$bdf"
return 0
fi
want=$(echo "$FPGA_VENDOR_ID" | tr 'A-Z' 'a-z')
# 2. sysfs vendor-ID scan (no lspci / pci.ids needed)
for dir in /sys/bus/pci/devices/*:*.0; do
[ -e "$dir/vendor" ] || continue
vend=$(tr 'A-Z' 'a-z' < "$dir/vendor" 2>/dev/null)
[ "$vend" = "$want" ] || continue
bdf="${dir##*/}"; bdf="${bdf%.0}"
_fpga_bdf_ok "$bdf" && cands="$cands $bdf"
done
# 3. lspci fallback (device name match, in case the vendor id differs)
if [ -z "$cands" ] && command -v lspci >/dev/null 2>&1; then
for bdf in $(lspci -Dnn 2>/dev/null | grep -iE "$FPGA_LSPCI_MATCH" \
| awk '{print $1}' | sed 's/\.[0-9a-f]*$//' | sort -u); do
_fpga_bdf_ok "$bdf" && cands="$cands $bdf"
done
fi
set -- $cands
[ $# -eq 0 ] && return 1
if [ $# -gt 1 ]; then
echo -e "[\033[33mWARN\033[0m] multiple candidate FPGAs found:$cands -> using $1" >&2
echo -e " set FPGA_BDF=<domain:bus:dev> and run fpga_rescan to pick another." >&2
fi
echo "$1"
}
# (Re)detect the FPGA BDF and refresh FUNCT0..3_RES. Called once at source time;
# call again by hand after swapping/re-enumerating a DUT.
# The one-line INFO goes to STDOUT (so wrappers that already do
# `source ... >/dev/null` stay silent); WARN/FAIL go to STDERR.
# Set FPGA_QUIET=1 to suppress the INFO line entirely.
fpga_rescan() {
local bdf
bdf=$(_fpga_detect_bdf)
if [ -z "$bdf" ]; then
bdf="$FPGA_BDF_FALLBACK"
echo -e "[\033[33mWARN\033[0m] no FPGA found (vendor $FPGA_VENDOR_ID / '$FPGA_LSPCI_MATCH');" >&2
echo -e " falling back to $bdf. Check with: lspci -Dnn | grep -iE 'Hewlett|fpga'" >&2
fi
FPGA_BDF_ACTIVE="$bdf"
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
if _fpga_bdf_ok "$bdf"; then
[ "${FPGA_QUIET:-0}" = "1" ] || \
echo -e "[\033[34mINFO\033[0m] CB FPGA BDF = \033[36m${bdf}.0-.3\033[0m (resource0)"
else
echo -e "[\033[31mFAIL\033[0m] ${bdf}.0-.3 resource0 not all present - register access will fail." >&2
return 1
fi
}
fpga_rescan
# === VSPI window base offset (relative to CB Function 2 resource0 start) === # === 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): # NOTE: CB.F2 has TWO near-identical VSPI register blocks (see spec register map):
@@ -268,6 +365,9 @@ blanton_fpga_help() {
echo -e " \033[33mswb0_fpga\033[0m <addr> [data] SWB0 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[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 -e " \033[33mfpga_debug\033[0m on|off Toggle debug mode (print commands and execute)"
echo -e " \033[33mfpga_rescan\033[0m Re-detect CB FPGA BDF (after DUT swap / re-enum)"
echo ""
echo -e " Active CB FPGA BDF: \033[36m${FPGA_BDF_ACTIVE:-unknown}\033[0m .0-.3 (pin with: FPGA_BDF=0000:05:00 fpga_rescan)"
echo "" echo ""
echo -e " Omit [data] to \033[32mread\033[0m, provide [data] to \033[31mwrite\033[0m." 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 " Backend: \033[36mpcimem\033[0m (sysfs resource file + relative offset)."
@@ -0,0 +1,326 @@
#!/usr/bin/env bash
# =============================================================================
# blanton_tr518.sh -- TH6 built-in packet test (tr 518) via bcmcmd
# =============================================================================
# Version History:
# V1.0.1 2026-08-28 Fix: start reported ERR on a run that was actually fine.
# bcmcmd exits NON-zero on 'tr 518' because the test
# outlives its response poll ("polling socket timeout:
# Success") -- the command had been accepted and was
# running. The tr 518 step is now judged on a POSITIVE
# marker in the output ("TR 518 TEST PARAMETERS", which
# the shell prints only after it starts the test) instead
# of on bcmcmd's exit status, and "polling socket timeout"
# is tolerated generally. Failures now also say WHICH
# check tripped (shell rejected it / bcmcmd exited N).
# V1.0.0 2026-08-28 First version. Bash port of tools/TR518.txt, command for
# command. start = port cd lb=mac, l2 learn off,
# test mode nr=yes, tr 518 ... testphase=1, then a settle
# wait so a power reading taken afterwards is a loaded and
# stable one rather than a ramping one.
# stop = port cd lb=none (exactly what TR518.txt does).
# -u 0 | -u 1 | -u all (default both).
# =============================================================================
#
# What this is for: tr 518 loads the switch so the power rails can be measured
# under traffic. The usual sequence is
#
# blanton_tr518.sh start # loads both units, waits to settle
# TH6_SWB0_power_readback.sh # now the numbers mean something
# TH6_SWB1_power_readback.sh
# blanton_tr518.sh stop
#
# ⚠️ tr 518 and blanton_traffic_linespeed CANNOT run at the same time. Both take
# over the cd ports and L2 learning; running one while the other is set up
# gives numbers from neither test. Stop one before the other starts.
#
# ⚠️ stop only undoes the loopback -- see TR_RESTORE_* below.
#
# Usage (run directly):
# ./blanton_tr518.sh start # both units
# ./blanton_tr518.sh start -u 0 # unit 0 only
# ./blanton_tr518.sh start -u 1 # unit 1 only
# ./blanton_tr518.sh stop [-u 0|1]
# ./blanton_tr518.sh help
#
# Usage (sourced, like the other blanton_*.sh tools):
# source blanton_tr518.sh
# blanton_tr518 start -u 0
#
# Options:
# -u|--unit <0|1|all|0,1> bcmcmd unit(s). Default: $TR_UNITS ("0 1")
# -w|--wait <sec> start: settle wait after tr 518 (default $TR_SETTLE_SEC)
# --no-wait start: skip the settle wait entirely
# -d|--debug print every bcmcmd and its output
# -n|--dry-run print commands only, do not touch the DUT
# -h|--help help
#
# Environment overrides:
# BCMCMD_CMD=bcmcmd TR_UNITS="0 1" TR_SETTLE_SEC=120 TR_UNIT_GAP_SEC=1
# TR_TEST_ARGS="..." TR_LB_PORTS=cd TR_RESTORE_LEARN=0 TR_RESTORE_TESTMODE=0
# DEBUG_MODE=1
# =============================================================================
# === Config ==================================================================
BCMCMD_CMD="${BCMCMD_CMD:-bcmcmd}"
TR_UNITS="${TR_UNITS:-0 1}" # one DUT = two switch boards (0 and 1)
# Settle wait after tr 518 is issued. The point is NOT the test itself -- it is
# to stop someone reading power while traffic is still ramping and reporting a
# number that is neither idle nor loaded. Lower it only if you know the rails
# have already settled.
TR_SETTLE_SEC="${TR_SETTLE_SEC:-120}"
# Gap between unit 0 and unit 1 (each unit is set up as a complete block, the
# way TR518.txt does it -- the cd ports are in MAC loopback, so a unit does not
# depend on the other one being ready).
TR_UNIT_GAP_SEC="${TR_UNIT_GAP_SEC:-1}"
# Ports put into MAC loopback. 'cd' = the cd port group, per TR518.txt.
TR_LB_PORTS="${TR_LB_PORTS:-cd}"
# The tr 518 argument string, verbatim from tools/TR518.txt. One editable line
# so it can be pasted straight from / into a manual bcmcmd.
# Printed by the BCM shell only after tr 518 has accepted its arguments and
# started. Used as the success marker, because bcmcmd's own exit status is not
# trustworthy for a command that runs longer than its response poll.
TR_TEST_OK_PAT="${TR_TEST_OK_PAT:-TR 518 TEST PARAMETERS}"
TR_TEST_ARGS="${TR_TEST_ARGS:-Scenario=6 Profile=2 Count=260 PktSize=324 PortList=1-259,270-565 rm=ext CheckLinkTime=20 CheckDataTime=200 testphase=1}"
# TR518.txt stops with 'port cd lb=none' and nothing else, so that is the
# default here too. But note what start changed and stop does NOT put back:
# l2 learn off -> the unit keeps learning disabled
# test mode nr=yes -> the unit stays in test mode
# Both persist until something else changes them or the box reboots. Set these
# to 1 if you want stop to be a true inverse; they are off by default because
# they are not in the bench-verified list.
TR_RESTORE_LEARN="${TR_RESTORE_LEARN:-0}"
TR_RESTORE_TESTMODE="${TR_RESTORE_TESTMODE:-0}"
DEBUG_MODE=${DEBUG_MODE:-0}
TR_DRY_RUN=${TR_DRY_RUN:-0}
TR_NO_WAIT=${TR_NO_WAIT:-0}
# === Logging =================================================================
_tr_info() { echo -e "[\033[34mINFO\033[0m] $*"; }
_tr_ok() { echo -e "[\033[32m OK \033[0m] $*"; }
_tr_warn() { echo -e "[\033[33mWARN\033[0m] $*" >&2; }
_tr_err() { echo -e "[\033[31mERR \033[0m] $*" >&2; }
_tr_dbg() { [ "$DEBUG_MODE" = "1" ] && echo -e "\033[90m[DEBG] $*\033[0m" >&2; return 0; }
# === bcmcmd wrapper ==========================================================
# stdin is closed for the same reason as in blanton_traffic_linespeed.sh:
# bcmcmd otherwise inherits the caller's stdin and eats whatever a surrounding
# loop was reading.
#
# bcmcmd exits 0 even when the BCM shell refused the command, so the output is
# pattern-matched as well (the V0.3.1 lesson from blanton_traffic_linespeed.sh).
TR_ERR_PAT="${TR_ERR_PAT:-ERROR|Failed to execute|Invalid|Syntax error|Unavailable|Unknown command}"
# ...and the reverse also happens: bcmcmd exits NON-zero on a command that was
# accepted and is running. 'tr 518' outlives bcmcmd's response poll, so bcmcmd
# gives up with "polling socket timeout: Success" and a non-zero status while
# the test carries on inside the BCM shell. Treating that as a failure is what
# made V1.0.0 report ERR on a run that was actually fine.
TR_BENIGN_PAT="${TR_BENIGN_PAT:-polling socket timeout}"
# _tr_bcm <unit> <cmd> -> 0 ok, 1 failed
# _tr_bcm_ok <unit> <ok_marker> <cmd> -> as above, but if the output contains
# <ok_marker> the command is a success
# regardless of exit status.
#
# The marker is a POSITIVE proof of success ("the shell printed the thing it
# only prints when it accepted this command"), which is far more reliable than
# trying to enumerate every way bcmcmd can look unhappy.
_tr_bcm() { _tr_bcm_ok "$1" "" "${@:2}"; }
_tr_bcm_ok() {
local unit="$1" okpat="$2"; shift 2
local cmd="$*" out rc why=""
_tr_dbg "$BCMCMD_CMD -n $unit -c '$cmd'"
if [ "$TR_DRY_RUN" = "1" ]; then
echo "[DRY ] $BCMCMD_CMD -n $unit -c '$cmd'"
return 0
fi
out=$("$BCMCMD_CMD" -n "$unit" -c "$cmd" </dev/null 2>&1); rc=$?
[ "$DEBUG_MODE" = "1" ] && [ -n "$out" ] && echo "$out"
# 1. positive proof wins over everything else
if [ -n "$okpat" ] && echo "$out" | grep -qF "$okpat"; then
[ "$rc" -ne 0 ] && _tr_dbg "unit $unit: bcmcmd rc=$rc but '$okpat' seen -> accepted"
return 0
fi
# 2. the BCM shell said no
if echo "$out" | grep -qiE "$TR_ERR_PAT"; then
why="the BCM shell rejected it"
# 3. bcmcmd itself was unhappy -- unless it is the known transport artifact
elif [ "$rc" -ne 0 ]; then
if echo "$out" | grep -qiE "$TR_BENIGN_PAT"; then
_tr_dbg "unit $unit: bcmcmd rc=$rc (${TR_BENIGN_PAT}) -> tolerated"
return 0
fi
why="bcmcmd exited $rc"
else
return 0
fi
_tr_err "unit $unit: $why -- '$cmd'"
[ "$DEBUG_MODE" = "1" ] || { [ -n "$out" ] && echo "$out" >&2; }
return 1
}
_tr_units_check() {
local u ok=""
for u in $TR_UNITS; do
case "$u" in
0|1) ok="$ok $u" ;;
*) _tr_err "invalid unit '$u' (use 0, 1 or all)"; return 1 ;;
esac
done
[ -n "$ok" ] || { _tr_err "no unit selected"; return 1; }
TR_UNITS="${ok# }"
return 0
}
_tr_is_uint() { case "$1" in ''|*[!0-9]*) return 1 ;; *) return 0 ;; esac; }
_tr_gap() {
[ "$TR_DRY_RUN" = "1" ] && return 0
[ "$TR_UNIT_GAP_SEC" = "0" ] && return 0
sleep "$TR_UNIT_GAP_SEC"
}
# === start ===================================================================
# One complete block per unit, exactly the order in tools/TR518.txt.
blanton_tr518_start() {
_tr_units_check || return 1
_tr_is_uint "$TR_SETTLE_SEC" || { _tr_err "settle wait '$TR_SETTLE_SEC' is not an integer (-w <sec>)"; return 1; }
local unit fail=0 first=1
_tr_info "TR518 start on unit(s): $TR_UNITS"
for unit in $TR_UNITS; do
[ "$first" = "0" ] && _tr_gap
first=0
_tr_info "unit $unit: port $TR_LB_PORTS lb=mac -> l2 learn off -> test mode nr=yes -> tr 518"
_tr_bcm "$unit" "port $TR_LB_PORTS lb=mac" || fail=$((fail+1))
_tr_bcm "$unit" "l2 learn off" || fail=$((fail+1))
_tr_bcm "$unit" 'dsh -c "test mode nr=yes"' || fail=$((fail+1))
# "TR 518 TEST PARAMETERS" is printed only once the shell has parsed the
# arguments and started the test -- that is the proof we check for.
_tr_bcm_ok "$unit" "$TR_TEST_OK_PAT" "dsh -c \"tr 518 $TR_TEST_ARGS\"" || fail=$((fail+1))
done
if [ "$fail" -ne 0 ]; then
_tr_err "$fail command(s) failed -- traffic may not be running, not waiting"
_tr_warn "the unit(s) may still be in loopback: blanton_tr518.sh stop"
return 1
fi
if [ "$TR_NO_WAIT" = "1" ] || [ "$TR_SETTLE_SEC" = "0" ]; then
_tr_ok "TR518 running on unit(s): $TR_UNITS (settle wait skipped)"
_tr_warn "rails may still be ramping -- a power reading taken now is not a settled one"
return 0
fi
_tr_info "settling ${TR_SETTLE_SEC}s before the rails are worth reading ..."
[ "$TR_DRY_RUN" = "1" ] || sleep "$TR_SETTLE_SEC"
_tr_ok "TR518 running and settled on unit(s): $TR_UNITS"
_tr_info "read power now: TH6_SWB0_power_readback.sh / TH6_SWB1_power_readback.sh"
_tr_info "when finished: blanton_tr518.sh stop"
return 0
}
# === stop ====================================================================
# TR518.txt stops with 'port cd lb=none' only. l2 learning and test mode are
# left as start set them unless TR_RESTORE_* is turned on.
blanton_tr518_stop() {
_tr_units_check || return 1
local unit fail=0 first=1
_tr_info "TR518 stop on unit(s): $TR_UNITS"
for unit in $TR_UNITS; do
[ "$first" = "0" ] && _tr_gap
first=0
_tr_bcm "$unit" "port $TR_LB_PORTS lb=none" || fail=$((fail+1))
[ "$TR_RESTORE_LEARN" = "1" ] && { _tr_bcm "$unit" "l2 learn on" || fail=$((fail+1)); }
[ "$TR_RESTORE_TESTMODE" = "1" ] && { _tr_bcm "$unit" 'dsh -c "test mode nr=no"' || fail=$((fail+1)); }
done
if [ "$fail" -eq 0 ]; then
_tr_ok "unit(s) $TR_UNITS: port $TR_LB_PORTS lb=none"
[ "$TR_RESTORE_LEARN" = "1" ] || \
_tr_info "l2 learning is still off (TR_RESTORE_LEARN=1 to put it back)"
[ "$TR_RESTORE_TESTMODE" = "1" ] || \
_tr_info "test mode is still nr=yes (TR_RESTORE_TESTMODE=1 to put it back)"
else
_tr_warn "$fail command(s) failed -- check the unit is not still in loopback:"
_tr_warn " bcmcmd -n <u> -c 'port $TR_LB_PORTS' | head"
return 1
fi
return 0
}
# === Help ====================================================================
blanton_tr518_help() {
echo -e "\033[1mblanton_tr518.sh - TH6 built-in packet test (tr 518) for power loading\033[0m"
echo ""
echo -e " Units : \033[36m$TR_UNITS\033[0m"
echo -e " Settle wait : \033[36m${TR_SETTLE_SEC}s\033[0m after tr 518 (so power is read loaded, not ramping)"
echo -e " Loopback : \033[36mport $TR_LB_PORTS lb=mac\033[0m"
echo -e " tr 518 args : \033[36m$TR_TEST_ARGS\033[0m"
echo ""
echo -e "\033[1mSubcommands\033[0m"
echo -e " \033[33mstart\033[0m per unit: port $TR_LB_PORTS lb=mac -> l2 learn off -> test mode nr=yes -> tr 518 -> wait"
echo -e " \033[33mstop\033[0m per unit: port $TR_LB_PORTS lb=none"
echo -e " \033[33mhelp\033[0m"
echo ""
echo -e "\033[1mOptions\033[0m"
echo -e " \033[33m-u|--unit\033[0m <0|1|all> bcmcmd unit(s); 'all' = 0 1 (default: $TR_UNITS)"
echo -e " \033[33m-w|--wait\033[0m <sec> start: settle wait (default $TR_SETTLE_SEC)"
echo -e " \033[33m--no-wait\033[0m start: skip the settle wait"
echo -e " \033[33m-d|--debug\033[0m print every bcmcmd and its output"
echo -e " \033[33m-n|--dry-run\033[0m print commands only, do not touch the DUT"
echo ""
echo -e "\033[1mExamples\033[0m"
echo -e " ./blanton_tr518.sh start # both units, then wait ${TR_SETTLE_SEC}s"
echo -e " ./blanton_tr518.sh start -u 0 # unit 0 only"
echo -e " ./blanton_tr518.sh stop -u 1 # release unit 1"
echo -e " ./blanton_tr518.sh start -n # show the bcmcmds, touch nothing"
echo ""
echo -e "\033[33m stop only sends 'port $TR_LB_PORTS lb=none'. l2 learning stays off and test mode\033[0m"
echo -e "\033[33m stays nr=yes -- TR_RESTORE_LEARN=1 / TR_RESTORE_TESTMODE=1 to put them back.\033[0m"
echo -e "\033[33m tr 518 and blanton_traffic_linespeed cannot run together.\033[0m"
}
# === Argument parsing ========================================================
blanton_tr518() {
local sub="${1:-help}"; shift 2>/dev/null
while [ $# -gt 0 ]; do
case "$1" in
-u|--unit)
case "$2" in
all|ALL) TR_UNITS="0 1" ;;
*) TR_UNITS="$(echo "$2" | tr ',' ' ')" ;;
esac
shift 2 ;;
-w|--wait) TR_SETTLE_SEC="$2"; shift 2 ;;
--no-wait) TR_NO_WAIT=1; shift ;;
-d|--debug) DEBUG_MODE=1; shift ;;
-n|--dry-run) TR_DRY_RUN=1; shift ;;
-h|--help) blanton_tr518_help; return 0 ;;
*) _tr_err "unknown option '$1'"; return 1 ;;
esac
done
case "$sub" in
start) blanton_tr518_start ;;
stop) blanton_tr518_stop ;;
help|-h|--help) blanton_tr518_help ;;
*) _tr_err "unknown subcommand '$sub'"; blanton_tr518_help; return 1 ;;
esac
}
# Run when executed, define-only when sourced (same as the other blanton_*.sh).
if [ "${BASH_SOURCE[0]}" = "$0" ]; then
blanton_tr518 "$@"
fi
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,344 @@
#!/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"
"i2ctransfer -f -y 1 w6@0x41 0x00 0xC0 0x55 0xAA 0x55 0xAA"
"i2ctransfer -f -y 1 w2@0x41 0x00 0xC0 r4"
"i2ctransfer -f -y 1 w6@0x41 0x00 0xC0 0xAA 0x55 0xAA 0x55"
"i2ctransfer -f -y 1 w2@0x41 0x00 0xC0 r4"
#"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
@@ -0,0 +1,403 @@
#!/bin/bash
###############################################################################
# mgmt_ping_monitor.sh
#
# Version : V3.0.0
# Author : ETWen
# Date : 20260826
#
# Purpose : Ping from BOTH management NICs at the same time, continuously, and
# keep accumulating until stopped:
#
# 10G (eth0) -> TARGET_10G
# 1G (eth1) -> TARGET_1G
#
# `stop` renders a report into the log: the last TAIL_LINES entries
# per NIC, each one's ping statistics and verdict, then
# `ip -s link show` for both interfaces.
#
# Notes : - Both NICs are configured with iproute2 `replace`, which is
# idempotent, and both are left up. On a shared subnet the target's
# ARP can be answered by either NIC, so ARP_STRICT keeps each one
# to its own address.
# - ARP WARM-UP: the first packet after a neighbour entry expires is
# spent resolving ARP and is counted as loss. On the bench this
# produced three FAILs whose only missing packet was icmp_seq=1,
# every time, with zero NIC errors or drops. One discarded ping
# before the measured run removes that artefact, which is what lets
# MAX_LOSS_PCT stay at 0 and still mean something.
# - MANAGEMENT CONNECTIVITY IS IN USE while this runs. Drive it from
# the serial console.
#
# Version History
# V1.0.0 20260824 Initial Version (ifconfig, one NIC at a time)
# V2.0.0 20260824 iproute2; both NICs stay up; per-NIC target;
# ip -s link show + TX/RX delta per leg
# V3.0.0 20260826 Both NICs ping SIMULTANEOUSLY and continuously instead
# of alternating fixed bursts. Add the ARP warm-up.
# `stop` renders last-N per NIC + statistics + both
# ip -s link outputs into the log.
###############################################################################
set -u
###############################################################################
# User Configurable Section -- normally this is the only part you need to edit
###############################################################################
# --- 10G management port ---
IF_10G="eth0"
IP_10G="192.168.1.99"
TARGET_10G="192.168.1.30" # host this NIC pings
PLEN_10G=24 # prefix length, e.g. 24 = /24
GW_10G="" # default gateway; empty = do not touch routing
METRIC_10G=100 # lower metric wins for off-subnet traffic
# --- 1G management port ---
IF_1G="eth1"
IP_1G="192.168.1.101"
TARGET_1G="192.168.1.31"
PLEN_1G=24
GW_1G=""
METRIC_1G=200
# --- ping ---
PING_INTERVAL_SEC=1 # seconds between echo requests (ping -i)
MAX_LOSS_PCT=0 # loss above this marks the NIC FAIL
TAIL_LINES=20 # how many recent entries per NIC the report shows
# Extra ping flags. -D timestamps every line, -O prints a marker for a request
# that got no reply, so a drop is visible in the tail instead of just missing.
# Clear this if the platform's ping does not accept them.
PING_EXTRA_OPTS="-D -O"
# One discarded ping per NIC before the measured run, to resolve ARP. Without
# it the first packet of the run is lost to neighbour resolution and looks
# exactly like a link fault. 0 disables.
ARP_WARMUP=1
ARP_WARMUP_TIMEOUT=2 # seconds to wait for the warm-up reply
# Both NICs stay up. If they share a subnet the target's ARP can be answered by
# either one, so a reply may arrive on the NIC that did not send. This applies
# arp_ignore=1 / arp_announce=2 to both. RAM only -- reverts on reboot.
ARP_STRICT=1
# Prefix for the ip commands; empty because this already runs as root.
SUDO=""
# Seconds to wait after bringing a link up before pinging.
LINK_SETTLE_SEC=5
# Logging
LOG_DIR="" # empty = <script directory>/log
LOG_NAME="mgmt_ping.log"
# What `start` does with the log left behind by the previous run:
# new = discard it (default -- one run, one log)
# archive = rename it to <log>.YYYYmmdd-HHMMSS first
# append = keep it
START_LOG_MODE="new"
###############################################################################
# Internal
###############################################################################
SCRIPT_NAME="$(basename -- "${BASH_SOURCE[0]}")"
SCRIPT_DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)"
[ -n "${LOG_DIR}" ] || LOG_DIR="${SCRIPT_DIR}/log"
LOG_FILE="${LOG_DIR}/${LOG_NAME}"
BASE="${LOG_DIR}/${LOG_NAME%.log}"
PID_FILE="${BASE}.pid"
STATE_FILE="${BASE}.state"
RAW_10G="${BASE}_${IF_10G}.raw"
RAW_1G="${BASE}_${IF_1G}.raw"
ts() { date '+%Y-%m-%d %H:%M:%S'; }
die() { printf '[ERROR] %s\n' "$*" >&2; exit 1; }
# "<rx_packets> <tx_packets>" for an interface. ip -s link orders the columns
# "bytes packets errors ...", so packets is $2, not $1.
if_counters() {
${SUDO} ip -s link show "$1" 2>/dev/null | awk '
/RX:/ { getline; rx = $2 }
/TX:/ { getline; tx = $2 }
END { printf "%s %s", (rx == "" ? 0 : rx), (tx == "" ? 0 : tx) }'
}
# Bring the NIC up and (re)apply its address and default route.
setup_iface() {
local iface="$1" ipaddr="$2" plen="$3" gw="$4" metric="$5"
${SUDO} ip link set "${iface}" up 2>&1 || return 1
${SUDO} ip address replace "${ipaddr}/${plen}" dev "${iface}" 2>&1 || return 1
if [ -n "${gw}" ]; then
${SUDO} ip route replace default via "${gw}" dev "${iface}" \
metric "${metric}" 2>&1 || return 1
fi
return 0
}
apply_arp_strict() {
local i
[ "${ARP_STRICT}" -eq 1 ] || return 0
for i in "${IF_10G}" "${IF_1G}"; do
${SUDO} sysctl -qw "net.ipv4.conf.${i}.arp_ignore=1" 2>/dev/null
${SUDO} sysctl -qw "net.ipv4.conf.${i}.arp_announce=2" 2>/dev/null
done
}
# Resolve the neighbour so the measured run does not spend its first packet on
# ARP. Result is deliberately discarded.
arp_warmup() {
local iface="$1" target="$2"
[ "${ARP_WARMUP}" -eq 1 ] || return 0
ping -I "${iface}" -c 1 -W "${ARP_WARMUP_TIMEOUT}" "${target}" >/dev/null 2>&1
return 0
}
# echo "pid_10g pid_1g" and return 0 when both are alive
is_running() {
local p0 p1
[ -f "${PID_FILE}" ] || return 1
p0="$(sed -n '1p' "${PID_FILE}" 2>/dev/null)"
p1="$(sed -n '2p' "${PID_FILE}" 2>/dev/null)"
[[ "${p0}" =~ ^[0-9]+$ ]] || return 1
[[ "${p1}" =~ ^[0-9]+$ ]] || return 1
kill -0 "${p0}" 2>/dev/null || kill -0 "${p1}" 2>/dev/null || return 1
printf '%s %s' "${p0}" "${p1}"
return 0
}
###############################################################################
# Report
###############################################################################
# The recent entries for one NIC: replies and, thanks to -O, the requests that
# got none.
tail_entries() {
grep -aE 'bytes from|no answer|Unreachable|Time to live' "$1" 2>/dev/null \
| tail -n "${TAIL_LINES}"
}
# The trailing "--- x ping statistics ---" block.
stats_block() {
sed -n '/ping statistics ---/,$p' "$1" 2>/dev/null
}
# render_leg <label> <iface> <target> <raw> <rx0> <tx0>
render_leg() {
local label="$1" iface="$2" target="$3" raw="$4" rx0="$5" tx0="$6"
local st tx rx loss avg verdict c1 rx1 tx1 drx dtx
printf -- '----- %s : %s -> %s : last %s entries -----\n' \
"${label}" "${iface}" "${target}" "${TAIL_LINES}"
tail_entries "${raw}"
printf '\n'
st="$(stats_block "${raw}")"
printf '%s\n' "${st}"
tx="$(printf '%s' "${st}" | sed -n 's/^\([0-9]\+\) packets transmitted.*/\1/p' | tail -1)"
rx="$(printf '%s' "${st}" | sed -n 's/.*[, ]\([0-9]\+\) received.*/\1/p' | tail -1)"
loss="$(printf '%s' "${st}" | sed -n 's/.*[, ]\([0-9]\+\)% packet loss.*/\1/p' | tail -1)"
avg="$(printf '%s' "${st}" | sed -n 's|.*= [0-9.]*/\([0-9.]*\)/.*|\1|p' | tail -1)"
[ -n "${tx}" ] || tx=0
[ -n "${rx}" ] || rx=0
[ -n "${loss}" ] || loss=100
[ -n "${avg}" ] || avg="-"
c1="$(if_counters "${iface}")"; rx1="${c1% *}"; tx1="${c1#* }"
drx=$(( rx1 - rx0 )); dtx=$(( tx1 - tx0 ))
if [ "${loss}" -le "${MAX_LOSS_PCT}" ] && [ "${tx}" -gt 0 ]; then
verdict="PASS"
else
verdict="FAIL"
fi
# nic_tx/nic_rx are this interface's own counter delta over the whole run.
# A pass with nic_tx near zero means the traffic left on the other NIC.
printf '[%s] RESULT %-3s %-6s -> %-15s tx=%s rx=%s loss=%s%% rtt_avg=%sms nic_tx=%s nic_rx=%s %s\n\n' \
"$(ts)" "${label}" "${iface}" "${target}" "${tx}" "${rx}" "${loss}" "${avg}" \
"${dtx}" "${drx}" "${verdict}"
}
write_report() {
local rx0_10g tx0_10g rx0_1g tx0_1g started
started="$(sed -n '1p' "${STATE_FILE}" 2>/dev/null)"
rx0_10g="$(sed -n '2p' "${STATE_FILE}" 2>/dev/null)"; rx0_10g="${rx0_10g:-0}"
tx0_10g="$(sed -n '3p' "${STATE_FILE}" 2>/dev/null)"; tx0_10g="${tx0_10g:-0}"
rx0_1g="$(sed -n '4p' "${STATE_FILE}" 2>/dev/null)"; rx0_1g="${rx0_1g:-0}"
tx0_1g="$(sed -n '5p' "${STATE_FILE}" 2>/dev/null)"; tx0_1g="${tx0_1g:-0}"
{
printf '#############################################################\n'
printf '[%s] mgmt ping report\n' "$(ts)"
printf ' started : %s\n' "${started:-unknown}"
printf ' stopped : %s\n' "$(ts)"
printf ' 10G : %s %s/%s -> %s (metric %s)\n' \
"${IF_10G}" "${IP_10G}" "${PLEN_10G}" "${TARGET_10G}" "${METRIC_10G}"
printf ' 1G : %s %s/%s -> %s (metric %s)\n' \
"${IF_1G}" "${IP_1G}" "${PLEN_1G}" "${TARGET_1G}" "${METRIC_1G}"
printf ' ping : -i %s %s, max loss %s%%\n' \
"${PING_INTERVAL_SEC}" "${PING_EXTRA_OPTS}" "${MAX_LOSS_PCT}"
printf '#############################################################\n\n'
render_leg "10G" "${IF_10G}" "${TARGET_10G}" "${RAW_10G}" "${rx0_10g}" "${tx0_10g}"
render_leg "1G" "${IF_1G}" "${TARGET_1G}" "${RAW_1G}" "${rx0_1g}" "${tx0_1g}"
printf -- '--- ip -s link show %s ---\n' "${IF_10G}"
${SUDO} ip -s link show "${IF_10G}" 2>&1
printf '\n'
printf -- '--- ip -s link show %s ---\n' "${IF_1G}"
${SUDO} ip -s link show "${IF_1G}" 2>&1
} >> "${LOG_FILE}"
}
###############################################################################
# Sub-commands
###############################################################################
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
;;
new)
# Truncate rather than unlink, so a tail -f already attached keeps
# following the new run.
[ -f "${LOG_FILE}" ] && { : > "${LOG_FILE}" || die "cannot truncate ${LOG_FILE}"; }
;;
*) die "invalid START_LOG_MODE: ${START_LOG_MODE}" ;;
esac
}
do_start() {
local pids p0 p1 c
pids="$(is_running)" && die "already running (pids=${pids})"
command -v ip >/dev/null 2>&1 || die "ip (iproute2) not found"
command -v ping >/dev/null 2>&1 || die "ping not found"
mkdir -p "${LOG_DIR}" || die "cannot create ${LOG_DIR}"
prepare_log_on_start
: > "${RAW_10G}"
: > "${RAW_1G}"
setup_iface "${IF_10G}" "${IP_10G}" "${PLEN_10G}" "${GW_10G}" "${METRIC_10G}" \
|| die "cannot configure ${IF_10G}"
setup_iface "${IF_1G}" "${IP_1G}" "${PLEN_1G}" "${GW_1G}" "${METRIC_1G}" \
|| die "cannot configure ${IF_1G}"
apply_arp_strict
sleep "${LINK_SETTLE_SEC}"
# Spend the ARP resolution here, not on the first measured packet.
arp_warmup "${IF_10G}" "${TARGET_10G}"
arp_warmup "${IF_1G}" "${TARGET_1G}"
# Counter baseline, taken after the warm-up so its packets are excluded.
{ c="$(if_counters "${IF_10G}")"
printf '%s\n%s\n%s\n' "$(ts)" "${c% *}" "${c#* }"
c="$(if_counters "${IF_1G}")"
printf '%s\n%s\n' "${c% *}" "${c#* }"
} > "${STATE_FILE}"
# Both NICs ping at the same time and keep accumulating until stopped.
nohup ping -I "${IF_10G}" -i "${PING_INTERVAL_SEC}" ${PING_EXTRA_OPTS} \
"${TARGET_10G}" >> "${RAW_10G}" 2>&1 &
p0=$!
nohup ping -I "${IF_1G}" -i "${PING_INTERVAL_SEC}" ${PING_EXTRA_OPTS} \
"${TARGET_1G}" >> "${RAW_1G}" 2>&1 &
p1=$!
disown "${p0}" 2>/dev/null
disown "${p1}" 2>/dev/null
printf '%s\n%s\n' "${p0}" "${p1}" > "${PID_FILE}"
sleep 1
kill -0 "${p0}" 2>/dev/null || die "10G ping failed to start, see ${RAW_10G}"
kill -0 "${p1}" 2>/dev/null || die "1G ping failed to start, see ${RAW_1G}"
printf 'started (10G pid=%s, 1G pid=%s)\nlog: %s\n' "${p0}" "${p1}" "${LOG_FILE}"
}
do_stop() {
local pids p0 p1 i
pids="$(is_running)" || {
printf 'not running\n'
rm -f "${PID_FILE}"
return 0
}
p0="${pids% *}"; p1="${pids#* }"
# SIGINT, not SIGTERM: ping prints its statistics block on interrupt, and
# that block is what the report parses.
kill -INT "${p0}" 2>/dev/null
kill -INT "${p1}" 2>/dev/null
for (( i = 0; i < 20; i++ )); do
kill -0 "${p0}" 2>/dev/null || kill -0 "${p1}" 2>/dev/null || break
sleep 0.5
done
kill -KILL "${p0}" 2>/dev/null
kill -KILL "${p1}" 2>/dev/null
write_report
rm -f "${PID_FILE}"
printf 'stopped (10G pid=%s, 1G pid=%s)\nreport: %s\n' "${p0}" "${p1}" "${LOG_FILE}"
}
do_status() {
local pids
if pids="$(is_running)"; then
printf 'status : running (10G pid=%s, 1G pid=%s)\n' "${pids% *}" "${pids#* }"
else
printf 'status : stopped\n'
fi
printf 'log : %s\n' "${LOG_FILE}"
printf '10G : %s %s/%s -> %s\n' "${IF_10G}" "${IP_10G}" "${PLEN_10G}" "${TARGET_10G}"
printf '1G : %s %s/%s -> %s\n' "${IF_1G}" "${IP_1G}" "${PLEN_1G}" "${TARGET_1G}"
[ -f "${RAW_10G}" ] && printf '10G recv: %s\n' "$(grep -ac 'bytes from' "${RAW_10G}")"
[ -f "${RAW_1G}" ] && printf '1G recv: %s\n' "$(grep -ac 'bytes from' "${RAW_1G}")"
}
usage() {
cat <<EOF
Usage: ${SCRIPT_NAME} {start|stop|status|summary|clear}
start Configure both NICs, warm up ARP, then ping from BOTH at the same
time and keep accumulating. The previous log is discarded first.
stop Stop both pings and render the report into the log
status Show pids, configured NICs and replies received so far
summary Print just the RESULT lines from the log
clear Remove the log and the raw captures (must be stopped first)
Log file : ${LOG_FILE}
The report holds, per NIC, the last ${TAIL_LINES} entries and the ping
statistics, then "ip -s link show" for both interfaces.
Both NICs are used at once, so management connectivity is in play -- drive
this from the serial console. Edit the User Configurable Section at the top
to change interfaces, addresses, targets or the ping options.
EOF
}
###############################################################################
# Entry point
###############################################################################
case "${1:-}" in
start) do_start ;;
stop) do_stop ;;
status) do_status ;;
summary) grep -a 'RESULT' "${LOG_FILE}" 2>/dev/null || printf 'no results in %s\n' "${LOG_FILE}" ;;
clear)
is_running >/dev/null && die "still running, stop it first"
rm -f "${LOG_FILE}" "${LOG_FILE}".[0-9]* "${RAW_10G}" "${RAW_1G}" "${STATE_FILE}"
printf 'log cleared\n'
;;
-h|--help|"") usage ;;
*) printf '[ERROR] unknown sub-command: %s\n\n' "$1" >&2; usage; exit 1 ;;
esac
@@ -0,0 +1,314 @@
#!/usr/bin/env bash
# =============================================================================
# nfc_polling.sh -- keep reading the NFC tag in the background, and tally it
# =============================================================================
# Version History:
# V1.0.0 2026-08-28 First version. start/stop/status/tail/report/clear/fg,
# wraps blantons_nfc_validate.py. 'report' works while the
# poll is still running.
# =============================================================================
#
# Each round runs blantons_nfc_validate.py once and appends a machine-readable
# verdict line, so 'report' never has to parse the tool's prose:
#
# [2026-08-28 15:04:11] NFC poll #000001 START
# ... the tool's own output ...
# [2026-08-28 15:04:14] NFC poll #000001 RESULT=PASS tag=046F7BD2142290
#
# The verdict comes from the tool's own [PASS] / [FAIL] summary line. If neither
# appears the round is ERROR, not FAIL -- "the tool did not run" and "the tag was
# not read" are different problems and get counted separately.
#
# Usage:
# ./nfc_polling.sh start # background, CLEARS the log first
# ./nfc_polling.sh status # running? pid, rounds so far, log size
# ./nfc_polling.sh tail [n] # last n lines (default 50) -- does NOT follow
# ./nfc_polling.sh report # PASS/FAIL tally; safe to run WHILE polling
# ./nfc_polling.sh stop
# ./nfc_polling.sh clear # delete the log (must be stopped)
# ./nfc_polling.sh fg # run in the foreground, Ctrl-C to stop
#
# Environment overrides:
# NFC_INTERVAL_SEC=10 NFC_TIMEOUT_SEC=60 NFC_MAX_ROUNDS=0 (0 = forever)
# NFC_ARGS="test --debug" NFC_LOG_DIR=<dir> NFC_BIN=<python3>
# =============================================================================
# --- User Configurable Section ----------------------------------------------
NFC_INTERVAL_SEC="${NFC_INTERVAL_SEC:-10}" # gap between rounds
NFC_TIMEOUT_SEC="${NFC_TIMEOUT_SEC:-60}" # kill a round that hangs (0 = never)
NFC_MAX_ROUNDS="${NFC_MAX_ROUNDS:-0}" # 0 = until 'stop'
# Called through python3 on purpose: blantons_nfc_validate.py arrives from git
# (mode 100644) and a Windows/USB copy does not carry the execute bit either, so
# "./blantons_nfc_validate.py" would fail with Permission denied on a fresh DUT.
NFC_PY="${NFC_PY:-python3}"
NFC_SCRIPT_NAME="${NFC_SCRIPT_NAME:-blantons_nfc_validate.py}"
# --debug is kept because that is the output that has been seen on the bench.
# It costs ~1.5 KB per round (~13 MB/day at a 10s interval), which is fine for a
# soak; drop it to "test" if you want a smaller log.
NFC_ARGS="${NFC_ARGS:-test --debug}"
NFC_LOG_DIR="${NFC_LOG_DIR:-}" # empty = <script dir>/log
NFC_LOG_NAME="${NFC_LOG_NAME:-nfc_poll.log}"
# -----------------------------------------------------------------------------
SCRIPT_NAME="$(basename -- "${BASH_SOURCE[0]}")"
SCRIPT_DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)"
SCRIPT_PATH="${SCRIPT_DIR}/${SCRIPT_NAME}"
NFC_SCRIPT="${NFC_SCRIPT:-${SCRIPT_DIR}/${NFC_SCRIPT_NAME}}"
[ -n "${NFC_LOG_DIR}" ] || NFC_LOG_DIR="${SCRIPT_DIR}/log"
LOG_FILE="${NFC_LOG_DIR}/${NFC_LOG_NAME}"
PID_FILE="${NFC_LOG_DIR}/${NFC_LOG_NAME%.log}.pid"
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
return 0
}
# sleep that a signal can cut short, so 'stop' does not wait out the interval
interruptible_sleep() {
local sec="$1"
[ "${sec}" -le 0 ] 2>/dev/null && return 0
sleep "${sec}" &
SLEEP_PID=$!
wait "${SLEEP_PID}" 2>/dev/null
SLEEP_PID=""
return 0
}
is_running() {
local pid
[ -f "${PID_FILE}" ] || return 1
pid="$(cat "${PID_FILE}" 2>/dev/null)"
[ -n "${pid}" ] || return 1
kill -0 "${pid}" 2>/dev/null || return 1
printf '%s' "${pid}"
return 0
}
# --- one round ---------------------------------------------------------------
# Appends START, the tool output, then a RESULT line. Everything about a round
# lands in the log even when the tool dies, so 'report' can still account for it.
run_one_round() {
local n="$1" out rc verdict tag reason
printf '[%s] NFC poll #%06d START\n' "$(ts)" "${n}"
if [ -n "${NFC_TIMEOUT_SEC}" ] && [ "${NFC_TIMEOUT_SEC}" -gt 0 ] 2>/dev/null \
&& command -v timeout >/dev/null 2>&1; then
out="$(timeout "${NFC_TIMEOUT_SEC}" "${NFC_PY}" "${NFC_SCRIPT}" ${NFC_ARGS} 2>&1)"; rc=$?
else
out="$("${NFC_PY}" "${NFC_SCRIPT}" ${NFC_ARGS} 2>&1)"; rc=$?
fi
printf '%s\n' "${out}"
# The tool's own summary decides. Exit status is only the fallback, because
# a tool that never printed a summary did not measure anything.
if printf '%s\n' "${out}" | grep -q '^\[PASS\]'; then
verdict="PASS"
elif printf '%s\n' "${out}" | grep -q '^\[FAIL\]'; then
verdict="FAIL"
elif [ "${rc}" -eq 124 ]; then
verdict="ERROR"; reason="round exceeded NFC_TIMEOUT_SEC=${NFC_TIMEOUT_SEC}s"
else
verdict="ERROR"; reason="no [PASS]/[FAIL] summary, exit=${rc}"
fi
tag="$(printf '%s\n' "${out}" | sed -n 's/.*NFCID1=\([0-9A-Fa-f]*\).*/\1/p' | tail -1)"
[ -n "${reason}" ] || reason="$(printf '%s\n' "${out}" | grep -m1 '^\[\(PASS\|FAIL\)\]' | cut -c8-)"
printf '[%s] NFC poll #%06d RESULT=%s tag=%s reason=%s\n' \
"$(ts)" "${n}" "${verdict}" "${tag:--}" "${reason:--}"
}
main_loop() {
local n=0
trap on_signal INT TERM
printf '[%s] ===== nfc_polling start : %s %s (interval %ss) =====\n' \
"$(ts)" "${NFC_PY}" "${NFC_SCRIPT}" "${NFC_INTERVAL_SEC}"
while [ "${STOP}" -eq 0 ]; do
n=$(( n + 1 ))
run_one_round "${n}"
if [ "${NFC_MAX_ROUNDS}" -gt 0 ] 2>/dev/null && [ "${n}" -ge "${NFC_MAX_ROUNDS}" ]; then
break
fi
[ "${STOP}" -eq 0 ] && interruptible_sleep "${NFC_INTERVAL_SEC}"
done
printf '[%s] ===== nfc_polling stop : %d round(s) =====\n' "$(ts)" "${n}"
}
# --- sub-commands ------------------------------------------------------------
do_start() {
local pid
pid="$(is_running)" && die "already running (pid=${pid})"
[ -r "${NFC_SCRIPT}" ] || die "cannot read ${NFC_SCRIPT}"
command -v "${NFC_PY}" >/dev/null 2>&1 || die "${NFC_PY} not found"
mkdir -p "${NFC_LOG_DIR}" || die "cannot create ${NFC_LOG_DIR}"
# start always begins a fresh log, so a report is about THIS run only
: > "${LOG_FILE}" || die "cannot write ${LOG_FILE}"
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 "${NFC_LOG_DIR}" || die "cannot create ${NFC_LOG_DIR}"
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}"
}
# counts straight out of the log, so it is the same answer running or stopped
_tally() {
local f="$1"
[ -r "${f}" ] || { printf '0 0 0 0\n'; return; }
awk '
/RESULT=PASS/ { p++ }
/RESULT=FAIL/ { f++ }
/RESULT=ERROR/ { e++ }
/ START$/ { s++ }
END { printf "%d %d %d %d\n", s+0, p+0, f+0, e+0 }
' "${f}"
}
do_status() {
local pid started passed failed errored size
if pid="$(is_running)"; then
printf 'status : running (pid=%s)\n' "${pid}"
else
printf 'status : stopped\n'
fi
printf 'log : %s\n' "${LOG_FILE}"
if [ -r "${LOG_FILE}" ]; then
size="$(du -h "${LOG_FILE}" 2>/dev/null | cut -f1)"
read -r started passed failed errored <<< "$(_tally "${LOG_FILE}")"
printf 'size : %s\n' "${size:-?}"
printf 'rounds : %s started (PASS %s / FAIL %s / ERROR %s)\n' \
"${started}" "${passed}" "${failed}" "${errored}"
printf 'last : %s\n' "$(grep -a 'RESULT=' "${LOG_FILE}" | tail -1)"
else
printf 'size : (no log yet)\n'
fi
}
do_report() {
local pid started passed failed errored inflight verdict first last
[ -r "${LOG_FILE}" ] || die "no log at ${LOG_FILE} -- has it been started?"
read -r started passed failed errored <<< "$(_tally "${LOG_FILE}")"
inflight=$(( started - passed - failed - errored ))
echo "===== NFC polling report ====="
printf 'log : %s\n' "${LOG_FILE}"
if pid="$(is_running)"; then
printf 'state : RUNNING (pid=%s) -- this is a snapshot, the count is still moving\n' "${pid}"
else
printf 'state : stopped\n'
fi
first="$(grep -a -m1 ' START$' "${LOG_FILE}" | sed -n 's/^\[\([^]]*\)\].*/\1/p')"
last="$(grep -a 'RESULT=' "${LOG_FILE}" | tail -1 | sed -n 's/^\[\([^]]*\)\].*/\1/p')"
printf 'window : %s -> %s\n' "${first:-?}" "${last:-?}"
echo ""
printf 'rounds : %d\n' "${started}"
printf 'PASS : %d\n' "${passed}"
printf 'FAIL : %d\n' "${failed}"
printf 'ERROR : %d (tool did not produce a verdict)\n' "${errored}"
[ "${inflight}" -gt 0 ] && printf 'in flight: %d (round started, no result yet)\n' "${inflight}"
if [ "${started}" -eq 0 ]; then
verdict="NO DATA"
elif [ "${failed}" -gt 0 ] || [ "${errored}" -gt 0 ]; then
verdict="FAIL"
else
verdict="PASS"
fi
printf 'RESULT : %s\n' "${verdict}"
echo ""
echo "--- tags seen ---"
grep -a 'RESULT=PASS' "${LOG_FILE}" \
| sed -n 's/.* tag=\([0-9A-Fa-f]*\) .*/\1/p' \
| sort | uniq -c | sort -rn | awk '{printf " %-20s %s\n", $2, $1}'
grep -aq 'RESULT=PASS.* tag=[0-9A-Fa-f]' "${LOG_FILE}" || echo " (none)"
if [ "$(( failed + errored ))" -gt 0 ]; then
echo ""
echo "--- last 10 FAIL / ERROR ---"
grep -aE 'RESULT=(FAIL|ERROR)' "${LOG_FILE}" | tail -10 | sed 's/^/ /'
fi
echo ""
[ "${verdict}" = "FAIL" ] && return 2
return 0
}
usage() {
cat <<USAGE
${SCRIPT_NAME} - background NFC tag polling (wraps ${NFC_SCRIPT_NAME})
start run in the background; CLEARS the log first
stop stop it
status running? pid, rounds so far, last result
tail [n] last n lines of the log (default 50) -- does NOT follow
report PASS/FAIL tally -- safe to run WHILE polling
clear delete the log (must be stopped)
fg run in the foreground, Ctrl-C to stop
interval ${NFC_INTERVAL_SEC}s per-round timeout ${NFC_TIMEOUT_SEC}s rounds ${NFC_MAX_ROUNDS} (0 = forever)
log ${LOG_FILE}
A round is PASS or FAIL from the tool's own summary line; ERROR means it
produced neither, which is a different problem from a tag that would not read.
USAGE
}
case "${1:-}" in
start) do_start ;;
stop) do_stop ;;
status) do_status ;;
report) do_report ;;
# deliberately NOT 'tail -f': that never returns, and a TTL macro waiting on
# the prompt would hang there forever.
tail) [ -r "${LOG_FILE}" ] || die "no log at ${LOG_FILE}"
tail -n "${2:-50}" "${LOG_FILE}" ;;
clear)
is_running >/dev/null && die "still running, stop it first"
rm -f "${LOG_FILE}"
printf 'log cleared\n'
;;
fg) main_loop ;;
__daemon) do_daemon ;;
-h|--help|"") usage ;;
*) printf '[ERROR] unknown sub-command: %s\n\n' "$1" >&2; usage; exit 1 ;;
esac
@@ -0,0 +1,431 @@
#!/bin/bash
###############################################################################
# port_prbs_monitor.sh
#
# Version : V1.0.0
# Author : ETWen
# Date : 20260826
#
# Purpose : Run a PRBS test on the two 100G uplinks AT THE SAME TIME and keep
# polling until stopped. Each port gets its own background worker:
#
# setup : sfputil lpmode off <port>
# phy diag <phy> prbs set <poly>
# phy diag <phy> prbsstat STArt Interval=<n>
# poll : phy diag <phy> prbs get <- PASS/FAIL comes from here
# phydiag <phy> prbsstat Ber <- recorded only
# stop : phydiag <phy> prbsstat STOp
# phydiag <phy> prbs clear
#
# Every command and its full output goes to that port's raw log.
# `stop` tears the test down and renders the report; `report`
# prints the PASS/FAIL tally per port.
#
# Notes : - A poll counts as PASS only when the output contains "PRBS OK!".
# bcmcmd exits 0 even when the BCM shell rejects a command, so the
# exit status cannot be used -- only the output can.
# - bcmcmd is always run with </dev/null. Without it, it inherits the
# polling loop's stdin and eats it, and the loop runs once.
#
# Version History
# V1.0.0 20260826 Initial Version
###############################################################################
set -u
###############################################################################
# User Configurable Section -- normally this is the only part you need to edit
###############################################################################
# --- port A ---
PORT_A_NAME="Ethernet513"
PORT_A_UNIT=0 # bcmcmd -n <unit>
PORT_A_PHY=268 # phy diag <phy>
# --- port B ---
PORT_B_NAME="Ethernet514"
PORT_B_UNIT=1
PORT_B_PHY=268
# Which ports `start` runs when no argument is given: both | a | b
# Override per run: start a / start b / start both
# Running one at a time is how you tell a genuine port fault from the DUT not
# coping with two PRBS streams at once.
PORTS="both"
# --- PRBS ---
PRBS_POLY="p=3" # passed to "prbs set"
PRBS_STAT_INTERVAL=5 # passed to "prbsstat STArt Interval="
POLL_INTERVAL_SEC=10 # seconds between polls
PASS_PATTERN="PRBS OK!" # a poll is PASS only if the output contains this
# Seconds to wait after the setup commands before the first poll, so the link
# has settled and the counters mean something.
SETTLE_SEC=10
# Stop after this many polls per port. 0 = run until stopped manually.
MAX_POLLS=0
# How many recent poll blocks the report shows per port.
TAIL_ENTRIES=20
# Prefix for privileged commands; empty because this already runs as root.
SUDO=""
BCMCMD="bcmcmd"
# Logging
LOG_DIR="" # empty = <script directory>/log
LOG_NAME="port_prbs.log"
# What `start` does with the log left behind by the previous run:
# new | archive | append
START_LOG_MODE="new"
###############################################################################
# Internal
###############################################################################
SCRIPT_NAME="$(basename -- "${BASH_SOURCE[0]}")"
SCRIPT_DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)"
SCRIPT_PATH="${SCRIPT_DIR}/${SCRIPT_NAME}"
[ -n "${LOG_DIR}" ] || LOG_DIR="${SCRIPT_DIR}/log"
LOG_FILE="${LOG_DIR}/${LOG_NAME}"
BASE="${LOG_DIR}/${LOG_NAME%.log}"
PID_FILE="${BASE}.pid"
STATE_FILE="${BASE}.state"
RAW_A="${BASE}_${PORT_A_NAME}.raw"
RAW_B="${BASE}_${PORT_B_NAME}.raw"
ts() { date '+%Y-%m-%d %H:%M:%S'; }
# port_tally <name> <raw> -> "<polls> <pass> <fail> <verdict>"
# A raw without a "PRBS START" line means the port was not run this session,
# which is SKIP -- reporting it as FAIL would make a deliberate single-port
# run look like half the hardware is broken.
port_tally() {
local name="$1" raw="$2" pass fail polls verdict
if [ ! -s "${raw}" ] || ! grep -aq "PRBS START ${name}" "${raw}" 2>/dev/null; then
printf '0 0 0 SKIP'; return 0
fi
pass=$(grep -ac "PRBS ${name} poll=[0-9]* PASS" "${raw}" 2>/dev/null); pass=${pass:-0}
fail=$(grep -ac "PRBS ${name} poll=[0-9]* FAIL" "${raw}" 2>/dev/null); fail=${fail:-0}
polls=$(( pass + fail ))
if [ "${polls}" -gt 0 ] && [ "${fail}" -eq 0 ]; then verdict=PASS; else verdict=FAIL; fi
printf '%s %s %s %s' "${polls}" "${pass}" "${fail}" "${verdict}"
}
die() { printf '[ERROR] %s\n' "$*" >&2; exit 1; }
# bcm <unit> <dsh command> -- output on stdout, never trusts the exit status.
# </dev/null so it cannot consume the caller's stdin.
bcm() {
local unit="$1" cmd="$2"
${SUDO} "${BCMCMD}" -n "${unit}" -c "dsh -c \"${cmd}\"" </dev/null 2>&1
}
# run_step <unit> <dsh command>
# The command and its full output are written to the LOG via stderr, because
# the worker has stderr pointed at the raw file. Only the output itself goes to
# stdout, so `out="$(run_step ...)"` captures the output without swallowing the
# log lines -- writing both to stdout would have put the whole record inside
# the variable and left the log with nothing but the poll verdicts.
run_step() {
local unit="$1" cmd="$2" out
out="$(bcm "${unit}" "${cmd}")"
{
printf '[%s] CMD : bcmcmd -n %s -c '\''dsh -c "%s"'\''\n' "$(ts)" "${unit}" "${cmd}"
printf '%s\n' "${out}"
} >&2
printf '%s' "${out}"
}
# Echo "<pid_a> <pid_b>" and return 0 while at least one worker is alive. A
# port that was not started this run is recorded as "-", so a single-port run
# is a first-class case rather than a half-broken two-port one.
is_running() {
local pa pb alive=0
[ -f "${PID_FILE}" ] || return 1
pa="$(sed -n '1p' "${PID_FILE}" 2>/dev/null)"; pa="${pa:--}"
pb="$(sed -n '2p' "${PID_FILE}" 2>/dev/null)"; pb="${pb:--}"
[[ "${pa}" =~ ^[0-9]+$ ]] && kill -0 "${pa}" 2>/dev/null && alive=1
[[ "${pb}" =~ ^[0-9]+$ ]] && kill -0 "${pb}" 2>/dev/null && alive=1
[ "${alive}" -eq 1 ] || return 1
printf '%s %s' "${pa}" "${pb}"
return 0
}
###############################################################################
# Worker -- one per port, launched by `start`
###############################################################################
WORKER_STOP=0
worker_on_signal() { WORKER_STOP=1; }
# __worker <name> <unit> <phy> <rawfile>
do_worker() {
local name="$1" unit="$2" phy="$3" raw="$4"
local n=0 pass=0 fail=0 out
exec >>"${raw}" 2>&1
trap worker_on_signal INT TERM
printf '#############################################################\n'
printf '[%s] PRBS START %s (unit %s, phy %s) poly=%s interval=%ss poll=%ss\n' \
"$(ts)" "${name}" "${unit}" "${phy}" "${PRBS_POLY}" \
"${PRBS_STAT_INTERVAL}" "${POLL_INTERVAL_SEC}"
printf '#############################################################\n'
# --- setup ---
printf '[%s] CMD : sfputil lpmode off %s\n' "$(ts)" "${name}"
${SUDO} sfputil lpmode off "${name}" </dev/null 2>&1
run_step "${unit}" "phy diag ${phy} prbs set ${PRBS_POLY}" >/dev/null
run_step "${unit}" "phy diag ${phy} prbsstat STArt Interval=${PRBS_STAT_INTERVAL}" >/dev/null
sleep "${SETTLE_SEC}"
# --- poll ---
while [ "${WORKER_STOP}" -eq 0 ]; do
n=$(( n + 1 ))
printf -- '---------- %s POLL %d @ %s ----------\n' "${name}" "${n}" "$(ts)"
out="$(run_step "${unit}" "phy diag ${phy} prbs get")"
# Ber is recorded for the log only; it does not decide the verdict.
run_step "${unit}" "phydiag ${phy} prbsstat Ber" >/dev/null
if printf '%s' "${out}" | grep -qF "${PASS_PATTERN}"; then
pass=$(( pass + 1 ))
printf '[%s] PRBS %s poll=%d PASS\n' "$(ts)" "${name}" "${n}"
else
fail=$(( fail + 1 ))
printf '[%s] PRBS %s poll=%d FAIL\n' "$(ts)" "${name}" "${n}"
fi
[ "${MAX_POLLS}" -gt 0 ] && [ "${n}" -ge "${MAX_POLLS}" ] && break
[ "${WORKER_STOP}" -eq 0 ] || break
sleep "${POLL_INTERVAL_SEC}"
done
# --- teardown ---
run_step "${unit}" "phydiag ${phy} prbsstat STOp" >/dev/null
run_step "${unit}" "phydiag ${phy} prbs clear" >/dev/null
printf '[%s] PRBS STOP %s polls=%d PASS=%d FAIL=%d\n' \
"$(ts)" "${name}" "${n}" "${pass}" "${fail}"
}
###############################################################################
# Report
###############################################################################
# render_port <name> <unit> <phy> <raw>
render_port() {
local name="$1" unit="$2" phy="$3" raw="$4"
local t polls pass fail verdict
t="$(port_tally "${name}" "${raw}")"
polls="$(printf '%s' "${t}" | awk '{print $1}')"
pass="$(printf '%s' "${t}" | awk '{print $2}')"
fail="$(printf '%s' "${t}" | awk '{print $3}')"
verdict="$(printf '%s' "${t}" | awk '{print $4}')"
if [ "${verdict}" = "SKIP" ]; then
printf -- '----- %s (unit %s, phy %s) : not run this session -----\n\n' \
"${name}" "${unit}" "${phy}"
printf '[%s] RESULT %-12s polls=%-4s PASS=%-4s FAIL=%-4s %s\n\n' \
"$(ts)" "${name}" "${polls}" "${pass}" "${fail}" "${verdict}"
return 0
fi
printf -- '----- %s (unit %s, phy %s) : last %s poll blocks -----\n' \
"${name}" "${unit}" "${phy}" "${TAIL_ENTRIES}"
grep -aE "^-{10} ${name} POLL|PRBS ${name} poll=|prbsstat Ber|^ *[0-9]+ *: " "${raw}" 2>/dev/null \
| tail -n $(( TAIL_ENTRIES * 3 ))
printf '\n'
printf '[%s] RESULT %-12s polls=%-4s PASS=%-4s FAIL=%-4s %s\n\n' \
"$(ts)" "${name}" "${polls}" "${pass}" "${fail}" "${verdict}"
}
write_report() {
local started
started="$(sed -n '1p' "${STATE_FILE}" 2>/dev/null)"
{
printf '#############################################################\n'
printf '[%s] 100G PRBS report\n' "$(ts)"
printf ' started : %s\n' "${started:-unknown}"
printf ' stopped : %s\n' "$(ts)"
printf ' %s : unit %s, phy %s\n' "${PORT_A_NAME}" "${PORT_A_UNIT}" "${PORT_A_PHY}"
printf ' %s : unit %s, phy %s\n' "${PORT_B_NAME}" "${PORT_B_UNIT}" "${PORT_B_PHY}"
printf ' poly=%s prbsstat Interval=%s poll every %ss\n' \
"${PRBS_POLY}" "${PRBS_STAT_INTERVAL}" "${POLL_INTERVAL_SEC}"
printf ' a poll is PASS only when the output contains "%s"\n' "${PASS_PATTERN}"
printf '#############################################################\n\n'
render_port "${PORT_A_NAME}" "${PORT_A_UNIT}" "${PORT_A_PHY}" "${RAW_A}"
render_port "${PORT_B_NAME}" "${PORT_B_UNIT}" "${PORT_B_PHY}" "${RAW_B}"
# Taken after prbsstat STOp + prbs clear, so this is the recovered
# state. During the test the same command would have reported DOWN.
printf -- '--- show interfaces status %s,%s (after prbsstat STOp + prbs clear) ---\n' \
"${PORT_A_NAME}" "${PORT_B_NAME}"
${SUDO} show interfaces status "${PORT_A_NAME},${PORT_B_NAME}" </dev/null 2>&1
} >> "${LOG_FILE}"
}
###############################################################################
# Sub-commands
###############################################################################
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}"
fi ;;
new)
[ -f "${LOG_FILE}" ] && { : > "${LOG_FILE}" || die "cannot truncate ${LOG_FILE}"; } ;;
*) die "invalid START_LOG_MODE: ${START_LOG_MODE}" ;;
esac
}
# do_start [both|a|b|<port name>]
do_start() {
local which="${1:-${PORTS}}" pids pa="-" pb="-" run_a=0 run_b=0
case "${which}" in
both|BOTH|all) run_a=1; run_b=1 ;;
a|A|"${PORT_A_NAME}") run_a=1 ;;
b|B|"${PORT_B_NAME}") run_b=1 ;;
*) die "unknown port selector '${which}' (use: both | a | b | ${PORT_A_NAME} | ${PORT_B_NAME})" ;;
esac
pids="$(is_running)" && die "already running (pids=${pids})"
command -v "${BCMCMD}" >/dev/null 2>&1 || die "${BCMCMD} not found"
mkdir -p "${LOG_DIR}" || die "cannot create ${LOG_DIR}"
prepare_log_on_start
# Only the selected ports are truncated. A raw with no "PRBS START" line is
# what the report uses to tell "not run this session" from "ran and failed".
[ "${run_a}" -eq 1 ] && : > "${RAW_A}"
[ "${run_b}" -eq 1 ] && : > "${RAW_B}"
printf '%s\n' "$(ts)" > "${STATE_FILE}"
# One worker per selected port; with both, they run at the same time.
# Invoked through bash so the file does not need the execute bit.
if [ "${run_a}" -eq 1 ]; then
setsid bash "${SCRIPT_PATH}" __worker \
"${PORT_A_NAME}" "${PORT_A_UNIT}" "${PORT_A_PHY}" "${RAW_A}" >/dev/null 2>&1 &
pa=$!
disown "${pa}" 2>/dev/null
fi
if [ "${run_b}" -eq 1 ]; then
setsid bash "${SCRIPT_PATH}" __worker \
"${PORT_B_NAME}" "${PORT_B_UNIT}" "${PORT_B_PHY}" "${RAW_B}" >/dev/null 2>&1 &
pb=$!
disown "${pb}" 2>/dev/null
fi
printf '%s\n%s\n' "${pa}" "${pb}" > "${PID_FILE}"
sleep 1
[ "${run_a}" -eq 1 ] && { kill -0 "${pa}" 2>/dev/null || die "${PORT_A_NAME} worker failed, see ${RAW_A}"; }
[ "${run_b}" -eq 1 ] && { kill -0 "${pb}" 2>/dev/null || die "${PORT_B_NAME} worker failed, see ${RAW_B}"; }
printf 'started (%s pid=%s, %s pid=%s)\nlog: %s\n' \
"${PORT_A_NAME}" "${pa}" "${PORT_B_NAME}" "${pb}" "${LOG_FILE}"
# Deliberately NOT printing "show interfaces status" here. With PRBS armed
# the link is out of normal operation and reports DOWN, which reads as a
# failure to anyone glancing at the console. The status is shown in the
# report instead, once PRBS has been cleared.
}
do_stop() {
local pids pa pb i
pids="$(is_running)" || { printf 'not running\n'; rm -f "${PID_FILE}"; return 0; }
pa="${pids% *}"; pb="${pids#* }"
# TERM lets each worker finish its poll and run prbsstat STOp / prbs clear.
[[ "${pa}" =~ ^[0-9]+$ ]] && kill -TERM "${pa}" 2>/dev/null
[[ "${pb}" =~ ^[0-9]+$ ]] && kill -TERM "${pb}" 2>/dev/null
for (( i = 0; i < 120; i++ )); do
kill -0 "${pa}" 2>/dev/null || kill -0 "${pb}" 2>/dev/null || break
sleep 0.5
done
if kill -0 "${pa}" 2>/dev/null || kill -0 "${pb}" 2>/dev/null; then
printf 'workers did not exit in time, sending SIGKILL (PRBS may be left running)\n' >&2
kill -KILL "${pa}" 2>/dev/null
kill -KILL "${pb}" 2>/dev/null
fi
write_report
rm -f "${PID_FILE}"
printf 'stopped\nreport: %s\n' "${LOG_FILE}"
}
do_report() {
local name raw pass fail polls verdict
printf '%-14s %-8s %-8s %-8s %s\n' PORT POLLS PASS FAIL RESULT
printf '%-14s %-8s %-8s %-8s %s\n' -------------- -------- -------- -------- ------
for spec in "${PORT_A_NAME}:${RAW_A}" "${PORT_B_NAME}:${RAW_B}"; do
name="${spec%%:*}"; raw="${spec#*:}"
set -- $(port_tally "${name}" "${raw}")
printf '%-14s %-8s %-8s %-8s %s\n' "${name}" "$1" "$2" "$3" "$4"
done
}
do_status() {
local pids
if pids="$(is_running)"; then
printf 'status : running (%s pid=%s, %s pid=%s)\n' \
"${PORT_A_NAME}" "${pids% *}" "${PORT_B_NAME}" "${pids#* }"
else
printf 'status : stopped\n'
fi
printf 'log : %s\n' "${LOG_FILE}"
do_report
}
usage() {
cat <<EOF
Usage: ${SCRIPT_NAME} {start [both|a|b]|stop|status|report|clear}
start Set up PRBS and poll in the background. With no argument it runs
\$PORTS (currently "${PORTS}"); "a" or "b" runs that port alone,
which is how you tell a genuine port fault from the DUT not coping
with two PRBS streams at once.
start both ports at the same time
start a ${PORT_A_NAME} only
start b ${PORT_B_NAME} only
Port status is NOT shown here: with PRBS armed the link reports
DOWN, which looks like a failure. See the report instead.
stop Stop polling, run prbsstat STOp + prbs clear on both ports, and
render the report into the log
report Print the PASS/FAIL tally per port
status Show worker pids plus the current tally
clear Remove the log and raw captures (must be stopped first)
Log file : ${LOG_FILE}
Raw : ${RAW_A}
${RAW_B}
A poll is PASS only when "phy diag <phy> prbs get" reports "${PASS_PATTERN}".
prbsstat Ber is captured alongside every poll but does not decide the verdict.
A port that was not run in this session reports SKIP, not FAIL.
EOF
}
###############################################################################
# Entry point
###############################################################################
case "${1:-}" in
start) shift; do_start "${1:-${PORTS}}" ;;
stop) do_stop ;;
status) do_status ;;
report) do_report ;;
clear)
is_running >/dev/null && die "still running, stop it first"
rm -f "${LOG_FILE}" "${LOG_FILE}".[0-9]* "${RAW_A}" "${RAW_B}" "${STATE_FILE}"
printf 'log cleared\n' ;;
__worker)
shift
do_worker "$1" "$2" "$3" "$4" ;;
-h|--help|"") usage ;;
*) printf '[ERROR] unknown sub-command: %s\n\n' "$1" >&2; usage; exit 1 ;;
esac
@@ -0,0 +1,203 @@
#!/bin/bash
###############################################################################
# usb_target.sh
#
# Version : V1.1.0
# Author : ETWen
# Date : 20260825
# Purpose : Auto-detect an inserted USB mass-storage device and report either
# its device node, its mount point, or both.
#
# stdout : requested value(s) only, for $(...) capture
# stderr : diagnostic messages
#
# Usage : usb_target.sh [-o dev|mnt|both|id] [-n] [-r seconds]
#
# -o dev print partition device node (e.g. /dev/sda1)
# -o mnt print mount point (e.g. /mnt/usb) [default]
# -o both print "<dev> <mnt>" on one line
# -o id print stable by-id path (/dev/disk/by-id/...)
# -n detect only, do not mount
# -r sec udev enumeration wait, default 15
#
# Exit : 0 success
# 1 no USB mass-storage found
# 2 multiple USB disks detected (refuse to guess)
# 3 no usable filesystem on the device
# 4 mount failed
# 5 mounted but not writable
#
# Version History
# V1.0.0 20260825 Initial Version
# V1.1.0 20260825 Add -o/-n/-r options; expose device node and by-id path
###############################################################################
set -u
MNT_BASE="/mnt/usb"
RETRY=15
OUTPUT="mnt"
DO_MOUNT=1
log() { echo "[usb] $*" >&2; } # diagnostics go to stderr; stdout stays clean
while getopts "o:nr:h" opt; do
case "$opt" in
o) OUTPUT="$OPTARG" ;;
n) DO_MOUNT=0 ;;
r) RETRY="$OPTARG" ;;
h) sed -n '3,30p' "$0" >&2; exit 0 ;;
*) log "invalid option"; exit 1 ;;
esac
done
shift $((OPTIND - 1))
case "$OUTPUT" in
dev|mnt|both|id) ;;
*) log "invalid -o value: $OUTPUT"; exit 1 ;;
esac
# "-o dev" / "-o id" alone does not require mounting.
[ "$OUTPUT" = "dev" ] && [ "$DO_MOUNT" = "1" ] && DO_MOUNT=0
[ "$OUTPUT" = "id" ] && [ "$DO_MOUNT" = "1" ] && DO_MOUNT=0
#------------------------------------------------------------------------------
# Identify the physical disk(s) backing rootfs / /host so they can be excluded.
# Needed because some platforms boot from a USB DOM, which also reports TRAN=usb.
#------------------------------------------------------------------------------
get_system_disk() {
local src
for mp in /host / ; do
src=$(findmnt -no SOURCE "$mp" 2>/dev/null) || continue
lsblk -no PKNAME "$src" 2>/dev/null | head -1
done | sort -u
}
#------------------------------------------------------------------------------
# List candidate USB disks (whole devices, not partitions).
#------------------------------------------------------------------------------
find_usb_disks() {
local sysdisks; sysdisks=$(get_system_disk)
lsblk -dn -o NAME,TYPE,TRAN,RM 2>/dev/null | while read -r name type tran rm; do
[ "$type" = "disk" ] || continue
case "$name" in loop*|ram*|dm-*|sr*|zram*) continue ;; esac
# Older util-linux may not expose the TRAN column; fall back to sysfs.
if [ "$tran" != "usb" ]; then
readlink -f "/sys/block/$name/device" 2>/dev/null | grep -q '/usb[0-9]' || continue
[ "$rm" = "1" ] || continue
fi
echo "$sysdisks" | grep -qx "$name" && { log "skip $name (system disk)"; continue; }
echo "/dev/$name"
done
}
#------------------------------------------------------------------------------
# Pick a mountable partition from a disk; fall back to the whole device for
# superfloppy layouts (filesystem written directly, no partition table).
#------------------------------------------------------------------------------
pick_partition() {
local disk="$1" p
p=$(lsblk -ln -o NAME,TYPE,FSTYPE "$disk" | \
awk '$2=="part" && $3!="" {print "/dev/"$1; exit}')
[ -n "$p" ] && { echo "$p"; return; }
[ -n "$(lsblk -dn -o FSTYPE "$disk")" ] && echo "$disk"
}
#------------------------------------------------------------------------------
# Resolve a device node to a stable /dev/disk/by-id path, if one exists.
#------------------------------------------------------------------------------
resolve_by_id() {
local dev="$1" real link
real=$(readlink -f "$dev")
for link in /dev/disk/by-id/*; do
[ -e "$link" ] || continue
case "$link" in *-part*|*) ;; esac
[ "$(readlink -f "$link")" = "$real" ] && { echo "$link"; return 0; }
done
return 1
}
#------------------------------------------------------------------------------
# Main
#------------------------------------------------------------------------------
disks=""
for i in $(seq 1 "$RETRY"); do
disks=$(find_usb_disks)
[ -n "$disks" ] && break
sleep 1
done
[ -z "$disks" ] && { log "no USB mass-storage found"; exit 1; }
n=$(echo "$disks" | wc -l)
if [ "$n" -gt 1 ]; then
log "multiple USB disks detected, refuse to guess:"
log "$disks"
exit 2
fi
disk="$disks"
part=$(pick_partition "$disk")
[ -z "$part" ] && { log "$disk has no usable filesystem"; exit 3; }
fstype=$(lsblk -no FSTYPE "$part")
log "found $part (disk=$disk fstype=$fstype)"
# --- device-node only: no mount needed ---------------------------------------
if [ "$OUTPUT" = "dev" ]; then
echo "$part"
exit 0
fi
if [ "$OUTPUT" = "id" ]; then
if byid=$(resolve_by_id "$part"); then
echo "$byid"
exit 0
fi
log "no by-id path for $part, falling back to device node"
echo "$part"
exit 0
fi
# --- mount point required ----------------------------------------------------
mp=$(lsblk -no MOUNTPOINT "$part" | head -1)
if [ -z "$mp" ] && [ "$DO_MOUNT" = "0" ]; then
log "$part is not mounted and -n was given"
exit 4
fi
if [ -z "$mp" ]; then
log "mounting $part -> $MNT_BASE"
mkdir -p "$MNT_BASE"
# exfat/ntfs are not always built into the platform kernel.
case "$fstype" in
exfat) grep -qw exfat /proc/filesystems || modprobe exfat 2>/dev/null ;;
ntfs) grep -qw ntfs3 /proc/filesystems || modprobe ntfs3 2>/dev/null ;;
esac
if ! mount -o rw,noatime "$part" "$MNT_BASE" 2>/dev/null; then
log "mount failed (fstype=$fstype)"
exit 4
fi
mp="$MNT_BASE"
# Verify it is actually writable (read-only media, dirty FAT, or full device).
if ! touch "$mp/.wtest" 2>/dev/null; then
log "mounted read-only or no space left"
umount "$mp"
exit 5
fi
rm -f "$mp/.wtest"
else
log "$part already mounted at $mp"
fi
if [ "$OUTPUT" = "both" ]; then
echo "$part $mp"
else
echo "$mp"
fi
exit 0
+13 -3
View File
@@ -1,12 +1,22 @@
;================================================================ ;================================================================
; 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
;================================================================
; FAN_SPEED
;================================================================
FAN_SPEED = 100
;================================================================
; Switch Unit Population
;================================================================
SWB_UNIT0 = 1 ; 1 = this DUT has switch unit 0, wait for it ; 0 = skip
SWB_UNIT1 = 1 ; 1 = this DUT has switch unit 1, wait for it ; 0 = skip
;================================================================ ;================================================================
; Prompt Definitions ; Prompt Definitions
;================================================================ ;================================================================
@@ -16,3 +16,8 @@ wait prompt_sonic_root
sendln "show platform leak status" sendln "show platform leak status"
wait prompt_sonic_root wait prompt_sonic_root
sendln "show platform leak channels" sendln "show platform leak channels"
wait prompt_sonic_root
sendln "tpm-dut-test fru"
wait prompt_sonic_root
sendln "tpm-dut-test tpm-read"
+8 -5
View File
@@ -1,8 +1,11 @@
; All Event
wait prompt_sonic_root wait prompt_sonic_root
sendln "date" sendln "dmesg -T | grep -Ei 'error|fail(ed|ure)?|fault|critical|panic|timeout'"
; i2c event
wait prompt_sonic_root wait prompt_sonic_root
sendln "dmesg | grep -i error" sendln "dmesg -T | grep -Ei 'error|fail(ed|ure)?|fault|critical|panic|timeout' | grep -i i2c"
; Clear Event
wait prompt_sonic_root wait prompt_sonic_root
sendln "dmesg | grep -i fail" sendln "dmesg -C"
wait prompt_sonic_root
sendln "dmesg | grep -i warning"
+105
View File
@@ -0,0 +1,105 @@
; =============================================================================
; File : utils/wait_init.ttl
; Version : V3.0.0
; Date : 2026-08-21
; Author : ETWen
; =============================================================================
; Wait until every switch unit ENABLED below reports at least WT_MIN ports up:
; bcmcmd -n <u> -c ps | grep -w up | wc -l
;
; The DUT is not always fully populated. Which units exist is declared ONCE in
; config.ttl, so the traffic blocks in Script A/B/C and this wait agree:
; both units -> SWB_UNIT0 = 1 , SWB_UNIT1 = 1
; unit 0 only -> SWB_UNIT0 = 1 , SWB_UNIT1 = 0
; unit 1 only -> SWB_UNIT0 = 0 , SWB_UNIT1 = 1
; no unit -> SWB_UNIT0 = 0 , SWB_UNIT1 = 0 (bypass, returns immediately)
;
; Enter : prompt of the previous command is NOT consumed.
; Exit : a command has been sent, prompt NOT consumed (caller does the wait).
;
; Why the "PORTS0=" marker: TTL can only test for a string, so reading a count
; means capturing it. Wrapping the number in an echo gives waitregex a unique
; anchor, and the ECHO of the command cannot false-match -- it reads
; "PORTS0=$(bcmcmd ..." and the pattern requires a digit right after the "=".
;
; Version History:
; V1.0.0 2026-08-21 Initial Version (waited on "show platform temperature"
; until it stopped reporting "Thermal Not detected")
; V2.0.0 2026-08-21 Wait on the bcmcmd port-up count of BOTH switch units
; instead of the thermal sensors; proceed only when both
; are greater than WT_MIN (216).
; V3.0.0 2026-08-21 Add WT_UNIT0 / WT_UNIT1 so a partly populated DUT can
; be declared: wait on the enabled units only, and bypass
; entirely when neither is set.
; V3.1.0 2026-08-21 Take the population from SWB_UNIT0 / SWB_UNIT1 in
; config.ttl instead of local copies, so this wait and the
; traffic blocks cannot drift apart.
; V3.1.1 2026-08-25 WT_INTERVAL 10 -> 60. Fix the WT_MIN comments: the test
; has been `< WT_MIN` (at least) since the threshold was
; corrected, but the text still said "more than", which is
; the off-by-one that once hung Script A.
; =============================================================================
; ---- which units to wait for: SWB_UNIT0 / SWB_UNIT1, set in config.ttl ------
WT_MIN = 216 ; an enabled unit must report AT LEAST this many ports up
; (216 = 108 loopback pairs x 2, i.e. every cabled port)
WT_INTERVAL = 60 ; seconds between polls
; -----------------------------------------------------------------------------
timeout = 60 ; per-wait cap; bcmcmd ps is not instant
wait prompt_sonic_root
; nothing declared -> bypass
if SWB_UNIT0 = 0 then
if SWB_UNIT1 = 0 then
goto NO_UNITS
endif
endif
:WAIT_PORTS
wt_ok = 1
if SWB_UNIT0 = 1 then
wt_up0 = 0
sendln "echo PORTS0=$(bcmcmd -n 0 -c ps | grep -w up | wc -l)"
waitregex "PORTS0=[ ]*([0-9]+)"
if result = 1 then
str2int wt_up0 groupmatchstr1
endif
wait prompt_sonic_root
if wt_up0 < WT_MIN then
wt_ok = 0
endif
endif
if SWB_UNIT1 = 1 then
wt_up1 = 0
sendln "echo PORTS1=$(bcmcmd -n 1 -c ps | grep -w up | wc -l)"
waitregex "PORTS1=[ ]*([0-9]+)"
if result = 1 then
str2int wt_up1 groupmatchstr1
endif
wait prompt_sonic_root
if wt_up1 < WT_MIN then
wt_ok = 0
endif
endif
if wt_ok = 1 then
goto PORTS_OK
endif
pause WT_INTERVAL
goto WAIT_PORTS
; ---- exits ------------------------------------------------------------------
; Both leave exactly one prompt unconsumed, so the caller's next
; "wait prompt_sonic_root" has something to match.
:NO_UNITS
sendln "echo wait_init:no-switch-unit-declared,bypassing-port-wait"
goto DONE
:PORTS_OK
sendln ""
:DONE
timeout = 0
+63
View File
@@ -0,0 +1,63 @@
echo PORTS0=$(bcmcmd -n 0 -c ps | grep -w up | wc -l)
echo PORTS0=$(bcmcmd -n 1 -c ps | grep -w up | wc -l)
show interfaces status Ethernet513,Ethernet514
//100G Port 513
sudo sfputil lpmode off Ethernet513
bcmcmd -n 0 -c 'dsh -c "phy diag 268 prbs set p=3"'
bcmcmd -n 0 -c 'dsh -c "phy diag 268 prbsstat STArt Interval=5"'
bcmcmd -n 0 -c 'dsh -c "phy diag 268 prbs get"'
bcmcmd -n 0 -c 'dsh -c "phydiag 268 prbsstat Ber"'
bcmcmd -n 0 -c 'dsh -c "phydiag 268 prbsstat STOp"'
bcmcmd -n 0 -c 'dsh -c "phydiag 268 prbs clear"'
//100G Port 514
sudo sfputil lpmode off Ethernet514
bcmcmd -n 1 -c 'dsh -c "phy diag 268 prbs set p=3"'
bcmcmd -n 1 -c 'dsh -c "phy diag 268 prbsstat STArt Interval=5"'
bcmcmd -n 1 -c 'dsh -c "phy diag 268 prbs get"'
bcmcmd -n 1 -c 'dsh -c "phydiag 268 prbsstat Ber"'
bcmcmd -n 1 -c 'dsh -c "phydiag 268 prbsstat STOp"'
bcmcmd -n 1 -c 'dsh -c "phydiag 268 prbs clear"'
//100G Port 513
sudo sfputil lpmode off Ethernet513
sudo sfputil lpmode off Ethernet514
bcmcmd -n 0 -c 'dsh -c "phy diag 268 prbs set p=3"'
sleep 1
bcmcmd -n 1 -c 'dsh -c "phy diag 268 prbs set p=3"'
bcmcmd -n 0 -c 'dsh -c "phy diag 268 prbsstat STArt Interval=5"'
sleep 1
bcmcmd -n 1 -c 'dsh -c "phy diag 268 prbsstat STArt Interval=5"'
bcmcmd -n 0 -c 'dsh -c "phy diag 268 prbs get"'
sleep 1
bcmcmd -n 1 -c 'dsh -c "phy diag 268 prbs get"'
bcmcmd -n 0 -c 'dsh -c "phydiag 268 prbsstat Ber"'
sleep 1
bcmcmd -n 1 -c 'dsh -c "phydiag 268 prbsstat Ber"'
bcmcmd -n 0 -c 'dsh -c "phydiag 268 prbsstat STOp"'
sleep 1
bcmcmd -n 1 -c 'dsh -c "phydiag 268 prbsstat STOp"'
bcmcmd -n 0 -c 'dsh -c "phydiag 268 prbs clear"'
sleep 1
bcmcmd -n 1 -c 'dsh -c "phydiag 268 prbs clear"'
+12
View File
@@ -0,0 +1,12 @@
//Init
bcmcmd -n 0 -c 'mirror init'
bcmcmd -n 0 -c 'mirror port ce0 Mode=Ingress DestPort=ce0'
bcmcmd -n 0 -c 'vlan create 138'
bcmcmd -n 0 -c 'vlan add 138 pbm=ce0'
//Start
bcmcmd -n 0 -c 'tx 100 length=512 VLantag=138'
//stop
bcmcmd -n 0 -c 'mirror port ce0 Mode=Off'
bcmcmd -n 0 -c 'vlan remove 138 pbm=ce0'
+900
View File
@@ -0,0 +1,900 @@
bcmcmd -n 0 -c " dsh -c 'phydiag 1-24,40-96,112-168,184-240,256-259,270-308,310-327,342-380,382-399,414-452,454-471,486-524,526-543,558-565 prbs set p=3' "
bcmcmd -n 0 -c " dsh -c 'phydiag 1-24,40-96,112-168,184-240,256-259,270-308,310-327,342-380,382-399,414-452,454-471,486-524,526-543,558-565 prbs get' "
bcmcmd -n 0 -c " dsh -c 'phydiag 1-24,40-96,112-168,184-240,256-259,270-308,310-327,342-380,382-399,414-452,454-471,486-524,526-543,558-565 prbsstat STArt Interval=30' "
bcmcmd -n 0 -c " dsh -c 'phydiag 1-24,40-96,112-168,184-240,256-259,270-308,310-327,342-380,382-399,414-452,454-471,486-524,526-543,558-565 prbsstat Ber' "
bcmcmd -n 0 -c " dsh -c 'phydiag 1-24,40-96,112-168,184-240,256-259,270-308,310-327,342-380,382-399,414-452,454-471,486-524,526-543,558-565 prbsstat STOp' "
bcmcmd -n 0 -c " dsh -c 'phydiag 1-24,40-96,112-168,184-240,256-259,270-308,310-327,342-380,382-399,414-452,454-471,486-524,526-543,558-565 prbs clear' "
root@sonic:~# bcmcmd -n 0 -c " dsh -c 'phydiag 1-24,40-96,112-168,184-240,256-259,270-308,310-327,342-380,382-399,414-452,454-471,486-524,526-543,558-565 prbs set p=3' "
dsh -c 'phydiag 1-24,40-96,112-168,184-240,256-259,270-308,310-327,342-380,382-399,414-452,454-471,486-524,526-543,558-565 prbs set p=3'
drivshell>
root@sonic:~# bcmcmd -n 0 -c " dsh -c 'phydiag 1-24,40-96,112-168,184-240,256-259,270-308,310-327,342-380,382-399,414-452,454-471,486-524,526-543,558-565 prbs get' "
dsh -c 'phydiag 1-24,40-96,112-168,184-240,256-259,270-308,310-327,342-380,382-399,414-452,454-471,486-524,526-543,558-565 prbs get'
1 : PRBS OK!has been unlocked since last call
2 : PRBS OK!has been unlocked since last call
3 : PRBS OK!has been unlocked since last call
4 : PRBS OK!has been unlocked since last call
5 : PRBS OK!has been unlocked since last call
6 : PRBS OK!has been unlocked since last call
7 : PRBS OK!has been unlocked since last call
8 : PRBS OK!has been unlocked since last call
18 : PRBS OK!has been unlocked since last call
19 : PRBS OK!has been unlocked since last call
20 : PRBS OK!has been unlocked since last call
21 : PRBS OK!has been unlocked since last call
22 : PRBS OK!has been unlocked since last call
23 : PRBS OK!has been unlocked since last call
24 : PRBS OK!has been unlocked since last call
40 : PRBS OK!has been unlocked since last call
41 : PRBS OK!has been unlocked since last call
42 : PRBS OK!has been unlocked since last call
43 : PRBS OK!has been unlocked since last call
54 : PRBS OK!has been unlocked since last call
55 : PRBS OK!has been unlocked since last call
56 : PRBS OK!has been unlocked since last call
57 : PRBS OK!has been unlocked since last call
58 : PRBS OK!has been unlocked since last call
59 : PRBS OK!has been unlocked since last call
60 : PRBS OK!has been unlocked since last call
61 : PRBS OK!has been unlocked since last call
72 : PRBS OK!has been unlocked since last call
73 : PRBS OK!has been unlocked since last call
74 : PRBS OK!has been unlocked since last call
75 : PRBS OK!has been unlocked since last call
76 : PRBS OK!has been unlocked since last call
77 : PRBS OK!has been unlocked since last call
78 : PRBS OK!has been unlocked since last call
79 : PRBS OK!has been unlocked since last call
90 : PRBS OK!has been unlocked since last call
91 : PRBS OK!has been unlocked since last call
92 : PRBS OK!has been unlocked since last call
93 : PRBS OK!has been unlocked since last call
94 : PRBS OK!has been unlocked since last call
95 : PRBS OK!has been unlocked since last call
96 : PRBS OK!has been unlocked since last call
112 : PRBS OK!has been unlocked since last call
113 : PRBS OK!has been unlocked since last call
114 : PRBS OK!has been unlocked since last call
115 : PRBS OK!has been unlocked since last call
126 : PRBS OK!has been unlocked since last call
127 : PRBS OK!has been unlocked since last call
128 : PRBS OK!has been unlocked since last call
129 : PRBS OK!has been unlocked since last call
130 : PRBS OK!has been unlocked since last call
131 : PRBS OK!has been unlocked since last call
132 : PRBS OK!has been unlocked since last call
133 : PRBS OK!has been unlocked since last call
144 : PRBS OK!has been unlocked since last call
145 : PRBS OK!has been unlocked since last call
146 : PRBS OK!has been unlocked since last call
147 : PRBS OK!has been unlocked since last call
148 : PRBS OK!has been unlocked since last call
149 : PRBS OK!has been unlocked since last call
150 : PRBS OK!has been unlocked since last call
151 : PRBS OK!has been unlocked since last call
162 : PRBS OK!has been unlocked since last call
163 : PRBS OK!has been unlocked since last call
164 : PRBS OK!has been unlocked since last call
165 : PRBS OK!has been unlocked since last call
166 : PRBS OK!has been unlocked since last call
167 : PRBS OK!has been unlocked since last call
168 : PRBS OK!has been unlocked since last call
184 : PRBS OK!has been unlocked since last call
185 : PRBS OK!has been unlocked since last call
186 : PRBS OK!has been unlocked since last call
187 : PRBS OK!has been unlocked since last call
198 : PRBS OK!has been unlocked since last call
199 : PRBS OK!has been unlocked since last call
200 : PRBS OK!has been unlocked since last call
201 : PRBS OK!has been unlocked since last call
202 : PRBS OK!has been unlocked since last call
203 : PRBS OK!has been unlocked since last call
204 : PRBS OK!has been unlocked since last call
205 : PRBS OK!has been unlocked since last call
216 : PRBS OK!has been unlocked since last call
217 : PRBS OK!has been unlocked since last call
218 : PRBS OK!has been unlocked since last call
219 : PRBS OK!has been unlocked since last call
220 : PRBS OK!has been unlocked since last call
221 : PRBS OK!has been unlocked since last call
222 : PRBS OK!has been unlocked since last call
223 : PRBS OK!has been unlocked since last call
234 : PRBS OK!has been unlocked since last call
235 : PRBS OK!has been unlocked since last call
236 : PRBS OK!has been unlocked since last call
237 : PRBS OK!has been unlocked since last call
238 : PRBS OK!has been unlocked since last call
239 : PRBS OK!has been unlocked since last call
240 : PRBS OK!has been unlocked since last call
256 : PRBS OK!has been unlocked since last call
257 : PRBS OK!has been unlocked since last call
258 : PRBS OK!has been unlocked since last call
259 : PRBS OK!has been unlocked since last call
270 : PRBS OK!has been unlocked since last call
271 : PRBS OK!has been unlocked since last call
272 : PRBS OK!has been unlocked since last call
273 : PRBS OK!has been unlocked since last call
274 : PRBS OK!has been unlocked since last call
275 : PRBS OK!has been unlocked since last call
276 : PRBS OK!has been unlocked since last call
277 : PRBS OK!has been unlocked since last call
288 : PRBS OK!has been unlocked since last call
289 : PRBS OK!has been unlocked since last call
290 : PRBS OK!has been unlocked since last call
291 : PRBS OK!has been unlocked since last call
292 : PRBS OK!has been unlocked since last call
293 : PRBS OK!has been unlocked since last call
294 : PRBS OK!has been unlocked since last call
295 : PRBS OK!has been unlocked since last call
306 : PRBS OK!has been unlocked since last call
307 : PRBS OK!has been unlocked since last call
308 : PRBS OK!has been unlocked since last call
310 : PRBS OK!has been unlocked since last call
311 : PRBS OK!has been unlocked since last call
312 : PRBS OK!has been unlocked since last call
313 : PRBS OK!has been unlocked since last call
324 : PRBS OK!has been unlocked since last call
325 : PRBS OK!has been unlocked since last call
326 : PRBS OK!has been unlocked since last call
327 : PRBS OK!has been unlocked since last call
342 : PRBS OK!has been unlocked since last call
343 : PRBS OK!has been unlocked since last call
344 : PRBS OK!has been unlocked since last call
345 : PRBS OK!has been unlocked since last call
346 : PRBS OK!has been unlocked since last call
347 : PRBS OK!has been unlocked since last call
348 : PRBS OK!has been unlocked since last call
349 : PRBS OK!has been unlocked since last call
360 : PRBS OK!has been unlocked since last call
361 : PRBS OK!has been unlocked since last call
362 : PRBS OK!has been unlocked since last call
363 : PRBS OK!has been unlocked since last call
364 : PRBS OK!has been unlocked since last call
365 : PRBS OK!has been unlocked since last call
366 : PRBS OK!has been unlocked since last call
367 : PRBS OK!has been unlocked since last call
378 : PRBS OK!has been unlocked since last call
379 : PRBS OK!has been unlocked since last call
380 : PRBS OK!has been unlocked since last call
382 : PRBS OK!has been unlocked since last call
383 : PRBS OK!has been unlocked since last call
384 : PRBS OK!has been unlocked since last call
385 : PRBS OK!has been unlocked since last call
396 : PRBS OK!has been unlocked since last call
397 : PRBS OK!has been unlocked since last call
398 : PRBS OK!has been unlocked since last call
399 : PRBS OK!has been unlocked since last call
414 : PRBS OK!has been unlocked since last call
415 : PRBS OK!has been unlocked since last call
416 : PRBS OK!has been unlocked since last call
417 : PRBS OK!has been unlocked since last call
418 : PRBS OK!has been unlocked since last call
419 : PRBS OK!has been unlocked since last call
420 : PRBS OK!has been unlocked since last call
421 : PRBS OK!has been unlocked since last call
432 : PRBS OK!has been unlocked since last call
433 : PRBS OK!has been unlocked since last call
434 : PRBS OK!has been unlocked since last call
435 : PRBS OK!has been unlocked since last call
436 : PRBS OK!has been unlocked since last call
437 : PRBS OK!has been unlocked since last call
438 : PRBS OK!has been unlocked since last call
439 : PRBS OK!has been unlocked since last call
450 : PRBS OK!has been unlocked since last call
451 : PRBS OK!has been unlocked since last call
452 : PRBS OK!has been unlocked since last call
454 : PRBS OK!has been unlocked since last call
455 : PRBS OK!has been unlocked since last call
456 : PRBS OK!has been unlocked since last call
457 : PRBS OK!has been unlocked since last call
468 : PRBS OK!has been unlocked since last call
469 : PRBS OK!has been unlocked since last call
470 : PRBS OK!has been unlocked since last call
471 : PRBS OK!has been unlocked since last call
486 : PRBS OK!has been unlocked since last call
487 : PRBS OK!has been unlocked since last call
488 : PRBS OK!has been unlocked since last call
489 : PRBS OK!has been unlocked since last call
490 : PRBS OK!has been unlocked since last call
491 : PRBS OK!has been unlocked since last call
492 : PRBS OK!has been unlocked since last call
493 : PRBS OK!has been unlocked since last call
504 : PRBS OK!has been unlocked since last call
505 : PRBS OK!has been unlocked since last call
506 : PRBS OK!has been unlocked since last call
507 : PRBS OK!has been unlocked since last call
508 : PRBS OK!has been unlocked since last call
509 : PRBS OK!has been unlocked since last call
510 : PRBS OK!has been unlocked since last call
511 : PRBS OK!has been unlocked since last call
522 : PRBS OK!has been unlocked since last call
523 : PRBS OK!has been unlocked since last call
524 : PRBS OK!has been unlocked since last call
526 : PRBS OK!has been unlocked since last call
527 : PRBS OK!has been unlocked since last call
528 : PRBS OK!has been unlocked since last call
529 : PRBS OK!has been unlocked since last call
540 : PRBS OK!has been unlocked since last call
541 : PRBS OK!has been unlocked since last call
542 : PRBS OK!has been unlocked since last call
543 : PRBS OK!has been unlocked since last call
558 : PRBS OK!has been unlocked since last call
559 : PRBS OK!has been unlocked since last call
560 : PRBS OK!has been unlocked since last call
561 : PRBS OK!has been unlocked since last call
562 : PRBS OK!has been unlocked since last call
563 : PRBS OK!has been unlocked since last call
564 : PRBS OK!has been unlocked since last call
565 : PRBS OK!has been unlocked since last call
drivshell>
root@sonic:~#
root@sonic:~#
root@sonic:~#
root@sonic:~# bcmcmd -n 0 -c " dsh -c 'phydiag 1-24,40-96,112-168,184-240,256-259,270-308,310-327,342-380,382-399,414-452,454-471,486-524,526-543,558-565 prbssat STArt Interval=30' "
dsh -c 'phydiag 1-24,40-96,112-168,184-240,256-259,270-308,310-327,342-380,382-399,414-452,454-471,486-524,526-543,558-565 prbsstat STArt Interval=30'
PRBSStat thread started ...
drivshell>
root@sonic:~# sleep 30
root@sonic:~# bcmcmd -n 0 -c " dsh -c 'phydiag 1-24,40-96,112-168,184-240,256-259,270-308,310-327,342-380,382-399,414-452,454-471,486-524,526-543,558-565 prbsstat Ber' "
dsh -c 'phydiag 1-24,40-96,112-168,184-240,256-259,270-308,310-327,342-380,382-399,414-452,454-471,486-524,526-543,558-565 prbsstat Ber'
port BER
====
1[0] : 3.53e-11
1[1] : 4.00e-11
====
2[0] : 1.64e-10
2[1] : 8.80e-11
====
3[0] : 6.01e-11
3[1] : 3.45e-12
====
4[0] : 7.69e-11
4[1] : 2.60e-11
====
5[0] : 9.41e-13
5[1] : 6.27e-13
====
6[0] : 3.14e-13
6[1] : 6.27e-13
====
7[0] : 2.98e-12
7[1] : 2.20e-12
====
8[0] : 4.24e-12
8[1] : 9.41e-13
====
18[0] : 7.84e-13
18[1] : 2.04e-12
====
19[0] : 1.57e-13
19[1] : 1.73e-12
====
20[0] : 1.19e-11
20[1] : 1.41e-12
====
21[0] : 5.65e-12
21[1] : 7.69e-12
====
22[0] : 1.54e-11
22[1] : 7.84e-13
====
23[0] : 1.10e-12
23[1] : 2.35e-12
====
24[0] : 2.51e-12
24[1] : 1.41e-12
====
40[0] : 2.23e-11
40[1] : 2.35e-11
====
41[0] : 3.76e-12
41[1] : 4.55e-12
====
42[0] : 7.84e-12
42[1] : 3.76e-12
====
43[0] : 5.02e-12
43[1] : 1.35e-11
====
54[0] : 7.84e-13
54[1] : 6.27e-13
====
55[0] : 1.57e-13
55[1] : 1.57e-13
====
56[0] : 3.14e-13
56[1] : 4.71e-13
====
57[0] : 4.71e-12
57[1] : 6.27e-13
====
58[0] : 5.80e-12
58[1] : 4.24e-11
====
59[0] : 6.90e-12
59[1] : 7.84e-13
====
60[0] : 1.13e-11
60[1] : 1.10e-12
====
61[0] : 2.35e-12
61[1] : 5.49e-12
====
72[0] : 4.39e-11
72[1] : 5.49e-12
====
73[0] : 7.22e-12
73[1] : 8.00e-12
====
74[0] : 3.29e-12
74[1] : 1.25e-12
====
75[0] : 5.96e-12
75[1] : 3.45e-12
====
76[0] : 1.57e-13
76[1] : 1.41e-12
====
77[0] : 2.20e-12
77[1] : 1.57e-13
====
78[0] : 2.67e-12
78[1] : 1.41e-12
====
79[0] : 6.27e-13
79[1] : 3.14e-13
====
90[0] : 3.61e-12
90[1] : 1.91e-11
====
91[0] : 1.88e-12
91[1] : 4.24e-12
====
92[0] : 8.47e-12
92[1] : 4.08e-12
====
93[0] : 3.92e-12
93[1] : 1.82e-11
====
94[0] : 1.57e-13
94[1] : 1.41e-12
====
95[0] : 1.57e-13
95[1] : 1.57e-13
====
96[0] : 1.57e-13
96[1] : 4.71e-13
====
112[0] : 4.08e-12
112[1] : 2.04e-12
====
113[0] : 4.08e-12
113[1] : 3.29e-12
====
114[0] : 7.84e-13
114[1] : 9.41e-13
====
115[0] : 6.27e-13
115[1] : 1.88e-12
====
126[0] : 2.82e-12
126[1] : 1.57e-13
====
127[0] : 1.57e-13
127[1] : 4.71e-13
====
128[0] : 3.45e-12
128[1] : 3.14e-13
====
129[0] : 4.71e-13
129[1] : 6.27e-13
====
130[0] : 2.67e-12
130[1] : 1.57e-12
====
131[0] : 2.35e-12
131[1] : 6.12e-12
====
132[0] : 3.33e-11
132[1] : 5.02e-12
====
133[0] : 9.88e-12
133[1] : 6.90e-12
====
144[0] : 7.84e-13
144[1] : 7.22e-12
====
145[0] : 3.29e-12
145[1] : 6.12e-12
====
146[0] : 1.02e-11
146[1] : 1.57e-12
====
147[0] : 1.57e-12
147[1] : 3.92e-12
====
148[0] : 1.57e-13
148[1] : 1.25e-12
====
149[0] : 1.57e-13
149[1] : 1.57e-13
====
150[0] : 6.27e-13
150[1] : 1.57e-13
====
151[0] : 6.27e-13
151[1] : 3.14e-13
====
162[0] : 1.57e-13
162[1] : 6.27e-13
====
163[0] : 3.14e-13
163[1] : 1.57e-12
====
164[0] : 1.25e-12
164[1] : 2.20e-12
====
165[0] : 4.71e-13
165[1] : 2.67e-12
====
166[0] : 1.57e-13
166[1] : 3.14e-13
====
167[0] : 1.57e-13
167[1] : 1.57e-12
====
168[0] : 4.71e-13
168[1] : 1.57e-13
====
184[0] : 4.11e-10
184[1] : 3.12e-10
====
185[0] : 1.71e-08
185[1] : 3.13e-09
====
186[0] : 2.05e-10
186[1] : 2.27e-09
====
187[0] : 3.94e-11
187[1] : 2.97e-09
====
198[0] : 3.44e-11
198[1] : 3.29e-11
====
199[0] : 7.53e-10
199[1] : 1.66e-10
====
200[0] : 4.38e-11
200[1] : 3.81e-11
====
201[0] : 5.43e-11
201[1] : 1.95e-11
====
202[0] : 1.82e-10
202[1] : 5.74e-09
====
203[0] : 1.98e-08
203[1] : 5.05e-09
====
204[0] : 2.09e-10
204[1] : 5.71e-11
====
205[0] : 6.59e-11
205[1] : 5.21e-11
====
216[0] : 4.71e-13
216[1] : 6.27e-13
====
217[0] : 1.57e-12
217[1] : 6.27e-13
====
218[0] : 3.14e-13
218[1] : 1.57e-13
====
219[0] : 1.41e-12
219[1] : 1.41e-12
====
220[0] : 1.57e-13
220[1] : 1.57e-13
====
221[0] : 1.57e-13
221[1] : 1.57e-13
====
222[0] : 1.57e-13
222[1] : 3.14e-13
====
223[0] : 9.41e-13
223[1] : 1.57e-13
====
234[0] : 1.57e-13
234[1] : 4.71e-13
====
235[0] : 3.14e-13
235[1] : 1.25e-12
====
236[0] : 4.71e-13
236[1] : 1.57e-13
====
237[0] : 3.14e-13
237[1] : 1.57e-13
====
238[0] : 1.57e-13
238[1] : 1.57e-13
====
239[0] : 3.14e-13
239[1] : 3.14e-13
====
240[0] : 2.35e-12
240[1] : 1.57e-13
====
256[0] : 4.24e-12
256[1] : 7.37e-12
====
257[0] : 4.71e-13
257[1] : 7.69e-12
====
258[0] : 1.09e-09
258[1] : 6.25e-09
====
259[0] : 1.66e-11
259[1] : 1.02e-10
====
270[0] : 1.10e-12
270[1] : 9.25e-12
====
271[0] : 5.33e-12
271[1] : 1.62e-11
====
272[0] : 1.37e-10
272[1] : 9.15e-10
====
273[0] : 9.41e-12
273[1] : 1.90e-11
====
274[0] : 1.57e-13
274[1] : 2.04e-12
====
275[0] : 1.73e-12
275[1] : 2.20e-12
====
276[0] : 1.30e-10
276[1] : 1.36e-10
====
277[0] : 1.41e-12
277[1] : 2.04e-12
====
288[0] : 4.71e-13
288[1] : 4.71e-13
====
289[0] : 3.14e-13
289[1] : 2.20e-12
====
290[0] : 3.14e-13
290[1] : 1.57e-13
====
291[0] : 3.29e-11
291[1] : 1.08e-11
====
292[0] : 3.14e-13
292[1] : 6.27e-13
====
293[0] : 1.57e-13
293[1] : 5.80e-12
====
294[0] : 5.96e-12
294[1] : 1.88e-12
====
295[0] : 1.27e-11
295[1] : 1.21e-11
====
306[0] : 4.71e-13
306[1] : 1.10e-12
====
307[0] : 7.84e-13
307[1] : 5.02e-12
====
308[0] : 3.92e-12
308[1] : 2.67e-12
====
310[0] : 3.92e-12
310[1] : 1.25e-12
====
311[0] : 2.51e-12
311[1] : 6.27e-13
====
312[0] : 2.20e-12
312[1] : 6.75e-12
====
313[0] : 5.43e-11
313[1] : 2.18e-10
====
324[0] : 1.46e-11
324[1] : 1.27e-11
====
325[0] : 2.67e-12
325[1] : 7.94e-11
====
326[0] : 5.35e-11
326[1] : 2.35e-12
====
327[0] : 7.15e-11
327[1] : 1.11e-11
====
342[0] : 2.04e-12
342[1] : 1.41e-12
====
343[0] : 3.14e-13
343[1] : 3.61e-12
====
344[0] : 1.41e-12
344[1] : 1.57e-13
====
345[0] : 1.25e-12
345[1] : 1.41e-12
====
346[0] : 3.14e-13
346[1] : 1.57e-13
====
347[0] : 1.57e-13
347[1] : 3.14e-13
====
348[0] : 1.57e-13
348[1] : 3.14e-13
====
349[0] : 1.73e-12
349[1] : 2.21e-11
====
360[0] : 8.78e-12
360[1] : 1.24e-11
====
361[0] : 7.22e-12
361[1] : 1.87e-11
====
362[0] : 1.30e-11
362[1] : 1.74e-11
====
363[0] : 1.51e-10
363[1] : 2.85e-11
====
364[0] : 1.38e-11
364[1] : 1.17e-10
====
365[0] : 3.95e-11
365[1] : 2.67e-12
====
366[0] : 4.24e-12
366[1] : 5.49e-12
====
367[0] : 1.71e-10
367[1] : 1.40e-11
====
378[0] : 1.41e-11
378[1] : 3.50e-11
====
379[0] : 7.67e-11
379[1] : 1.03e-09
====
380[0] : 2.16e-11
380[1] : 1.62e-11
====
382[0] : 4.35e-11
382[1] : 1.81e-10
====
383[0] : 2.28e-10
383[1] : 1.41e-10
====
384[0] : 7.22e-12
384[1] : 5.71e-11
====
385[0] : 3.36e-11
385[1] : 2.16e-11
====
396[0] : 2.82e-12
396[1] : 4.55e-12
====
397[0] : 1.27e-11
397[1] : 1.41e-11
====
398[0] : 2.99e-10
398[1] : 5.80e-11
====
399[0] : 2.51e-11
399[1] : 1.11e-11
====
414[0] : 4.55e-12
414[1] : 8.31e-12
====
415[0] : 1.57e-13
415[1] : 9.41e-13
====
416[0] : 6.27e-12
416[1] : 5.96e-12
====
417[0] : 2.20e-12
417[1] : 1.88e-12
====
418[0] : 6.27e-13
418[1] : 1.25e-12
====
419[0] : 1.57e-13
419[1] : 1.41e-12
====
420[0] : 3.14e-13
420[1] : 2.82e-12
====
421[0] : 4.39e-12
421[1] : 1.16e-11
====
432[0] : 4.71e-13
432[1] : 6.27e-13
====
433[0] : 4.71e-13
433[1] : 1.57e-12
====
434[0] : 4.71e-13
434[1] : 1.57e-13
====
435[0] : 1.25e-12
435[1] : 1.57e-13
====
436[0] : 1.57e-13
436[1] : 1.57e-13
====
437[0] : 1.57e-13
437[1] : 4.71e-13
====
438[0] : 7.84e-13
438[1] : 1.41e-12
====
439[0] : 1.41e-12
439[1] : 1.10e-12
====
450[0] : 6.27e-13
450[1] : 4.71e-13
====
451[0] : 1.57e-13
451[1] : 1.25e-12
====
452[0] : 1.10e-12
452[1] : 7.84e-13
====
454[0] : 1.10e-12
454[1] : 1.88e-12
====
455[0] : 6.27e-13
455[1] : 1.57e-12
====
456[0] : 1.88e-12
456[1] : 9.41e-13
====
457[0] : 7.84e-13
457[1] : 4.71e-13
====
468[0] : 6.27e-13
468[1] : 9.41e-13
====
469[0] : 2.67e-12
469[1] : 4.08e-12
====
470[0] : 3.14e-13
470[1] : 4.71e-13
====
471[0] : 3.14e-13
471[1] : 7.84e-13
====
486[0] : 1.73e-12
486[1] : 1.57e-12
====
487[0] : 1.10e-12
487[1] : 2.82e-12
====
488[0] : 4.71e-13
488[1] : 4.71e-13
====
489[0] : 9.41e-13
489[1] : 3.14e-13
====
490[0] : 1.57e-13
490[1] : 3.14e-13
====
491[0] : 3.14e-13
491[1] : 1.57e-13
====
492[0] : 9.41e-13
492[1] : 1.10e-12
====
493[0] : 7.84e-13
493[1] : 2.98e-12
====
504[0] : 4.71e-13
504[1] : 3.14e-13
====
505[0] : 1.57e-13
505[1] : 7.84e-13
====
506[0] : 4.71e-13
506[1] : 1.57e-13
====
507[0] : 3.14e-13
507[1] : 1.57e-13
====
508[0] : 2.04e-12
508[1] : 1.41e-12
====
509[0] : 9.41e-13
509[1] : 7.84e-13
====
510[0] : 1.57e-13
510[1] : 6.27e-13
====
511[0] : 6.27e-13
511[1] : 4.71e-13
====
522[0] : 2.35e-12
522[1] : 1.57e-13
====
523[0] : 3.14e-13
523[1] : 6.27e-13
====
524[0] : 7.84e-13
524[1] : 1.57e-13
====
526[0] : 7.84e-13
526[1] : 3.14e-13
====
527[0] : 1.41e-12
527[1] : 4.71e-13
====
528[0] : 3.14e-13
528[1] : 9.41e-13
====
529[0] : 4.71e-13
529[1] : 1.57e-13
====
540[0] : 6.27e-13
540[1] : 1.25e-12
====
541[0] : 3.14e-13
541[1] : 1.10e-12
====
542[0] : 2.98e-12
542[1] : 6.27e-13
====
543[0] : 2.98e-12
543[1] : 3.14e-13
====
558[0] : 9.41e-13
558[1] : 1.10e-12
====
559[0] : 3.29e-12
559[1] : 2.04e-12
====
560[0] : 1.57e-13
560[1] : 1.57e-13
====
561[0] : 2.35e-12
561[1] : 4.71e-13
====
562[0] : 1.57e-13
562[1] : 1.57e-13
====
563[0] : 1.57e-13
563[1] : 4.71e-13
====
564[0] : 1.41e-12
564[1] : 7.84e-13
====
565[0] : 4.55e-12
565[1] : 1.57e-13
====
drivshell>
root@sonic:~# bcmcmd -n 0 -c " dsh -c 'phydiag 1-24,40-96,112-168,184-240,256-259,270-308,310-327,342-380,382-399,414-452,454-471,486-524,526-543,558-565 prbssat STOp' "
dsh -c 'phydiag 1-24,40-96,112-168,184-240,256-259,270-308,310-327,342-380,382-399,414-452,454-471,486-524,526-543,558-565 prbsstat STOp'
Stopping PRBSStat thread
drivshell>
root@sonic:~# bcmcmd -n 0 -c " dsh -c 'phydiag 1-24,40-96,112-168,184-240,256-259,270-308,310-327,342-380,382-399,414-452,454-471,486-524,526-543,558-565 prbs clear' "
dsh -c 'phydiag 1-24,40-96,112-168,184-240,256-259,270-308,310-327,342-380,382-399,414-452,454-471,486-524,526-543,558-565 prbs clear'
drivshell>
root@sonic:~#
+17
View File
@@ -0,0 +1,17 @@
//Start Unit0 Tr518
bcmcmd -n 0 -c 'port cd lb=mac'
bcmcmd -n 0 -c 'l2 learn off'
bcmcmd -n 0 -c 'dsh -c "test mode nr=yes"'
bcmcmd -n 0 -c 'dsh -c "tr 518 Scenario=6 Profile=2 Count=260 PktSize=324 PortList=1-259,270-565 rm=ext CheckLinkTime=20 CheckDataTime=200 testphase=1"'
//Stop Unit0 Tr518
bcmcmd -n 0 -c 'port cd lb=none'
//Start Unit1 Tr518
bcmcmd -n 1 -c 'port cd lb=mac'
bcmcmd -n 1 -c 'l2 learn off'
bcmcmd -n 1 -c 'dsh -c "test mode nr=yes"'
bcmcmd -n 1 -c 'dsh -c "tr 518 Scenario=6 Profile=2 Count=260 PktSize=324 PortList=1-259,270-565 rm=ext CheckLinkTime=20 CheckDataTime=200 testphase=1"'
//Stop Unit1 Tr518
bcmcmd -n 1 -c 'port cd lb=none'
+325
View File
@@ -0,0 +1,325 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
bcm_mibpair_report_V1.1.0.py
Author : ETWen
Date : 20260730
Purpose : Extract MIB_TPOK / MIB_RPOK counters from a Broadcom drivshell log
and lay them out horizontally, one loopback pair per block:
Port TX RX PASS/FAIL
cd1 723629081133 581693309458 PASS
cd33 581693309458 723629081133 PASS
Pair membership and pair order come from the
"vlan remove <vid> pbm=cdA,cdB" lines in the same log, so the report
always follows the order the test actually ran in.
Verdict per pair comes from the loopback cross-check: for a pair,
cdA.TPOK must equal cdB.RPOK and cdA.RPOK must equal cdB.TPOK.
PASS - both directions match (within --tolerance)
FAIL - at least one direction is off; delta is listed in the
trailing summary
NA - the pair has no counters in the log
Usage : ./bcm_mibpair_report_V1.1.0.py <drivshell.log> [options]
-a, --all also emit pairs that have no counters in the log
(values shown as "-")
-b, --block repeat the header and blank-separate each pair
-g, --grouped keep the SDK's comma grouping (1,234,567)
-p, --with-port render "cd1(2)" instead of "cd1"
-t, --tsv tab-separated output (for Excel / pandas)
-s, --no-status drop the PASS/FAIL column
-T, --tolerance N allowed |delta| in packets before a pair is FAIL
(default 0). A +/-1 skew is normal: the SDK reads
counters port-by-port while traffic still runs, so
-T 1 is a reasonable production setting.
-q, --quiet suppress the trailing summary comments
-o, --output write to a file instead of stdout
Notes : - The trailing "+delta" column printed by the SDK is stripped.
- A cd port missing from the dump usually means the console
scrolled or auto-logged out mid-"show c", NOT that the port was
down. Cross-check against "ps" before drawing conclusions.
- A +/-1 packet delta between a pair is normal snapshot skew: the
SDK reads counters port-by-port while traffic is still running.
Exit : 0 = every pair had counters and cross-checked clean
1 = usage / input error
2 = at least one pair is FAIL or NA (see summary)
Version History
V1.0.0 20260730 Initial Version (parity with bcm_mibpair_report_V1.2.0.sh)
V1.0.1 20260730 Handle BrokenPipeError quietly when piped into head
V1.1.0 20260730 Add PASS/FAIL/NA verdict column and --tolerance
"""
from __future__ import annotations
import argparse
import re
import sys
from dataclasses import dataclass, field
from pathlib import Path
from typing import Iterable, TextIO
EXIT_OK = 0
EXIT_USAGE = 1
EXIT_WARN = 2
# "vlan remove 31 pbm=cd1,cd33"
RE_PBM = re.compile(r"pbm=(cd\d+),(cd\d+)")
# "MIB_TPOK.cd1(2) : 723,629,081,133 +723,629,081,133"
RE_MIB = re.compile(
r"^MIB_(?P<kind>TPOK|RPOK)\.(?P<cd>cd\d+)\((?P<lport>\d+)\)\s*:\s*(?P<val>[\d,]+)"
)
MISSING = "-"
@dataclass
class PortCounters:
"""TPOK / RPOK for a single cd port."""
lport: int | None = None
tx: int | None = None
rx: int | None = None
@property
def complete(self) -> bool:
return self.tx is not None and self.rx is not None
@dataclass
class LogData:
"""Everything the report needs, extracted from one drivshell log."""
pairs: list[tuple[str, str]] = field(default_factory=list)
ports: dict[str, PortCounters] = field(default_factory=dict)
def counters(self, cd: str) -> PortCounters:
return self.ports.setdefault(cd, PortCounters())
def parse_log(lines: Iterable[str]) -> LogData:
"""Pull pbm pair definitions and TPOK/RPOK counters out of a drivshell log."""
data = LogData()
seen_pairs: set[tuple[str, str]] = set()
for raw in lines:
line = raw.rstrip("\r\n")
for a, b in RE_PBM.findall(line):
if (a, b) not in seen_pairs:
seen_pairs.add((a, b))
data.pairs.append((a, b))
# drivshell echoes commands, so a MIB line may be prefixed with
# "drivshell>" noise; anchor on the MIB_ token instead of the line start.
idx = line.find("MIB_")
if idx < 0:
continue
m = RE_MIB.match(line[idx:])
if not m:
continue
pc = data.counters(m.group("cd"))
pc.lport = int(m.group("lport"))
value = int(m.group("val").replace(",", ""))
if m.group("kind") == "TPOK":
pc.tx = value
else:
pc.rx = value
return data
def fmt_value(value: int | None, grouped: bool) -> str:
if value is None:
return MISSING
return f"{value:,}" if grouped else str(value)
def fmt_port(cd: str, pc: PortCounters, with_port: bool) -> str:
if with_port and pc.lport is not None:
return f"{cd}({pc.lport})"
return cd
PASS, FAIL, NA = "PASS", "FAIL", "NA"
def pair_deltas(data: LogData, a: str, b: str) -> tuple[int, int] | None:
"""(cdA.TX - cdB.RX, cdA.RX - cdB.TX), or None if counters are missing."""
pa, pb = data.counters(a), data.counters(b)
if not (pa.complete and pb.complete):
return None
return pa.tx - pb.rx, pa.rx - pb.tx
def verdict(data: LogData, a: str, b: str, tolerance: int) -> str:
d = pair_deltas(data, a, b)
if d is None:
return NA
return PASS if max(abs(d[0]), abs(d[1])) <= tolerance else FAIL
def render(data: LogData, args: argparse.Namespace, out: TextIO) -> int:
status = not args.no_status
rows: list[tuple[str, ...]] = []
blocks: list[list[tuple[str, ...]]] = []
missing: list[tuple[str, str]] = []
failed: list[tuple[str, str, int, int]] = []
tally = {PASS: 0, FAIL: 0, NA: 0}
for a, b in data.pairs:
pa, pb = data.counters(a), data.counters(b)
v = verdict(data, a, b, args.tolerance)
tally[v] += 1
if v == NA:
missing.append((a, b))
if not args.all:
continue
elif v == FAIL:
d_tx, d_rx = pair_deltas(data, a, b)
failed.append((a, b, d_tx, d_rx))
block = []
for cd, pc in ((a, pa), (b, pb)):
row = (
fmt_port(cd, pc, args.with_port),
fmt_value(pc.tx, args.grouped),
fmt_value(pc.rx, args.grouped),
)
block.append(row + (v,) if status else row)
blocks.append(block)
rows.extend(block)
header = ("Port", "TX", "RX", "PASS/FAIL") if status else ("Port", "TX", "RX")
if args.tsv:
if not args.block:
print("\t".join(header), file=out)
for block in blocks:
if args.block:
print("\t".join(header), file=out)
for row in block:
print("\t".join(row), file=out)
if args.block:
print(file=out)
else:
# size columns to the widest cell actually emitted
ncol = len(header)
width = [
max([len(r[i]) for r in rows] + [len(header[i])]) + 2
for i in range(ncol)
]
def line(row: tuple[str, ...]) -> str:
cells = [f"{row[0]:<{width[0]}}"]
cells += [f"{row[i]:>{width[i]}}" for i in range(1, ncol)]
return "".join(cells).rstrip()
if not args.block:
print(line(header), file=out)
for block in blocks:
if args.block:
print(line(header), file=out)
for row in block:
print(line(row), file=out)
if args.block:
print(file=out)
if not args.quiet:
if not args.block:
print(file=out)
print(f"# pairs in pbm list : {len(data.pairs)}", file=out)
print(f"# pairs reported : {len(blocks)}", file=out)
print(
f"# PASS {tally[PASS]} FAIL {tally[FAIL]} NA {tally[NA]}"
f" (tolerance {args.tolerance})",
file=out,
)
if missing:
joined = " ".join(f"{a}/{b}" for a, b in missing)
print(f"# NA (no counters): {joined}", file=out)
for a, b, d_tx, d_rx in failed:
print(
f"# FAIL {a}/{b}: {a}.TX-{b}.RX={d_tx:+d} {a}.RX-{b}.TX={d_rx:+d}",
file=out,
)
return EXIT_WARN if (tally[FAIL] or tally[NA]) else EXIT_OK
def build_parser() -> argparse.ArgumentParser:
p = argparse.ArgumentParser(
description="Render Broadcom drivshell MIB_TPOK/RPOK counters as a "
"loopback-pair table.",
epilog="Exit 2 means missing counters and/or a cross-check mismatch.",
)
p.add_argument("log", type=Path, help="drivshell log file ('-' for stdin)")
p.add_argument("-a", "--all", action="store_true",
help="also emit pairs with no counters")
p.add_argument("-b", "--block", action="store_true",
help="repeat header and blank-separate each pair")
p.add_argument("-g", "--grouped", action="store_true",
help="keep comma grouping in values")
p.add_argument("-p", "--with-port", action="store_true",
help="render cd1(2) instead of cd1")
p.add_argument("-t", "--tsv", action="store_true",
help="tab-separated output")
p.add_argument("-s", "--no-status", action="store_true",
help="drop the PASS/FAIL column")
p.add_argument("-T", "--tolerance", type=int, default=0, metavar="N",
help="allowed |delta| in packets before a pair is FAIL "
"(default 0; -T 1 absorbs normal snapshot skew)")
p.add_argument("-q", "--quiet", action="store_true",
help="suppress trailing summary comments")
p.add_argument("-o", "--output", type=Path, default=None,
help="write to a file instead of stdout")
return p
def main(argv: list[str] | None = None) -> int:
args = build_parser().parse_args(argv)
try:
if str(args.log) == "-":
data = parse_log(sys.stdin)
else:
# console captures are frequently not clean UTF-8
with args.log.open("r", encoding="utf-8", errors="replace") as fh:
data = parse_log(fh)
except OSError as exc:
print(f"error: cannot read log: {exc}", file=sys.stderr)
return EXIT_USAGE
if not data.pairs:
print("error: no 'pbm=cdA,cdB' pair definition found in log",
file=sys.stderr)
return EXIT_USAGE
try:
if args.output:
with args.output.open("w", encoding="utf-8", newline="\n") as fh:
return render(data, args, fh)
return render(data, args, sys.stdout)
except BrokenPipeError:
# downstream closed the pipe (e.g. "| head") - not an error
try:
sys.stdout.close()
except BrokenPipeError:
pass
return EXIT_OK
except OSError as exc:
print(f"error: cannot write output: {exc}", file=sys.stderr)
return EXIT_USAGE
if __name__ == "__main__":
sys.exit(main())
+7
View File
@@ -0,0 +1,7 @@
echo 18-0020 > /sys/bus/i2c/drivers/max31790/unbind
sleep .5
echo 18-0020 > /sys/bus/i2c/drivers/max31790/bind
echo 25-0020 > /sys/bus/i2c/drivers/max31790/unbind
sleep .5
echo 25-0020 > /sys/bus/i2c/drivers/max31790/bind
fan-speed-control.sh 30
+59
View File
@@ -0,0 +1,59 @@
#!/bin/bash
# =============================================================================
# gen_channel_map.sh - Build docs/LTC2980_channel_map.csv from settings/*.conf
# Version : V1.0.0
# Date : 2026-08-27
# Author : ETWen
# =============================================================================
# Runs on the DEV machine, not the DUT. Reads every LTC2980 margin settings
# file and flattens CH<n>_VNOM / CH<n>_NET into one CSV:
#
# Board,CONN,Ch,NetName,Vnom
# CB,CONN13,CH0,V5P0_ALW,5.0
#
# NC channels keep their literal NC / "-" so the page numbering still lines up
# (ch/8 -> CHIPS[] index, ch%8 -> LTC2977 PAGE). Do not filter them out.
#
# Usage:
# tools/gen_channel_map.sh # write docs/LTC2980_channel_map.csv
# tools/gen_channel_map.sh - # write to stdout
# =============================================================================
set -eu
REPO_ROOT="$(cd "$(dirname "$0")/.." && pwd)"
SET_DIR="${REPO_ROOT}/src/Script_ABC_Blanton/Blanton_Script/LTC2980_Margin_Script/settings"
OUT="${1:-${REPO_ROOT}/docs/LTC2980_channel_map.csv}"
[ -d "${SET_DIR}" ] || { echo "settings dir not found: ${SET_DIR}" >&2; exit 1; }
# 3-argument match(str, regex, array) is a gawk extension. Under mawk it is a
# syntax error at best and an empty CSV at worst - fail loudly instead.
awk 'BEGIN { if (match("a", /a/, m) != 1) exit 1 }' 2>/dev/null \
|| { echo "need gawk (3-arg match); this awk is: $(awk --version 2>&1 | head -1)" >&2; exit 1; }
gen() {
printf 'Board,CONN,Ch,NetName,Vnom\n'
# CB first, then SWB0/SWB1 CONN13..CONN16 - matches the physical walk order.
for f in "${SET_DIR}"/Blanton_CB_CONN*.conf \
"${SET_DIR}"/Blanton_SWB0_CONN*.conf \
"${SET_DIR}"/Blanton_SWB1_CONN*.conf ; do
[ -f "${f}" ] || continue
base="$(basename "${f}" .conf)" # Blanton_SWB0_CONN13
board="$(echo "${base}" | cut -d_ -f2)" # SWB0
conn="$(echo "${base}" | cut -d_ -f3)" # CONN13
# .conf files are LF per .gitattributes, but strip CR anyway - a stray
# CR would land inside the net name and travel into the CSV unseen.
sed 's/\r$//' "${f}" | awk -v b="${board}" -v c="${conn}" '
match($0, /^CH([0-9]+)_VNOM="([^"]*)"[^"]*CH[0-9]+_NET="([^"]*)"/, m) {
printf "%s,%s,CH%s,%s,%s\n", b, c, m[1], m[3], m[2]
}'
done
}
if [ "${OUT}" = "-" ]; then
gen
else
gen > "${OUT}"
echo "wrote ${OUT} ($(($(wc -l < "${OUT}") - 1)) channels)"
fi
+108
View File
@@ -0,0 +1,108 @@
bcmcmd -n 1 -c 'tx 100 length=512 VLantag=30'
bcmcmd -n 1 -c 'tx 100 length=512 VLantag=31'
bcmcmd -n 1 -c 'tx 100 length=512 VLantag=32'
bcmcmd -n 1 -c 'tx 100 length=512 VLantag=33'
bcmcmd -n 1 -c 'tx 100 length=512 VLantag=34'
bcmcmd -n 1 -c 'tx 100 length=512 VLantag=35'
bcmcmd -n 1 -c 'tx 100 length=512 VLantag=36'
bcmcmd -n 1 -c 'tx 100 length=512 VLantag=37'
bcmcmd -n 1 -c 'tx 100 length=512 VLantag=38'
bcmcmd -n 1 -c 'tx 100 length=512 VLantag=39'
bcmcmd -n 1 -c 'tx 100 length=512 VLantag=40'
bcmcmd -n 1 -c 'tx 100 length=512 VLantag=41'
bcmcmd -n 1 -c 'tx 100 length=512 VLantag=42'
bcmcmd -n 1 -c 'tx 100 length=512 VLantag=43'
bcmcmd -n 1 -c 'tx 100 length=512 VLantag=44'
bcmcmd -n 1 -c 'tx 100 length=512 VLantag=45'
bcmcmd -n 1 -c 'tx 100 length=512 VLantag=46'
bcmcmd -n 1 -c 'tx 100 length=512 VLantag=47'
bcmcmd -n 1 -c 'tx 100 length=512 VLantag=48'
bcmcmd -n 1 -c 'tx 100 length=512 VLantag=49'
bcmcmd -n 1 -c 'tx 100 length=512 VLantag=50'
bcmcmd -n 1 -c 'tx 100 length=512 VLantag=51'
bcmcmd -n 1 -c 'tx 100 length=512 VLantag=52'
bcmcmd -n 1 -c 'tx 100 length=512 VLantag=53'
bcmcmd -n 1 -c 'tx 100 length=512 VLantag=54'
bcmcmd -n 1 -c 'tx 100 length=512 VLantag=55'
bcmcmd -n 1 -c 'tx 100 length=512 VLantag=56'
bcmcmd -n 1 -c 'tx 100 length=512 VLantag=57'
bcmcmd -n 1 -c 'tx 100 length=512 VLantag=58'
bcmcmd -n 1 -c 'tx 100 length=512 VLantag=59'
bcmcmd -n 1 -c 'tx 100 length=512 VLantag=60'
bcmcmd -n 1 -c 'tx 100 length=512 VLantag=61'
bcmcmd -n 1 -c 'tx 100 length=512 VLantag=62'
bcmcmd -n 1 -c 'tx 100 length=512 VLantag=63'
bcmcmd -n 1 -c 'tx 100 length=512 VLantag=64'
bcmcmd -n 1 -c 'tx 100 length=512 VLantag=65'
bcmcmd -n 1 -c 'tx 100 length=512 VLantag=66'
bcmcmd -n 1 -c 'tx 100 length=512 VLantag=67'
bcmcmd -n 1 -c 'tx 100 length=512 VLantag=68'
bcmcmd -n 1 -c 'tx 100 length=512 VLantag=69'
bcmcmd -n 1 -c 'tx 100 length=512 VLantag=70'
bcmcmd -n 1 -c 'tx 100 length=512 VLantag=71'
bcmcmd -n 1 -c 'tx 100 length=512 VLantag=72'
bcmcmd -n 1 -c 'tx 100 length=512 VLantag=73'
bcmcmd -n 1 -c 'tx 100 length=512 VLantag=74'
bcmcmd -n 1 -c 'tx 100 length=512 VLantag=75'
bcmcmd -n 1 -c 'tx 100 length=512 VLantag=76'
bcmcmd -n 1 -c 'tx 100 length=512 VLantag=77'
bcmcmd -n 1 -c 'tx 100 length=512 VLantag=78'
bcmcmd -n 1 -c 'tx 100 length=512 VLantag=79'
bcmcmd -n 1 -c 'tx 100 length=512 VLantag=80'
bcmcmd -n 1 -c 'tx 100 length=512 VLantag=81'
bcmcmd -n 1 -c 'tx 100 length=512 VLantag=82'
bcmcmd -n 1 -c 'tx 100 length=512 VLantag=83'
bcmcmd -n 1 -c 'tx 100 length=512 VLantag=84'
bcmcmd -n 1 -c 'tx 100 length=512 VLantag=85'
bcmcmd -n 1 -c 'tx 100 length=512 VLantag=86'
bcmcmd -n 1 -c 'tx 100 length=512 VLantag=87'
bcmcmd -n 1 -c 'tx 100 length=512 VLantag=88'
bcmcmd -n 1 -c 'tx 100 length=512 VLantag=89'
bcmcmd -n 1 -c 'tx 100 length=512 VLantag=90'
bcmcmd -n 1 -c 'tx 100 length=512 VLantag=91'
bcmcmd -n 1 -c 'tx 100 length=512 VLantag=92'
bcmcmd -n 1 -c 'tx 100 length=512 VLantag=93'
bcmcmd -n 1 -c 'tx 100 length=512 VLantag=94'
bcmcmd -n 1 -c 'tx 100 length=512 VLantag=95'
bcmcmd -n 1 -c 'tx 100 length=512 VLantag=96'
bcmcmd -n 1 -c 'tx 100 length=512 VLantag=97'
bcmcmd -n 1 -c 'tx 100 length=512 VLantag=98'
bcmcmd -n 1 -c 'tx 100 length=512 VLantag=99'
bcmcmd -n 1 -c 'tx 100 length=512 VLantag=100'
bcmcmd -n 1 -c 'tx 100 length=512 VLantag=101'
bcmcmd -n 1 -c 'tx 100 length=512 VLantag=102'
bcmcmd -n 1 -c 'tx 100 length=512 VLantag=103'
bcmcmd -n 1 -c 'tx 100 length=512 VLantag=104'
bcmcmd -n 1 -c 'tx 100 length=512 VLantag=105'
bcmcmd -n 1 -c 'tx 100 length=512 VLantag=106'
bcmcmd -n 1 -c 'tx 100 length=512 VLantag=107'
bcmcmd -n 1 -c 'tx 100 length=512 VLantag=108'
bcmcmd -n 1 -c 'tx 100 length=512 VLantag=109'
bcmcmd -n 1 -c 'tx 100 length=512 VLantag=110'
bcmcmd -n 1 -c 'tx 100 length=512 VLantag=111'
bcmcmd -n 1 -c 'tx 100 length=512 VLantag=112'
bcmcmd -n 1 -c 'tx 100 length=512 VLantag=113'
bcmcmd -n 1 -c 'tx 100 length=512 VLantag=114'
bcmcmd -n 1 -c 'tx 100 length=512 VLantag=115'
bcmcmd -n 1 -c 'tx 100 length=512 VLantag=116'
bcmcmd -n 1 -c 'tx 100 length=512 VLantag=117'
bcmcmd -n 1 -c 'tx 100 length=512 VLantag=118'
bcmcmd -n 1 -c 'tx 100 length=512 VLantag=119'
bcmcmd -n 1 -c 'tx 100 length=512 VLantag=120'
bcmcmd -n 1 -c 'tx 100 length=512 VLantag=121'
bcmcmd -n 1 -c 'tx 100 length=512 VLantag=122'
bcmcmd -n 1 -c 'tx 100 length=512 VLantag=123'
bcmcmd -n 1 -c 'tx 100 length=512 VLantag=124'
bcmcmd -n 1 -c 'tx 100 length=512 VLantag=125'
bcmcmd -n 1 -c 'tx 100 length=512 VLantag=126'
bcmcmd -n 1 -c 'tx 100 length=512 VLantag=127'
bcmcmd -n 1 -c 'tx 100 length=512 VLantag=128'
bcmcmd -n 1 -c 'tx 100 length=512 VLantag=129'
bcmcmd -n 1 -c 'tx 100 length=512 VLantag=130'
bcmcmd -n 1 -c 'tx 100 length=512 VLantag=131'
bcmcmd -n 1 -c 'tx 100 length=512 VLantag=132'
bcmcmd -n 1 -c 'tx 100 length=512 VLantag=133'
bcmcmd -n 1 -c 'tx 100 length=512 VLantag=134'
bcmcmd -n 1 -c 'tx 100 length=512 VLantag=135'
bcmcmd -n 1 -c 'tx 100 length=512 VLantag=136'
bcmcmd -n 1 -c 'tx 100 length=512 VLantag=137'
+108
View File
@@ -0,0 +1,108 @@
bcmcmd -n 1 -c 'vlan remove 30 pbm=cd0,cd32'
bcmcmd -n 1 -c 'vlan remove 31 pbm=cd1,cd33'
bcmcmd -n 1 -c 'vlan remove 32 pbm=cd2,cd34'
bcmcmd -n 1 -c 'vlan remove 33 pbm=cd3,cd35'
bcmcmd -n 1 -c 'vlan remove 34 pbm=cd4,cd36'
bcmcmd -n 1 -c 'vlan remove 35 pbm=cd5,cd37'
bcmcmd -n 1 -c 'vlan remove 36 pbm=cd6,cd38'
bcmcmd -n 1 -c 'vlan remove 37 pbm=cd7,cd39'
bcmcmd -n 1 -c 'vlan remove 38 pbm=cd8,cd40'
bcmcmd -n 1 -c 'vlan remove 39 pbm=cd9,cd41'
bcmcmd -n 1 -c 'vlan remove 40 pbm=cd10,cd42'
bcmcmd -n 1 -c 'vlan remove 41 pbm=cd11,cd43'
bcmcmd -n 1 -c 'vlan remove 42 pbm=cd12,cd44'
bcmcmd -n 1 -c 'vlan remove 43 pbm=cd13,cd45'
bcmcmd -n 1 -c 'vlan remove 44 pbm=cd14,cd46'
bcmcmd -n 1 -c 'vlan remove 45 pbm=cd20,cd52'
bcmcmd -n 1 -c 'vlan remove 46 pbm=cd21,cd53'
bcmcmd -n 1 -c 'vlan remove 47 pbm=cd22,cd54'
bcmcmd -n 1 -c 'vlan remove 48 pbm=cd23,cd55'
bcmcmd -n 1 -c 'vlan remove 49 pbm=cd24,cd56'
bcmcmd -n 1 -c 'vlan remove 50 pbm=cd25,cd57'
bcmcmd -n 1 -c 'vlan remove 51 pbm=cd26,cd58'
bcmcmd -n 1 -c 'vlan remove 52 pbm=cd27,cd59'
bcmcmd -n 1 -c 'vlan remove 53 pbm=cd28,cd60'
bcmcmd -n 1 -c 'vlan remove 54 pbm=cd29,cd61'
bcmcmd -n 1 -c 'vlan remove 55 pbm=cd30,cd62'
bcmcmd -n 1 -c 'vlan remove 56 pbm=cd31,cd63'
bcmcmd -n 1 -c 'vlan remove 57 pbm=cd64,cd96'
bcmcmd -n 1 -c 'vlan remove 58 pbm=cd65,cd97'
bcmcmd -n 1 -c 'vlan remove 59 pbm=cd66,cd98'
bcmcmd -n 1 -c 'vlan remove 60 pbm=cd67,cd99'
bcmcmd -n 1 -c 'vlan remove 61 pbm=cd68,cd100'
bcmcmd -n 1 -c 'vlan remove 62 pbm=cd69,cd101'
bcmcmd -n 1 -c 'vlan remove 63 pbm=cd70,cd102'
bcmcmd -n 1 -c 'vlan remove 64 pbm=cd71,cd103'
bcmcmd -n 1 -c 'vlan remove 65 pbm=cd72,cd104'
bcmcmd -n 1 -c 'vlan remove 66 pbm=cd73,cd105'
bcmcmd -n 1 -c 'vlan remove 67 pbm=cd74,cd106'
bcmcmd -n 1 -c 'vlan remove 68 pbm=cd75,cd107'
bcmcmd -n 1 -c 'vlan remove 69 pbm=cd76,cd108'
bcmcmd -n 1 -c 'vlan remove 70 pbm=cd77,cd109'
bcmcmd -n 1 -c 'vlan remove 71 pbm=cd78,cd110'
bcmcmd -n 1 -c 'vlan remove 72 pbm=cd84,cd116'
bcmcmd -n 1 -c 'vlan remove 73 pbm=cd85,cd117'
bcmcmd -n 1 -c 'vlan remove 74 pbm=cd86,cd118'
bcmcmd -n 1 -c 'vlan remove 75 pbm=cd87,cd119'
bcmcmd -n 1 -c 'vlan remove 76 pbm=cd88,cd120'
bcmcmd -n 1 -c 'vlan remove 77 pbm=cd89,cd121'
bcmcmd -n 1 -c 'vlan remove 78 pbm=cd90,cd122'
bcmcmd -n 1 -c 'vlan remove 79 pbm=cd91,cd123'
bcmcmd -n 1 -c 'vlan remove 80 pbm=cd92,cd124'
bcmcmd -n 1 -c 'vlan remove 81 pbm=cd93,cd125'
bcmcmd -n 1 -c 'vlan remove 82 pbm=cd94,cd126'
bcmcmd -n 1 -c 'vlan remove 83 pbm=cd95,cd127'
bcmcmd -n 1 -c 'vlan remove 84 pbm=cd128,cd160'
bcmcmd -n 1 -c 'vlan remove 85 pbm=cd129,cd161'
bcmcmd -n 1 -c 'vlan remove 86 pbm=cd130,cd162'
bcmcmd -n 1 -c 'vlan remove 87 pbm=cd131,cd163'
bcmcmd -n 1 -c 'vlan remove 88 pbm=cd132,cd164'
bcmcmd -n 1 -c 'vlan remove 89 pbm=cd133,cd165'
bcmcmd -n 1 -c 'vlan remove 90 pbm=cd134,cd166'
bcmcmd -n 1 -c 'vlan remove 91 pbm=cd135,cd167'
bcmcmd -n 1 -c 'vlan remove 92 pbm=cd136,cd168'
bcmcmd -n 1 -c 'vlan remove 93 pbm=cd137,cd169'
bcmcmd -n 1 -c 'vlan remove 94 pbm=cd138,cd170'
bcmcmd -n 1 -c 'vlan remove 95 pbm=cd140,cd172'
bcmcmd -n 1 -c 'vlan remove 96 pbm=cd141,cd173'
bcmcmd -n 1 -c 'vlan remove 97 pbm=cd142,cd174'
bcmcmd -n 1 -c 'vlan remove 98 pbm=cd143,cd175'
bcmcmd -n 1 -c 'vlan remove 99 pbm=cd144,cd176'
bcmcmd -n 1 -c 'vlan remove 100 pbm=cd145,cd177'
bcmcmd -n 1 -c 'vlan remove 101 pbm=cd146,cd178'
bcmcmd -n 1 -c 'vlan remove 102 pbm=cd147,cd179'
bcmcmd -n 1 -c 'vlan remove 103 pbm=cd152,cd184'
bcmcmd -n 1 -c 'vlan remove 104 pbm=cd153,cd185'
bcmcmd -n 1 -c 'vlan remove 105 pbm=cd154,cd186'
bcmcmd -n 1 -c 'vlan remove 106 pbm=cd155,cd187'
bcmcmd -n 1 -c 'vlan remove 107 pbm=cd156,cd188'
bcmcmd -n 1 -c 'vlan remove 108 pbm=cd157,cd189'
bcmcmd -n 1 -c 'vlan remove 109 pbm=cd158,cd190'
bcmcmd -n 1 -c 'vlan remove 110 pbm=cd159,cd191'
bcmcmd -n 1 -c 'vlan remove 111 pbm=cd192,cd224'
bcmcmd -n 1 -c 'vlan remove 112 pbm=cd193,cd225'
bcmcmd -n 1 -c 'vlan remove 113 pbm=cd194,cd226'
bcmcmd -n 1 -c 'vlan remove 114 pbm=cd195,cd227'
bcmcmd -n 1 -c 'vlan remove 115 pbm=cd196,cd228'
bcmcmd -n 1 -c 'vlan remove 116 pbm=cd197,cd229'
bcmcmd -n 1 -c 'vlan remove 117 pbm=cd198,cd230'
bcmcmd -n 1 -c 'vlan remove 118 pbm=cd199,cd231'
bcmcmd -n 1 -c 'vlan remove 119 pbm=cd200,cd232'
bcmcmd -n 1 -c 'vlan remove 120 pbm=cd201,cd233'
bcmcmd -n 1 -c 'vlan remove 121 pbm=cd202,cd234'
bcmcmd -n 1 -c 'vlan remove 122 pbm=cd204,cd236'
bcmcmd -n 1 -c 'vlan remove 123 pbm=cd205,cd237'
bcmcmd -n 1 -c 'vlan remove 124 pbm=cd206,cd238'
bcmcmd -n 1 -c 'vlan remove 125 pbm=cd207,cd239'
bcmcmd -n 1 -c 'vlan remove 126 pbm=cd208,cd240'
bcmcmd -n 1 -c 'vlan remove 127 pbm=cd209,cd241'
bcmcmd -n 1 -c 'vlan remove 128 pbm=cd210,cd242'
bcmcmd -n 1 -c 'vlan remove 129 pbm=cd211,cd243'
bcmcmd -n 1 -c 'vlan remove 130 pbm=cd216,cd248'
bcmcmd -n 1 -c 'vlan remove 131 pbm=cd217,cd249'
bcmcmd -n 1 -c 'vlan remove 132 pbm=cd218,cd250'
bcmcmd -n 1 -c 'vlan remove 133 pbm=cd219,cd251'
bcmcmd -n 1 -c 'vlan remove 134 pbm=cd220,cd252'
bcmcmd -n 1 -c 'vlan remove 135 pbm=cd221,cd253'
bcmcmd -n 1 -c 'vlan remove 136 pbm=cd222,cd254'
bcmcmd -n 1 -c 'vlan remove 137 pbm=cd223,cd255'
+325
View File
@@ -0,0 +1,325 @@
bcmcmd -n 1 -c 'vlan remove 1 pbm=cd'
bcmcmd -n 1 -c 'vlan create 30'
bcmcmd -n 1 -c 'vlan add 30 pbm=cd0,cd32'
bcmcmd -n 1 -c 'vlan create 31'
bcmcmd -n 1 -c 'vlan add 31 pbm=cd1,cd33'
bcmcmd -n 1 -c 'vlan create 32'
bcmcmd -n 1 -c 'vlan add 32 pbm=cd2,cd34'
bcmcmd -n 1 -c 'vlan create 33'
bcmcmd -n 1 -c 'vlan add 33 pbm=cd3,cd35'
bcmcmd -n 1 -c 'vlan create 34'
bcmcmd -n 1 -c 'vlan add 34 pbm=cd4,cd36'
bcmcmd -n 1 -c 'vlan create 35'
bcmcmd -n 1 -c 'vlan add 35 pbm=cd5,cd37'
bcmcmd -n 1 -c 'vlan create 36'
bcmcmd -n 1 -c 'vlan add 36 pbm=cd6,cd38'
bcmcmd -n 1 -c 'vlan create 37'
bcmcmd -n 1 -c 'vlan add 37 pbm=cd7,cd39'
bcmcmd -n 1 -c 'vlan create 38'
bcmcmd -n 1 -c 'vlan add 38 pbm=cd8,cd40'
bcmcmd -n 1 -c 'vlan create 39'
bcmcmd -n 1 -c 'vlan add 39 pbm=cd9,cd41'
bcmcmd -n 1 -c 'vlan create 40'
bcmcmd -n 1 -c 'vlan add 40 pbm=cd10,cd42'
bcmcmd -n 1 -c 'vlan create 41'
bcmcmd -n 1 -c 'vlan add 41 pbm=cd11,cd43'
bcmcmd -n 1 -c 'vlan create 42'
bcmcmd -n 1 -c 'vlan add 42 pbm=cd12,cd44'
bcmcmd -n 1 -c 'vlan create 43'
bcmcmd -n 1 -c 'vlan add 43 pbm=cd13,cd45'
bcmcmd -n 1 -c 'vlan create 44'
bcmcmd -n 1 -c 'vlan add 44 pbm=cd14,cd46'
bcmcmd -n 1 -c 'vlan create 45'
bcmcmd -n 1 -c 'vlan add 45 pbm=cd20,cd52'
bcmcmd -n 1 -c 'vlan create 46'
bcmcmd -n 1 -c 'vlan add 46 pbm=cd21,cd53'
bcmcmd -n 1 -c 'vlan create 47'
bcmcmd -n 1 -c 'vlan add 47 pbm=cd22,cd54'
bcmcmd -n 1 -c 'vlan create 48'
bcmcmd -n 1 -c 'vlan add 48 pbm=cd23,cd55'
bcmcmd -n 1 -c 'vlan create 49'
bcmcmd -n 1 -c 'vlan add 49 pbm=cd24,cd56'
bcmcmd -n 1 -c 'vlan create 50'
bcmcmd -n 1 -c 'vlan add 50 pbm=cd25,cd57'
bcmcmd -n 1 -c 'vlan create 51'
bcmcmd -n 1 -c 'vlan add 51 pbm=cd26,cd58'
bcmcmd -n 1 -c 'vlan create 52'
bcmcmd -n 1 -c 'vlan add 52 pbm=cd27,cd59'
bcmcmd -n 1 -c 'vlan create 53'
bcmcmd -n 1 -c 'vlan add 53 pbm=cd28,cd60'
bcmcmd -n 1 -c 'vlan create 54'
bcmcmd -n 1 -c 'vlan add 54 pbm=cd29,cd61'
bcmcmd -n 1 -c 'vlan create 55'
bcmcmd -n 1 -c 'vlan add 55 pbm=cd30,cd62'
bcmcmd -n 1 -c 'vlan create 56'
bcmcmd -n 1 -c 'vlan add 56 pbm=cd31,cd63'
bcmcmd -n 1 -c 'vlan create 57'
bcmcmd -n 1 -c 'vlan add 57 pbm=cd64,cd96'
bcmcmd -n 1 -c 'vlan create 58'
bcmcmd -n 1 -c 'vlan add 58 pbm=cd65,cd97'
bcmcmd -n 1 -c 'vlan create 59'
bcmcmd -n 1 -c 'vlan add 59 pbm=cd66,cd98'
bcmcmd -n 1 -c 'vlan create 60'
bcmcmd -n 1 -c 'vlan add 60 pbm=cd67,cd99'
bcmcmd -n 1 -c 'vlan create 61'
bcmcmd -n 1 -c 'vlan add 61 pbm=cd68,cd100'
bcmcmd -n 1 -c 'vlan create 62'
bcmcmd -n 1 -c 'vlan add 62 pbm=cd69,cd101'
bcmcmd -n 1 -c 'vlan create 63'
bcmcmd -n 1 -c 'vlan add 63 pbm=cd70,cd102'
bcmcmd -n 1 -c 'vlan create 64'
bcmcmd -n 1 -c 'vlan add 64 pbm=cd71,cd103'
bcmcmd -n 1 -c 'vlan create 65'
bcmcmd -n 1 -c 'vlan add 65 pbm=cd72,cd104'
bcmcmd -n 1 -c 'vlan create 66'
bcmcmd -n 1 -c 'vlan add 66 pbm=cd73,cd105'
bcmcmd -n 1 -c 'vlan create 67'
bcmcmd -n 1 -c 'vlan add 67 pbm=cd74,cd106'
bcmcmd -n 1 -c 'vlan create 68'
bcmcmd -n 1 -c 'vlan add 68 pbm=cd75,cd107'
bcmcmd -n 1 -c 'vlan create 69'
bcmcmd -n 1 -c 'vlan add 69 pbm=cd76,cd108'
bcmcmd -n 1 -c 'vlan create 70'
bcmcmd -n 1 -c 'vlan add 70 pbm=cd77,cd109'
bcmcmd -n 1 -c 'vlan create 71'
bcmcmd -n 1 -c 'vlan add 71 pbm=cd78,cd110'
bcmcmd -n 1 -c 'vlan create 72'
bcmcmd -n 1 -c 'vlan add 72 pbm=cd84,cd116'
bcmcmd -n 1 -c 'vlan create 73'
bcmcmd -n 1 -c 'vlan add 73 pbm=cd85,cd117'
bcmcmd -n 1 -c 'vlan create 74'
bcmcmd -n 1 -c 'vlan add 74 pbm=cd86,cd118'
bcmcmd -n 1 -c 'vlan create 75'
bcmcmd -n 1 -c 'vlan add 75 pbm=cd87,cd119'
bcmcmd -n 1 -c 'vlan create 76'
bcmcmd -n 1 -c 'vlan add 76 pbm=cd88,cd120'
bcmcmd -n 1 -c 'vlan create 77'
bcmcmd -n 1 -c 'vlan add 77 pbm=cd89,cd121'
bcmcmd -n 1 -c 'vlan create 78'
bcmcmd -n 1 -c 'vlan add 78 pbm=cd90,cd122'
bcmcmd -n 1 -c 'vlan create 79'
bcmcmd -n 1 -c 'vlan add 79 pbm=cd91,cd123'
bcmcmd -n 1 -c 'vlan create 80'
bcmcmd -n 1 -c 'vlan add 80 pbm=cd92,cd124'
bcmcmd -n 1 -c 'vlan create 81'
bcmcmd -n 1 -c 'vlan add 81 pbm=cd93,cd125'
bcmcmd -n 1 -c 'vlan create 82'
bcmcmd -n 1 -c 'vlan add 82 pbm=cd94,cd126'
bcmcmd -n 1 -c 'vlan create 83'
bcmcmd -n 1 -c 'vlan add 83 pbm=cd95,cd127'
bcmcmd -n 1 -c 'vlan create 84'
bcmcmd -n 1 -c 'vlan add 84 pbm=cd128,cd160'
bcmcmd -n 1 -c 'vlan create 85'
bcmcmd -n 1 -c 'vlan add 85 pbm=cd129,cd161'
bcmcmd -n 1 -c 'vlan create 86'
bcmcmd -n 1 -c 'vlan add 86 pbm=cd130,cd162'
bcmcmd -n 1 -c 'vlan create 87'
bcmcmd -n 1 -c 'vlan add 87 pbm=cd131,cd163'
bcmcmd -n 1 -c 'vlan create 88'
bcmcmd -n 1 -c 'vlan add 88 pbm=cd132,cd164'
bcmcmd -n 1 -c 'vlan create 89'
bcmcmd -n 1 -c 'vlan add 89 pbm=cd133,cd165'
bcmcmd -n 1 -c 'vlan create 90'
bcmcmd -n 1 -c 'vlan add 90 pbm=cd134,cd166'
bcmcmd -n 1 -c 'vlan create 91'
bcmcmd -n 1 -c 'vlan add 91 pbm=cd135,cd167'
bcmcmd -n 1 -c 'vlan create 92'
bcmcmd -n 1 -c 'vlan add 92 pbm=cd136,cd168'
bcmcmd -n 1 -c 'vlan create 93'
bcmcmd -n 1 -c 'vlan add 93 pbm=cd137,cd169'
bcmcmd -n 1 -c 'vlan create 94'
bcmcmd -n 1 -c 'vlan add 94 pbm=cd138,cd170'
bcmcmd -n 1 -c 'vlan create 95'
bcmcmd -n 1 -c 'vlan add 95 pbm=cd140,cd172'
bcmcmd -n 1 -c 'vlan create 96'
bcmcmd -n 1 -c 'vlan add 96 pbm=cd141,cd173'
bcmcmd -n 1 -c 'vlan create 97'
bcmcmd -n 1 -c 'vlan add 97 pbm=cd142,cd174'
bcmcmd -n 1 -c 'vlan create 98'
bcmcmd -n 1 -c 'vlan add 98 pbm=cd143,cd175'
bcmcmd -n 1 -c 'vlan create 99'
bcmcmd -n 1 -c 'vlan add 99 pbm=cd144,cd176'
bcmcmd -n 1 -c 'vlan create 100'
bcmcmd -n 1 -c 'vlan add 100 pbm=cd145,cd177'
bcmcmd -n 1 -c 'vlan create 101'
bcmcmd -n 1 -c 'vlan add 101 pbm=cd146,cd178'
bcmcmd -n 1 -c 'vlan create 102'
bcmcmd -n 1 -c 'vlan add 102 pbm=cd147,cd179'
bcmcmd -n 1 -c 'vlan create 103'
bcmcmd -n 1 -c 'vlan add 103 pbm=cd152,cd184'
bcmcmd -n 1 -c 'vlan create 104'
bcmcmd -n 1 -c 'vlan add 104 pbm=cd153,cd185'
bcmcmd -n 1 -c 'vlan create 105'
bcmcmd -n 1 -c 'vlan add 105 pbm=cd154,cd186'
bcmcmd -n 1 -c 'vlan create 106'
bcmcmd -n 1 -c 'vlan add 106 pbm=cd155,cd187'
bcmcmd -n 1 -c 'vlan create 107'
bcmcmd -n 1 -c 'vlan add 107 pbm=cd156,cd188'
bcmcmd -n 1 -c 'vlan create 108'
bcmcmd -n 1 -c 'vlan add 108 pbm=cd157,cd189'
bcmcmd -n 1 -c 'vlan create 109'
bcmcmd -n 1 -c 'vlan add 109 pbm=cd158,cd190'
bcmcmd -n 1 -c 'vlan create 110'
bcmcmd -n 1 -c 'vlan add 110 pbm=cd159,cd191'
bcmcmd -n 1 -c 'vlan create 111'
bcmcmd -n 1 -c 'vlan add 111 pbm=cd192,cd224'
bcmcmd -n 1 -c 'vlan create 112'
bcmcmd -n 1 -c 'vlan add 112 pbm=cd193,cd225'
bcmcmd -n 1 -c 'vlan create 113'
bcmcmd -n 1 -c 'vlan add 113 pbm=cd194,cd226'
bcmcmd -n 1 -c 'vlan create 114'
bcmcmd -n 1 -c 'vlan add 114 pbm=cd195,cd227'
bcmcmd -n 1 -c 'vlan create 115'
bcmcmd -n 1 -c 'vlan add 115 pbm=cd196,cd228'
bcmcmd -n 1 -c 'vlan create 116'
bcmcmd -n 1 -c 'vlan add 116 pbm=cd197,cd229'
bcmcmd -n 1 -c 'vlan create 117'
bcmcmd -n 1 -c 'vlan add 117 pbm=cd198,cd230'
bcmcmd -n 1 -c 'vlan create 118'
bcmcmd -n 1 -c 'vlan add 118 pbm=cd199,cd231'
bcmcmd -n 1 -c 'vlan create 119'
bcmcmd -n 1 -c 'vlan add 119 pbm=cd200,cd232'
bcmcmd -n 1 -c 'vlan create 120'
bcmcmd -n 1 -c 'vlan add 120 pbm=cd201,cd233'
bcmcmd -n 1 -c 'vlan create 121'
bcmcmd -n 1 -c 'vlan add 121 pbm=cd202,cd234'
bcmcmd -n 1 -c 'vlan create 122'
bcmcmd -n 1 -c 'vlan add 122 pbm=cd204,cd236'
bcmcmd -n 1 -c 'vlan create 123'
bcmcmd -n 1 -c 'vlan add 123 pbm=cd205,cd237'
bcmcmd -n 1 -c 'vlan create 124'
bcmcmd -n 1 -c 'vlan add 124 pbm=cd206,cd238'
bcmcmd -n 1 -c 'vlan create 125'
bcmcmd -n 1 -c 'vlan add 125 pbm=cd207,cd239'
bcmcmd -n 1 -c 'vlan create 126'
bcmcmd -n 1 -c 'vlan add 126 pbm=cd208,cd240'
bcmcmd -n 1 -c 'vlan create 127'
bcmcmd -n 1 -c 'vlan add 127 pbm=cd209,cd241'
bcmcmd -n 1 -c 'vlan create 128'
bcmcmd -n 1 -c 'vlan add 128 pbm=cd210,cd242'
bcmcmd -n 1 -c 'vlan create 129'
bcmcmd -n 1 -c 'vlan add 129 pbm=cd211,cd243'
bcmcmd -n 1 -c 'vlan create 130'
bcmcmd -n 1 -c 'vlan add 130 pbm=cd216,cd248'
bcmcmd -n 1 -c 'vlan create 131'
bcmcmd -n 1 -c 'vlan add 131 pbm=cd217,cd249'
bcmcmd -n 1 -c 'vlan create 132'
bcmcmd -n 1 -c 'vlan add 132 pbm=cd218,cd250'
bcmcmd -n 1 -c 'vlan create 133'
bcmcmd -n 1 -c 'vlan add 133 pbm=cd219,cd251'
bcmcmd -n 1 -c 'vlan create 134'
bcmcmd -n 1 -c 'vlan add 134 pbm=cd220,cd252'
bcmcmd -n 1 -c 'vlan create 135'
bcmcmd -n 1 -c 'vlan add 135 pbm=cd221,cd253'
bcmcmd -n 1 -c 'vlan create 136'
bcmcmd -n 1 -c 'vlan add 136 pbm=cd222,cd254'
bcmcmd -n 1 -c 'vlan create 137'
bcmcmd -n 1 -c 'vlan add 137 pbm=cd223,cd255'