Link Bring-Up 序列
地址适用于
libtpu-0.0.40-cp314wheel 中的libtpu.so。其他版本会有差异。二进制文件:extracted/libtpu-0.0.40-cp314-cp314-manylinux_2_31_x86_64/libtpu/libtpu.so(build-id89edbbe81c5b328a958fe628a9f2207d;.textVMA == 文件偏移)。下面的符号从完整符号二进制 demangle 得到,并已与 IDA 反编译结果交叉核对。
摘要
ICI link bring-up 是指把 pod-slice 中每个芯片上的四个冷 SerDes 端口推进到 data-link layer 已 up、routing 已安装、global time counter 已同步的状态;这是任何 collective 移动一个字节之前的前置条件。本页负责记录控制器 accel_ssw::deepsea::slice_builder::Master 运行的 slice-wide bring-up 序列:Master::InitSlice @0x1fbbaac0 的 16 步编排、每芯片 data-link 轮询循环 IciControl::WaitForLinksUp @0xe7b1060、轮询检查的 7 值 LinkStackReadyState enum,以及 SerDes 端口实际启用的位置(EnableIciDataLink fan-out → driver EnableIciPorts)。
这个形状对做过多节点 fabric bring-up 的人会很熟悉(InfiniBand subnet manager、NVLink/NVSwitch fabric manager):一个全局排序器通过 RPC 把工作 fan out 到每节点 agent,然后在继续前轮询每个节点直到 ready。它与这些 fabric 的不同之处在于固件/主机边界。Analog PHY,即 SerDes calibration、adaptive equalization、lane lock、64b/66b alignment,完全由芯片 embedded core 上的固件拥有;主机没有进入其中的软件 hook。主机只翻转 enable_ici_serdes_training,然后通过单个每端口 3-bit port_ready_state register 观察进度,该寄存器被重新映射到软件 LinkStackReadyState enum。Data-link layer 及其以上全部由主机/驱动拥有,这正是 Master 排序并由 IciControl 轮询的内容。
本页记录重新实现者必须复现的三件事:(1) 16 步 InitSlice 序列:哪些步骤是 ExecuteOnAllWorkers gRPC fan-out,哪些是 local-and-locked,哪些是 sequential,以及其中两个步骤的 gate;(2) WaitForLinksUp 轮询循环:固定 1 ms AbslInternalSleepFor quantum(当剩余预算低于 1 ms 时 clamp 到剩余预算)、每 link 的退出条件 IsLinkUp ∧ GetLinkStackReadyState,以及 deadline 算术;(3) link-state 模型:0xe7b6400 处的 firmware-to-software state remap、7 值 LinkStackReadyState enum,以及每端口 DataLinkLayerState。Topology discovery(步骤 2 的 payload)在 Topology Discovery;fault/reset 路径在 Failure Recovery;章节地图在 overview。本页不重复这些内容。
重新实现时,契约是:
- 16 步
InitSlice顺序以及每个步骤的 dispatch class(fanout / local-locked / sequential / gated)。顺序是正确性约束:routing 在 data-link 启用前安装;在推送坐标前等待 data-link-up。 - PHY/host 边界:主机通过
EnableIciPorts写入enable_ici_serdes_training+disabled_serdes_indexmask,然后轮询每端口port_ready_state。它绝不触碰 analog PHY。 WaitForLinksUp轮询:每 link 满足IsLinkUp(l) == up ∧ GetLinkStackReadyState(l) == ready,以固定 1 ms quantum sleep(0x3D0900q-ns;低于 1 ms 时 clamp 到剩余预算),由Now() + configure_ici_timeout + wait_for_data_link_up_timeout限界。- state 模型:firmware
port_ready_state ∈ [0,7]→LinkStackReadyState(0xe7b6400处 identity remap,≥8 为错误),以及 driver 在 enable 时翻转、reset 时清除的每端口DataLinkLayerState。
| Slice controller entry | Master::InitSlice @0x1fbbaac0(578 行反编译;11 个 ExecuteOnAllWorkers site) |
| 步骤数 | 16 个有序步骤;约 11 个 fanout,其余为 local-locked / sequential |
| DL-up 轮询循环 | IciControl::WaitForLinksUp @0xe7b1060(set<int>, absl::Duration, bool) |
| 轮询 quantum | 固定 1 ms(mov $0x3D0900,%eax @0xe7b11c2);comparand cmp $0x3D0901 @0xe7b1198 在低于 1 ms 时选择完整剩余预算 sleep |
| 每 link 退出条件 | IsLinkUp @0xe7afe80 ∧ GetLinkStackReadyState @0xe7afd00 |
| Link-state enum | LinkStackReadyState — 7 个值,descriptor 0xe7b6540;FW→SW remap 0xe7b6400 |
| SerDes enable(driver) | jfc::Ici::EnableIciPorts @0xe7accc0 / dfc::Ici::EnableIciPorts @0xe76e980 |
| Enable-once gate | SliceConfiguration offset 0xe8(ports_enabled)— "ICI ports should only be enabled once." |
1. Master::InitSlice — 16 步序列
目的
Master::InitSlice @0x1fbbaac0 是 slice bring-up 的单个全局排序器。每个 pod-slice 有一个 Master;它拥有跨芯片顺序,并把每个步骤驱动为 ExecuteOnAllWorkers gRPC fan-out(同一个 per-worker callable 并行投递到每个 peer 的 SliceBuilderWorkerService,然后 join)、Master::mu_ 下的本地加锁计算,或顺序 per-worker walk。Fan-out/local 分工很重要:本地步骤(discovery、routing-table generation、GTC-tree generation)必须先在 controller 上完成,然后安装其结果的 fan-out 才能运行。
入口点
Master::InitSlice (0x1fbbaac0) ── slice-wide orderer, under Master::mu_
├─ ExecuteOnAllWorkers(GetLocalTopology) ── step 1, fanout → per-worker link sets
├─ DiscoverTopology (0x1fbbe4e0) ── step 2, local → topology-discovery.md
├─ ExecuteOnAllWorkers(SetGlobalChipId) ── step 3, fanout
├─ DetectRoutingTableDeadlock (0x1fbbed60) ── step 5, gated on this+0x90
├─ ExecuteOnAllWorkers(SetRoutingTable) ── step 6, fanout → ../routing
├─ ExecuteOnAllWorkers(SetGtcConfiguration) ── step 8, fanout
├─ ExecuteOnAllWorkers(ControlIciErrorReport) ── step 9, gated fanout — masks bring-up errors
├─ ExecuteOnAllWorkers(EnableIciDataLink) ── step 10, fanout → PHY + DL kick-off (§3)
└─ ExecuteOnAllWorkers × 4 (steps 11,14 + GTC) ── WaitForDataLinkUp / GTC reset / SetChipCoordinates
```text
> **NOTE —** `InitSlice` 的反编译结果正好包含 **11** 个 `ExecuteOnAllWorkers` call site(已验证)。前六个把一个具名 `Master::` 方法绑定为 callable(`GetLocalTopology`、`SetGlobalChipId`、`SetRoutingTable`、`SetGtcConfiguration`、`ControlIciErrorReport`、`EnableIciDataLink`);最后四个(反编译第 424/458/475/493/512 行)通过 vtable-relative offset 绑定 member-function pointer,本次反编译未能解析为符号。它们的身份(WaitForDataLinkUp、ClearGlobalGtc/WaitForGtcReset、SetChipCoordinates、BroadcastSliceInformation/DisableIciInterrupts)从周围的 `Worker::` RPC handler(§4)重建,标记为 HIGH,而不是 CERTAIN。
### 算法
```c
function Master_InitSlice(this): // 0x1fbbaac0
lock(this->mu_) // held across phase boundaries
drain_stale_local_topology() // step 0 — LocalTopology dtor on cached entries
// ---- discovery ----
ExecuteOnAllWorkers(&Master::GetLocalTopology) // 1 fanout: each worker returns its links
if !ok: goto fail
DiscoverTopology(this) // 2 local+locked → ResilientToroidalTopology
ExecuteOnAllWorkers(&Master::SetGlobalChipId) // 3 fanout: push Cartesian-ordered chip-id map
// ---- routing ----
GenerateRoutingTables() // 4 local: RoutingTableGeneratorFactory
if this->flag_0x90: // 5 gated — only if deadlock-check enabled
DetectRoutingTableDeadlock(this) // walk channel-dependency graph for cycles
ExecuteOnAllWorkers(&Master::SetRoutingTable) // 6 fanout: install per-link ICR tables
// ---- GTC tree ----
GenerateGtcTree() // 7 local: root/leaf assignment
ExecuteOnAllWorkers(&Master::SetGtcConfiguration) // 8 fanout
// ---- error masking + link enable ----
if error_report_gate: // 9 gated fanout
ExecuteOnAllWorkers(&Master::ControlIciErrorReport) // mask bring-up errors
ExecuteOnAllWorkers(&Master::EnableIciDataLink) // 10 fanout: PHY + DL training kick-off (§3)
// ---- per-chip DL-up wait, then GTC resync, then coordinates ----
ExecuteOnAllWorkers(/* WaitForDataLinkUp */) // 11 per-chip DL-up poll → IciControl::WaitForLinksUp
ExecuteOnAllWorkers(/* ClearGlobalGtc */) // 12
ExecuteOnAllWorkers(/* WaitForGtcReset */) // 13
ExecuteOnAllWorkers(/* SetChipCoordinates */) // 14 push (X,Y,Z) per chip
BroadcastSliceInformation(); DisableIciInterrupts() // 15,16 sequential — quiesce bring-up IRQ
unlock(this->mu_)
return OK
fail:
FailSlice(SLICE_FAILURE_INIT_ERROR) // → failure-recovery.md值得内化的是这些 ordering invariant。Routing 在 data-link 启用(步骤 10)之前安装(步骤 6),因为第一批 DL traffic 流动时 routing table 必须已经驻留;坐标在 DL-up 确认(步骤 11)之后推送(步骤 14),因为 chip-id-to-coordinate map 只有在 discovery 折叠每个 worker 的 link 后才有效。两个 gated 步骤,即 deadlock detection(步骤 5,由 this+0x90 gate)和 ControlIciErrorReport(步骤 9),跳过也不会破坏正确性;deadlock detection 是诊断,error reporting 只是在 training 期间 mask 掉 transient PHY noise,避免进入 failure path。
步骤表
(fanout) = ExecuteOnAllWorkers gRPC broadcast-and-join;(local) = 在 controller 上、Master::mu_ 下执行;(seq) = per-worker sequential RPC;(gated) = 条件执行。
| # | Step | Dispatch | Implementation |
|---|---|---|---|
| 0 | Drain stale local topology | local | cached entries 上的 LocalTopology dtor |
| 1 | Discover local topology | fanout | Master::GetLocalTopology → per-worker link set |
| 2 | Aggregate → global topology | local | Master::DiscoverTopology @0x1fbbe4e0 |
| 3 | Set global chip IDs | fanout | Master::SetGlobalChipId @0x1fbbe7e0 |
| 4 | Generate routing tables | local | RoutingTableGeneratorFactory::Generate |
| 5 | Detect routing-table deadlock | gated local | Master::DetectRoutingTableDeadlock @0x1fbbed60(若 this+0x90) |
| 6 | Install routing tables | fanout | Master::SetRoutingTable @0x1fbbf6e0 |
| 7 | Generate GTC tree | local | global-time-counter root/leaf |
| 8 | Install GTC configuration | fanout | Master::SetGtcConfiguration @0x1fbc0580 |
| 9 | Control ICI error reporting | gated fanout | Master::ControlIciErrorReport @0x1fbc0d00 |
| 10 | Enable ICI data link | fanout | Master::EnableIciDataLink @0x1fbc0ee0(§3) |
| 11 | Wait for data-link-up | seq | Master::WaitForDataLinkUp @0x1fbc3b20 → IciControl::WaitForLinksUp(§2) |
| 12 | Clear / reset global GTC | seq | Master::ClearGlobalGtc @0x1fbc3d80 |
| 13 | Wait for GTC reset | seq | Master::WaitForGtcReset @0x1fbc3fe0 |
| 14 | Set chip coordinates | fanout | Master::SetChipCoordinates @0x1fbc4640 |
| 15 | Broadcast slice information | seq | Master::BroadcastSliceInformation @0x1fbc4240 |
| 16 | Disable ICI interrupts | seq | Master::DisableIciInterrupts @0x1fbc4a80 |
GOTCHA — 步骤 11(
WaitForDataLinkUp)在原始设计意图中不是ExecuteOnAllWorkersfan-out;原始分类把它标为 sequential,“wait per chip, not fanned out”。反编译在尾部 block 中显示了一个 fan-out call site,但每个 worker 内部的 per-chip poll(Worker::WaitForDataLinkUp@0x1fc417e0→ driverWaitForDataLinkUp→IciControl::WaitForLinksUp)会阻塞,直到该芯片的 link up 或 deadline 过期。无论 controller 是并发等待所有芯片(fanout-join)还是串行等待,per-chip blocking 语义相同,且 deadline 可通过WaitForDataLinkUpRequest_ChipDataLinkUpTimeout按芯片覆盖。把步骤 11 视为“每个芯片都必须达到 DL-up,之后任何芯片才可进入步骤 12”。
Phase-to-RPC 映射
每个 fan-out 的 worker 侧都是 SliceBuilderWorkerService 上的方法(RPC 前缀 /accel_ssw.deepsea.slice_builder.SliceBuilderWorkerService/)。恢复出的 worker entry point:
| Step | RPC | Worker entry (VA) | Request → Reply |
|---|---|---|---|
| 3 | SetGlobalChipId | Worker::SetGlobalChipId | SetGlobalChipIdRequest → …Reply |
| 6 | SetRoutingTable | Worker::SetRoutingTable @0x1fc40140 | SetRoutingTableRequest → …Reply |
| 8 | SetGtcConfiguration | Worker::SetGtcConfiguration @0x1fc40760 | SetGtcConfigurationRequest → …Reply |
| 10 | EnableIciDataLink | Worker::EnableIciDataLink @0x1fc411c0 | EnableIciDataLinkRequest → …Reply |
| 11 | WaitForDataLinkUp | Worker::WaitForDataLinkUp @0x1fc417e0 | WaitForDataLinkUpRequest → …Reply |
| 12 | ClearGlobalGtc | Worker::ClearGlobalGtc @0x1fc41c80 | ClearGlobalGtcRequest → …Reply |
| 13 | WaitForGtcReset | Worker::WaitForGtcReset @0x1fc42120 | WaitForGtcResetRequest → …Reply |
| teardown | LinksDownReset | Worker::LinksDownReset @0x1fc430a0 | LinksDownResetRequest → …Reply(failure-recovery) |
NOTE — Cloud 部署在
Master和 driver 之间插入tpunetddaemon(SuperpodController → tpunetd → driver)。ICI 子集的 protobuf message shape 相同;Cloud envelope 是superpod.tpunetd.ConfigureIciRequest,带三个 oneof arm(EnableIciDataLinkRequest、WaitForDataLinkUpRequest、ResetIciNetworkRequest)。只有 transport 不同,因此上面的 16 步顺序在两条路径上都成立。
2. IciControl::WaitForLinksUp — data-link 轮询循环
目的
IciControl::WaitForLinksUp @0xe7b1060 是步骤 11 最终阻塞等待的芯片本地轮询。给定一个 link index 的 std::set<int>、一个 absl::Duration deadline budget,以及一个 boolean(include-loopback / verbose),它会自旋到每个请求的 link 都报告 hardware-up 且 firmware state ready,或 deadline 过期;过期时返回 DEADLINE_EXCEEDED,并携带 offending link 的每 link state。这是主机与固件拥有的 PHY training 之间唯一的同步点:analog bring-up 是黑盒,主机通过这个循环得知它已完成。
入口点
Master::WaitForDataLinkUp (0x1fbc3b20) ── slice step 11, reads timeout offsets
└─ Worker::WaitForDataLinkUp (0x1fc417e0) ── per-worker RPC handler
└─ ici::SliceConfiguration::WaitForDataLinkUp (0x1fdb46e0)
└─ IciControl::WaitForLinksReadyAndUp (0xe7b0780) ── umbrella: refresh + waitUp
├─ IciControl::WaitForLinkStateRefresh (0xe7b0ec0) ── one-shot, no loop
└─ IciControl::WaitForLinksUp (0xe7b1060) ── the poll loop (this section)
├─ IciControl::IsLinkUp (0xe7afe80) ── HW link-up bit per port
├─ IciControl::GetLinkStackReadyState (0xe7afd00) ── FW state per port (§3)
└─ AbslInternalSleepFor(quantum)
```text
### 算法
```c
function WaitForLinksUp(this, links, budget, include_loopback): // 0xe7b1060
deadline = absl::Now() + budget // budget = configure_ici_timeout
// + wait_for_data_link_up_timeout (§ Deadlines)
loop:
all_up = true
for link in links: // set<int>, ascending
up = IsLinkUp(link) // 0xe7afe80 — HW link-up bit
state = GetLinkStackReadyState(link) // 0xe7afd00 — FW state → LinkStackReadyState (§3)
if not (up == 1 and state == kReady):
all_up = false
if link is unrecognized: log "Unrecognized data link layer state: <v>"
if all_up:
return OK
now = absl::Now()
if now >= deadline: // deadline reached
return DEADLINE_EXCEEDED(per-link state) // names rendered via NameOfDenseEnum<...,0,7>
remaining = deadline - now
// quantum selection (verified constants):
// if remaining >= 0x3D0901 q-ns (> 1 ms) → sleep a fixed 1 ms (0x3D0900 q-ns)
// else → sleep the whole remaining budget
quantum = (remaining > 1ms) ? 1ms : remaining
AbslInternalSleepFor(quantum) // yields; firmware advances PHY in the gap从反汇编恢复出的单个 comparand(cmp $0x3D0901,%edx @0xe7b1198,反编译第 299 行)是 encoded quarter-nanosecond 剩余预算上的 off-by-one 上界 guard:当 seconds 部分为零且 sub-second 部分 < 0x3D0901 q-ns(≤ 1 ms)时,循环 sleep 完整剩余预算;否则 sleep 固定 0x3D0900 = 4,000,000 q-ns = 1 ms(mov $0x3D0900,%eax @0xe7b11c2)。这个循环中没有第二种(500 ms)cadence;quantum 是固定 1 ms 并带 short-budget clamp,不是 dual-tier back-off。absl::InfiniteFuture(int64 max,low word 0xFFFFFFFF)会把 deadline 折叠为 gpr_inf_future,循环无限期阻塞。
QUIRK — 每 link failure message 由
proto2::internal::NameOfDenseEnum<&LinkStackReadyState_descriptor, 0, 7>渲染(在反编译第 947–950 行验证)— 第二个 template argument7是 enum arity,确认 7 值LinkStackReadyStateenum。名称在运行时从 proto descriptor 拉取,而不是来自.rodatastring literal,因此即使 arity 可证,静态 dump 二进制也看不到这七个 enum-value 字符串。
Deadlines 和 retries
Master::WaitForDataLinkUp @0x1fbc3b20 从 Master 读取两个 absl::Duration 字段,并求和形成向下传递的 budget:
| Master offset | Field | Feeds |
|---|---|---|
0x14:0x1c | configure_ici_timeout | 步骤 10(EnableIciDataLink)PHY-training budget |
0x30:0x38 | wait_for_data_link_up_timeout | 步骤 11(WaitForDataLinkUp)DL-up budget |
每次调用的 gRPC deadline 是 absl::Now() + configure_ici_timeout + wait_for_data_link_up_timeout,通过 absl::ToTimespec 转为 timespec 并存入 ClientContext。如果任一项是 absl::InfiniteFuture,deadline 会折叠为 gpr_inf_future。编译内置默认值从 slice_builder::Options 填充(通过 MasterFactory::Create @0x1fbb6a20 构造),但未恢复(LOW)— 它们可由用户/env 覆盖。面向用户的 knob 是 wait_for_data_link_up_timeout(per-call deadline override;针对异构 pod,存在 per-chip override WaitForDataLinkUpRequest_ChipDataLinkUpTimeout)和 max_ici_retries_per_minute(per-link retry budget,由 IciControl::UpdateAndGetRetriesPerMinute @0xe7af540 通过 RetryHistory ring buffer 强制执行)。
同级 state-check 函数
WaitForLinksUp 是 IciControl 上一组 state inspector 之一;重新实现者应知道该调用哪一个:
| Function | VA | Behavior |
|---|---|---|
IciControl::AllLinksUp(span<int>, bool) | 0xe7b0200 | Snapshot check,不等待 |
IciControl::IsLinkUp(int) | 0xe7afe80 | 单端口 HW link-up bit |
IciControl::GetLinkStackReadyState(int) | 0xe7afd00 | 单端口 FW state(§3) |
IciControl::GetValidLinks(bool) | 0xe7b0980 | 枚举 enabled non-loopback ports |
IciControl::WaitForLinkStateRefresh(Duration) | 0xe7b0ec0 | One-shot refresh poll,无循环 |
IciControl::WaitForLinksReadyAndUp(Duration, bool) | 0xe7b0780 | Umbrella:refresh 然后 WaitForLinksUp |
IciControl::WaitForLinksUp(set<int>, Duration, bool) | 0xe7b1060 | 轮询循环(本节) |
GOTCHA —
GetValidLinks(bool include_loopback)会排除固件留在 loopback mode 的任何端口("<port> is incorrectly left in loopback mode. Ignoring this link for ICI links discovery.")。如果重新实现者把原始 0..3 port set 传给WaitForLinksUp,而不是传GetValidLinks的结果,就会挂起等待一个永远不会到达 peer 的 loopback port。
3. SerDes 端口启用和固件/主机边界
目的
步骤 10(EnableIciDataLink)是 SerDes 端口实际开启的位置。Slice 侧 Master::EnableIciDataLink @0x1fbc0ee0 构建 per-link configuration 并 fan out;每个 worker 将它交给芯片本地 driver,driver 通过 EnableIciPorts 写入面向固件的 enable。这是主机拥有的 data-link control 与固件拥有的 PHY training 之间的精确边界。
PHY 由固件拥有
主机对 analog PHY 没有软件 hook。SerDes calibration、adaptive equalization、eye-opening、baud/lane lock 和 64b/66b alignment 都运行在芯片 embedded DeepSea CM/MGT firmware 上。主机对 PHY bring-up 的全部贡献,就是通过 EnableIciPorts 写入三个 flag:
enable_ici_serdes_training— gate PHY-level training(kick-off)。ignore_external_ici_ports— 忽略未连接的(tray-external)端口。disabled_serdes_index— per-link disable mask。
写入这些之后,主机观察 PHY 进度的唯一窗口是每端口 3-bit cm_scratch_user_firmware::link_stack_ready_state::port_ready_state register 和 IciSerdesInterrupt IRQ。因此主机侧时间线(§2 轮询)把 PHY training 视为由 configure_ici_timeout 预算约束的黑盒延迟。
算法
function Master_EnableIciDataLink(this, target, stub): // 0x1fbc0ee0
req = EnableIciDataLinkRequest()
for each owned chip/link:
cfg = req.add_ici_data_link_configuration() // RepeatedPtrFieldBase::Add<...
// IciDataLinkConfiguration> @0x1fbc6ba0
cfg.set_*(...) // per-link: serdes-training, disable mask, ...
stub->EnableIciDataLink(req) → worker → driver
function Driver_EnableIciPorts(span<int> links): // jfc 0xe7accc0 / dfc 0xe76e980
// host-side: write enable_ici_serdes_training + disabled_serdes_index to firmware mailbox
// firmware then runs the analog PHY bring-up asynchronously
for link in links:
IciPortUser::SetDataLinkLayerState(link, /*on=*/true) // 0x1fe8a2e0
// host now leaves; progress observed only via port_ready_state (§ State model)
```text
Per-link config 是 repeated `EnableIciDataLinkRequest_IciDataLinkConfiguration` sub-message(`RepeatedPtrFieldBase::Add<…IciDataLinkConfiguration>` site 在 `EnableIciDataLink` 反编译第 133 行验证)。它的字段级 layout 只能在运行时通过 protobuf descriptor 恢复,这里**不枚举**(LOW)。
### `EnableIci` one-shot gate
芯片本地 `jxc::SliceConfiguration::EnableIci` @`0xe799da0` 设置 `ports_enabled`(offset `0xe8` = `+232`,在 `slice_configuration.cc:291–295` 设置),`WaitForDataLinkUp` 再读回它作为 enable-before-wait gate。One-shot guard 本身在下一层 driver 的 `jfc::Ici::EnableIciPorts`(以及 `dfc` twin)中,它维护自己的 bool 并拒绝再次 enable:
| Guard | Owner / VA | Trigger | Message |
|---|---|---|---|
| Enable-once | `jfc::Ici::EnableIciPorts` @`0xe7accc0`(`ici.cc:111`);flag at `+0x14` | 已 enabled 时调用 `EnableIciPorts` | `"ICI ports should only be enabled once."` |
| Enable-before-wait | `jxc::SliceConfiguration::WaitForDataLinkUp` @`0xe799ec0`(`slice_configuration.cc:307`);测试 `0xe8` | `0xe8 == 0` 时调用 `WaitForDataLinkUp` | `"EnableIci() must be called before WaitForDataLinkUp()"` |
`LinksDownReset`([failure-recovery](failure-recovery.md))会清除 `0xe8`,使 slice 可在 reset 后重新 enable。
### Driver-side bring-up state
芯片本地 `SliceConfiguration` 持有每端口 DL state 和 enable bookkeeping。字节精确锚定的字段是 `asic_sw::driver::deepsea::jxc::SliceConfiguration` 中 offset `0xe8`(`= +232`)处的 `ports_enabled` bool;现代 `ici::SliceConfiguration` @`0x1fdb43e0` 携带相同逻辑状态但 layout 偏移不同(其 enabled-port array/length/capacity 位于 `0x108`/`0x110`/`0x118`,已在 `ici::SliceConfiguration::EnableIci` 中验证)。重新实现者必须复制的字段:
| Offset | Class | Type | Field | Set by | Cleared by |
|---|---|---|---|---|---|
| `0xe8` | `jxc::SliceConfiguration` | bool | `ports_enabled`(已调用 `EnableIci`) | `jxc::SliceConfiguration::EnableIci` @`0xe799da0`(`slice_configuration.cc:291–295`) | `LinksDownReset` @`0xe79a440`(`+232 = 0`) |
| `0x108` | `ici::SliceConfiguration` | int* | enabled-port indices | `ici::SliceConfiguration::EnableIci` @`0x1fdb43e0` | `LinksDownReset` |
| `0x110` | `ici::SliceConfiguration` | uint64 | enabled-port count | `EnableIci` | `LinksDownReset`(→ 0) |
| `0x118` | `ici::SliceConfiguration` | uint64 | enabled-port capacity | `EnableIci` | (immutable) |
每端口 `DataLinkLayerState` array(由 `CollectDataLinkState` 重新读取)归每端口 `IciPortUser` 所有(见下),而不是本 build 中 `SliceConfiguration` 的 inline field。
---
## 4. Link-state 模型
### `LinkStackReadyState` — 7 值 enum 和 FW remap
固件报告每端口 `port_ready_state ∈ [0,7]`(3-bit field)。主机通过 `IciLinkInfo::FirmwareStateToLinkStackReadyState` @`0xe7b6400` 把它翻译为软件 `LinkStackReadyState` enum,返回 `StatusOr<LinkStackReadyState>`。
该映射是 **identity**,不是 permutation。函数体是普通的 8-arm `switch`:对每个 `k ∈ [0,7]`,`case k:` 都把 `k` 存入 `StatusOr` value slot(offset +8),并设置 OK status(`*(_QWORD*)a1 = 1`)。`default` arm(`port_ready_state ≥ 8`,对 3-bit field 来说结构上不可达)构造错误:在 `platforms/asic_sw/lib/deepsea/jxc/common/ici_link_info.cc:54` 通过 `MakeErrorImpl<3>` 产生 `"Unknown ready_state %d"`。重新实现者应将 firmware code → enum value 做 1:1 映射,并拒绝 ≥8 的 code。
Enum descriptor 是 `LinkStackReadyState_descriptor` @`0xe7b6540`;其 arity(7)由 `WaitForLinksUp`(§2)中的 `NameOfDenseEnum<&…, 0, 7>` 调用确认。七个 value-name 字符串在运行时从 proto descriptor 发出,**不作为 `.rodata` literal 存在**(LOW — 名称需要 `link_stack.proto` FileDescriptorProto)。数值模型:
| FW `port_ready_state` | `LinkStackReadyState` value | Source |
|---|---|---|
| 0 | 0 | `0xe7b6400` case 0 |
| 1 | 1 | case 1 |
| 2 | 2 | case 2 |
| 3 | 3 | case 3 |
| 4 | 4 | case 4 |
| 5 | 5 | case 5 |
| 6 | 6 | case 6 |
| 7 | 7 → 若 proto arity 为 7 则 error | case 7;descriptor arity 7 |
| ≥8 | `"Unknown ready_state %d"` error | default arm |
> **QUIRK —** firmware register 是 8 值(0..7),但 proto enum 被 `NameOfDenseEnum<…,0,7>` 描述为 7 值。Identity remap 会原样传递全部八个 firmware code;如果 descriptor 确实只携带七个 dense name(index 0..6),firmware code 7 就没有名称,`NameOfDenseEnum` 会走其 `NameOfDenseEnumSlow` path(在反编译第 950 行验证),而不是索引 cached name table。这个不一致对 poll 是良性的,因为 `WaitForLinksUp` 比较的是 numeric ready value,不是名称;但渲染诊断信息的重新实现者必须处理未命名的第八个 code。
### 每端口 `DataLinkLayerState`
不同于 firmware ready-state,主机维护每端口 `DataLinkLayerState`,在 enable 时设置它,并读回它来确认 DL-up。它由 `IciPortUser` 拥有:
| Symbol | VA | Purpose |
|---|---|---|
| `IciPortUser::SetDataLinkLayerState(bool)` | `0x1fe8a2e0` | 主机为某端口开启/关闭 DL |
| `IciPortUser::GetDataLinkLayerState() const` | `0x1fe8a3c0` | 读取每端口 DL state |
如果在 driver init 前调用,两者都会抛出 `"… Must call Initialize() first"`。`CollectDataLinkState`([failure-recovery](failure-recovery.md))会把每个端口重新读入 `0xf0` array;`kDown` 值(enum 中 slot 4)是 reset target。该路径上看到的诊断包括:`"Bringing DL up on ICI link <n>"`、`"Failed to get data link layer state on link <n>"`、`"Unrecognized data link layer state: <v>"`、`"Failed to bring up data link on chip <loc>"`。
---
## 5. 注意事项
- **顺序是正确性,不是优化。** 如果重新实现者在安装 routing(步骤 6)之前启用 data link(步骤 10),flit 会在没有驻留 routing table 的情况下移动;如果在 DL-up(步骤 11)之前推送坐标(步骤 14),会发布由不完整 discovery 构建的 coordinate map。四条关键 ordering edge 是 discovery → chip-ids、routing → DL-enable、DL-up → coordinates、GTC-config → GTC-reset。
- **轮询预算是和,不是单个 timeout。** `WaitForLinksUp` 收到的是 `configure_ici_timeout + wait_for_data_link_up_timeout`,因此包含一个慢芯片的异构 pod 需要 per-chip `ChipDataLinkUpTimeout` override;只提高全局 `wait_for_data_link_up_timeout` 会统一延长每个芯片的 deadline。
- **PHY 是不透明的。** SerDes equalization tap、baud 或 lane width 没有主机可见 knob;这些都在固件内部。主机的杠杆正好是三个 flag(`enable_ici_serdes_training`、`ignore_external_ici_ports`、`disabled_serdes_index`)加上 poll deadline。重新实现者必须单独拥有固件侧才能复现 PHY training;本页只覆盖主机/驱动一半。
- **单芯片 slice 跳过 GTC tree。** 当 slice 只拥有一个芯片时,步骤 7–8/12–13 退化:`jxc::SliceConfiguration::EnableSingleChipGtc` @`0xe799a00`(`slice_configuration.cc:252`;`ici::SliceConfiguration::EnableSingleChipGtc` @`0x1fdb38e0` 是现代 twin)/ `IciControl::SetupSingleGtc` @`0xe7b49c0` 安装 self-leader GTC,而不是 peer tree;如果没有 enabled port,触发 `"Failed to find any enabled ICI port as the single-chip GTC leader"`。
---
## 验证说明
> 已与 `libtpu.so` v0.0.40 的 IDA 反编译结果交叉核对:
>
> - `Master::InitSlice` @`0x1fbbaac0`(578 行):正好 **11** 个 `ExecuteOnAllWorkers` site;具名 callable 顺序为 `GetLocalTopology, SetGlobalChipId, SetRoutingTable, SetGtcConfiguration, ControlIciErrorReport, EnableIciDataLink`;`DiscoverTopology`(local)位于 site 1 和 2 之间;`DetectRoutingTableDeadlock` 在 `SetRoutingTable` 前 gated。16 步序列一致。
> - `IciControl::WaitForLinksUp` @`0xe7b1060`:`absl::Now()` deadline base,单个 comparand `cmp $0x3D0901` @`0xe7b1198`(第 299 行)送入固定 `mov $0x3D0900,%eax` @`0xe7b11c2` 1 ms quantum(没有第二个 500 ms tier — `0x77359400` **不**出现在该函数 `0xe7b1060–0xe7b1900` 范围内),每 link 的 `IsLinkUp`(第 316 行)∧ `GetLinkStackReadyState`(第 325 行),`AbslInternalSleepFor`(第 896 行),以及 `NameOfDenseEnum<&LinkStackReadyState_descriptor, 0, 7>`(第 947–950 行,带 `NameOfDenseEnumSlow` fallback)— 精确;**7 值** arity 由 template argument 证明。
> - `EnableIciDataLink` @`0x1fbc0ee0`:构建 repeated `EnableIciDataLinkRequest_IciDataLinkConfiguration`(第 133 行的 `RepeatedPtrFieldBase::Add<…>`)— 确认 per-link config fan-out。
> - `FirmwareStateToLinkStackReadyState` @`0xe7b6400`:一个 8-arm identity `switch`,返回 `StatusOr<LinkStackReadyState>`;`default` → `ici_link_info.cc:54` 的 `"Unknown ready_state %d"`。
>
> **[LOW]** `configure_ici_timeout` / `wait_for_data_link_up_timeout` 的编译内置默认值(来自 `slice_builder::Options`);七个 `LinkStackReadyState` value-name 字符串(运行时通过 proto descriptor 渲染,不在 `.rodata` 中);`EnableIciDataLinkRequest_IciDataLinkConfiguration` 的字段 layout。最后四个 `ExecuteOnAllWorkers` callable(步骤 11–16)的身份从 `Worker::` RPC handler 重建(HIGH),因为反编译通过未解析的 vtable-relative member pointer 绑定它们。
---
## 相关组件
| Component | Relationship |
|---|---|
| [Topology Discovery](topology-discovery.md) | 步骤 2 的 payload — 把步骤 1 的 `LocalTopology` sets 折叠成全局 toroidal topology |
| [Failure Recovery](failure-recovery.md) | 每个步骤的 fail path(`FailSlice`、`LinksDownReset`)以及 `0xe8`/`0x110` reset |
| [Routing](../routing/overview.md) | 拥有步骤 4(generate)和 6(install)— DL-enable 前驻留的 per-link ICR tables |
| [DMA Descriptor](dma-descriptor.md) | 在本页 bring up 的 links 上承载传输的 transfer unit |
## 交叉引用
- [ICI Overview](overview.md) — 章节地图:two-level control plane、bring-up → discovery → transfer 主干、link/resource model
- [Topology Discovery](topology-discovery.md) — square-seed polarity、BFS coordinates、`LocalTopology` wire format
- [Failure Recovery](failure-recovery.md) — `SliceFailureType`、`FailDevice` cascade、`LinksDownReset`、bring-up 期间 error masking
- [DMA Descriptor](dma-descriptor.md) — 每 family descriptor word layout、remote sync-flag encoding
- [Routing](../routing/overview.md) — `(src,dst) → link path`、route-table generation and install
- [Megascale](../megascale/overview.md) — 消费 per-slice bring-up result 的 cross-slice topology stitching
- [返回索引](../index.md)