# TDA4VH U-Boot 早期异常 — CR 122077956 提交改动说明

> 本文档依据 iCode CR **122077956** 的真实代码 diff 整理（非反汇编还原）。
> 与仓库中 `uboot_early_abort_patch.md`（反汇编还原版）相比，本文以源码为准，并修正了其中几处偏差（见 §6）。

---

## 1. CR 元信息

| 项 | 值 |
|---|---|
| CR 编号 | 122077956 |
| 标题 | `ADS7-RDC-62: add uboot log for abort` |
| 仓库 | `baidu/adu-lab/tda4vh-sdk11` |
| 目标分支 | `master` |
| Change-Id | `Ia1d86623ff5995030869d50b92ae8e4060847757` |
| 提交 commit | `990291a702f27521f339467a36f3d1ee8b6e1748` |
| 父提交 | `046615f2914d98610abba60f90063bdc7b70869a` |
| 当前版本 | revision 2（patchset 2） |
| 作者 | anjianjun01 |
| 评审人 | shichangshuai、dairui01 |
| 状态 | NEW（CR_NEED，待评审） |
| 创建时间 | 2026-08-13 13:06:04 |
| 更新时间 | 2026-08-13 13:31:33 |
| 流水线 | ChangePipeline：**SUCCESS**（buildId 265701391） |

> 背景：本提交服务于 `uboot_early_abort_diagnostic_plan.md` / `uboot_early_abort_dump_analysis.md` 中描述的 **U-Boot 早期同步异常（pre-relocation synchronous abort）定位**。目的是在 BSS 清零、重定位（relocation）、gd/x18 建立之前，也能把"首次异常的干净现场"落盘到 `.data` 段，供调试器按符号地址直接读取。

---

## 2. 改动概览

共 **8 个文件**，`334 insertions(+), 1 deletion(-)`：

| 文件（相对 `board-support/ti-u-boot-2025.01+git/`） | 类型 | 增/删 |
|---|---|---|
| `arch/arm/lib/Makefile` | 修改 | +1 |
| `arch/arm/lib/early_crash.c` | **新增** | +91 |
| `arch/arm/lib/interrupts_64.c` | 修改 | +75 |
| `drivers/core/lists.c` | 修改 | +11 |
| `drivers/firmware/ti_sci.c` | 修改 | +19 |
| `include/early_crash.h` | **新增** | +86 |
| `include/efi_loader.h` | 修改 | +4 |
| `lib/efi_loader/efi_boottime.c` | 修改 | +47 |

核心思路：新增一个**固定布局、放在 `.data` 段的 crash record**，在异常处理路径最前端（`do_sync`）先于任何 `gd/x18` 操作写入干净现场；同时给 `efi_gd` 增加一个 `.data` 段 cookie，避免 pre-relocation 阶段 BSS 未清零时误恢复垃圾 gd；再在 DM / TI-SCI 绑定路径埋入**纯整数 trace ring**，还原异常前走到哪一步。

---

## 3. 逐文件改动

### 3.1 `arch/arm/lib/Makefile`（+1）

```diff
 obj-$(CONFIG_DRIVER_GICV2)	+= gic-v2.o
 obj-$(CONFIG_GIC_V3_ITS)	+= gic-v3-its.o
 obj-y	+= interrupts_64.o
+obj-y	+= early_crash.o
 else
 obj-y	+= interrupts.o
 endif
```

把 `early_crash.o` 挂在 ARM64 条件块内（与 `interrupts_64.o` 同属 `CONFIG_ARM64` 分支），非 ARM64（如 R5 / ARMv7）不编译该实现，靠 `early_crash.h` 里的 no-op 内联 `early_trace()` 保证公共调用点能链接。

### 3.2 `include/early_crash.h`（新增，+86）

定义崩溃记录 / trace ring 的布局、常量与接口：

- 常量（均为可打印 ASCII 魔数，便于在内存里肉眼识别）：
  - `EARLY_CRASH_MAGIC = 0x5542435241534831`（"UBCRASH1"）
  - `EARLY_CRASH_STATE_READY/WRITING/VALID = "READ"/"WRIT"/"VALD"`
  - `EARLY_TRACE_COUNT = 16`
- `struct early_crash_record`：magic / version / state / exception_count / trace_index，随后是异常现场字段（current_el、spsr、elr、esr、far、pre_exception_sp、x18、x29、x30、pt_regs、expected_gd、efi_gd_raw），最后是 `trace[16]`。
- `enum early_trace_tag`：
  - TI-SCI：`0x1001` PROBE_ENTER、`0x1002` OF_TO_INFO_ENTER、`0x1003` OF_TO_INFO_OK、`0x1004` BIND_BEFORE、`0x1005` BIND_AFTER
  - DM：`0x2001` BIND_ENTER、`0x2002` DRIVER_FOUND、`0x2003` BIND_RETURN
- 接口：`early_crash_capture()` / `early_crash_halt()`（`__attribute__((noreturn))`）/ `early_trace()` / `early_crash_get()`，仅在 `CONFIG_ARM64` 下声明。
- **非 ARM64 分支**：提供 `static inline void early_trace(...)` 空实现，让 `drivers/core/lists.c`、`drivers/firmware/ti_sci.c` 这类公共调用点在 ARMv7（R5）构建下也能链接。

### 3.3 `arch/arm/lib/early_crash.c`（新增，+91）

```c
static volatile struct early_crash_record early_crash_record = {
	.magic = EARLY_CRASH_MAGIC,
	.version = EARLY_CRASH_VERSION,
	.state = EARLY_CRASH_STATE_READY,
};
```

**关键点：带非零初值 → 链接器把对象放进 `.data` 而非 `.bss`**，因此镜像一加载就有确定值，不依赖 BSS 清零、不依赖重定位，调试器可按符号地址读取。

- `early_crash_capture(regs, far, current_el, expected_gd, efi_gd_raw)`：
  - **one-shot**：仅当 `state == READY` 才写全字段并置 `VALID`、`exception_count = 1`；再次进入（递归异常）只 `exception_count++` 并返回 `false`，不覆盖首发现场。
  - 只做标量存储（不用 printf / memcpy / gd / malloc / console），末尾 `dsb sy` 保证调试器看到完整有序的记录。
- `early_crash_halt()`：`msr daifset, #0xf` 屏蔽所有异常 → `dsb sy; isb` → 死循环 `wfe`，用于递归异常时直接停机。
- `early_trace(tag, arg0, arg1, arg2)`：顺序写 ring，写满 `EARLY_TRACE_COUNT` 即停止（保留最早事件），每写入一帧后 `dmb sy` 再更新 `trace_index`。

### 3.4 `arch/arm/lib/interrupts_64.c`（+75）

1. 新增 `read_far(esr, current_el, &far)`：按 ESR EC 字段（`(esr >> 26) & 0b111111`）判断是否 abort/watchpoint 类（`0x20/0x21/0x22/0x24/0x25/0x34/0x35`），再按 `CurrentEL` 选读 `FAR_EL1` / `FAR_EL2`；非此类返回 `false`，**避免对 EC=0x00 这类"无 FAR 含义"的异常误读 FAR**。
2. 新增 `early_expected_gd()`：镜像 `board_init_f_alloc_reserve()` 的保留顺序，`SYS_INIT_SP_ADDR - SYS_MALLOC_F_LEN - sizeof(struct global_data)` 后 16 字节对齐，作为捕获 x18 时交叉校验的 hint（**仅供记录，绝不解引用**）。
3. `do_sync()` 顺序重排（本提交最关键改动）：

```c
void do_sync(struct pt_regs *pt_regs)
{
	unsigned long current_el, far;
	bool far_valid, first;

	if (CONFIG_IS_ENABLED(SEMIHOSTING_FALLBACK) && smh_emulate_trap(pt_regs))
		return;

	asm volatile("mrs %0, CurrentEL" : "=r" (current_el));
	far_valid = read_far(pt_regs->esr, current_el, &far);

	first = early_crash_capture(pt_regs, far_valid ? far : 0, current_el,
				    early_expected_gd(),
				    efi_get_saved_gd_raw());
	if (!first)
		early_crash_halt();

	efi_restore_gd();
	printf("\"Synchronous Abort\" handler, esr 0x%08lx", pt_regs->esr);
	...
}
```

   - **先捕获、再恢复 gd**：原实现第一步就是 `efi_restore_gd()`，若异常正是由 gd/x18 损坏引起，恢复会污染现场；现在先落盘、后恢复。
   - 捕获路径不碰 x18/gd，因此 gd 损坏场景下依然安全；递归进入则直接 halt，阻断递归风暴。

### 3.5 `drivers/core/lists.c`（+11）

> ⚠️ 注意：`device_bind_driver_to_node()` 位于 **`drivers/core/lists.c`**（并非反汇编还原文档里写的 `drivers/core/device.c`）。

在 `device_bind_driver_to_node()` 三个位置埋 trace：

```c
early_trace(TRACE_DM_BIND_ENTER,     (u64)parent, (u64)drv_name, (u64)dev_name);
drv = lists_driver_lookup_name(drv_name);
if (!drv) { ... return -ENOENT; }
early_trace(TRACE_DM_DRIVER_FOUND,   (u64)parent, (u64)drv, (u64)devp);
ret = device_bind_with_driver_data(parent, drv, dev_name, 0, node, devp);
early_trace(TRACE_DM_BIND_RETURN,    (u64)parent, (u64)(s64)ret, devp ? (u64)*devp : 0);
```

`BIND_RETURN` 对 `devp` 判空，避免为打点引入 NULL 解引用。

### 3.6 `drivers/firmware/ti_sci.c`（+19）

在 `ti_sci_probe()` 内埋 4 个 trace 点（`0x1004 BIND_BEFORE` 定义但本次未使用）：

```c
early_trace(TRACE_TI_SCI_PROBE_ENTER,    (u64)dev, (u64)dev->driver, (u64)dev->parent);
info = dev_get_priv(dev);
early_trace(TRACE_TI_SCI_OF_TO_INFO_ENTER, (u64)dev, (u64)info, (u64)(s64)dev_of_offset(dev));
ret = ti_sci_of_to_info(dev, info);
if (ret) { early_trace(TRACE_TI_SCI_BIND_AFTER, (u64)dev, (u64)(s64)ret, 0); return ret; }
early_trace(TRACE_TI_SCI_OF_TO_INFO_OK,  (u64)dev, (u64)info, (u64)info->desc);
...
ret = device_bind_driver(dev, "ti-sci-sysreset", "sysreset", NULL);
early_trace(TRACE_TI_SCI_BIND_AFTER,     (u64)dev, (u64)(s64)ret, 0);
```

### 3.7 `include/efi_loader.h`（+4）

声明 `efi_get_saved_gd_raw()`；非 EFI_LOADER 分支给 `static inline` 空实现返回 0。

### 3.8 `lib/efi_loader/efi_boottime.c`（+47）

给 `efi_gd` 增加 `.data` 段 cookie 状态，并新增只读 getter：

```c
#define EFI_GD_STATE_UNSAVED	0x45464930U	/* "EFI0" */
#define EFI_GD_STATE_SAVED	0x45464947U	/* "EFIG" */

static volatile gd_t *efi_gd, *app_gd;
static u32 efi_gd_state = EFI_GD_STATE_UNSAVED;   /* 非零初值 → .data */
```

- `efi_save_gd()`：先 `efi_gd = gd;` → `dmb sy` → 最后置 `EFI_GD_STATE_SAVED`（**写序靠后标记有效，保证消费者看到 SAVED 时 gd 已可见**）。
- `efi_restore_gd()`：改为 **先判 `efi_gd_state == SAVED`**，不满足直接返回，再判 `efi_gd` 非空才 `set_gd()`。
- `efi_get_saved_gd_raw()`：只读返回 `efi_gd` 原始值（不写 x18），供 crash record 记录 `efi_gd_raw` 用于交叉校验。

**为什么必须加 cookie**：`efi_gd` 本身在 `.bss`，pre-relocation 阶段 BSS 未清零时可能是任意垃圾值，旧的 `if (!efi_gd) return;` 无法区分"垃圾非零值"与"真实 gd"。`efi_gd_state` 放在 `.data`、镜像加载即有确定值，且读取走 PC 相对寻址不依赖 x18，因此"异常本身由 gd/x18 损坏引起"时也能安全判断。

---

## 4. 设计要点小结

| 问题 | 方案 |
|---|---|
| pre-relocation 阶段 BSS 未清零、无 gd/console | crash record 带非零初值 → 强制进 `.data` |
| 捕获路径不能依赖 gd/x18 | 只做标量存储 + `dsb sy`，不调 printf/memcpy |
| 递归异常会覆盖首发现场 | one-shot：第二次只计数并 `early_crash_halt()` |
| gd 损坏时 `efi_restore_gd()` 会二次破坏 | 先 `early_crash_capture()` 落盘，再 `efi_restore_gd()` |
| `efi_gd` 是 BSS 垃圾值误恢复 | `.data` cookie `efi_gd_state` 挡住 |
| 异常前走了哪条 bind/probe 路径 | DM / TI-SCI 纯整数 trace ring |
| 非 ARM64（R5/ARMv7）构建链接 | `early_crash.h` 提供 no-op 内联 `early_trace()` |
| 异常发生时的 Exception Level / 期望 gd | 记录 `current_el` 与 `expected_gd` 作交叉校验 |

---

## 5. trace tag 速查

| tag | 常量 | 位置 |
|---|---|---|
| `0x1001` | TRACE_TI_SCI_PROBE_ENTER | ti_sci_probe 入口 |
| `0x1002` | TRACE_TI_SCI_OF_TO_INFO_ENTER | ti_sci_of_to_info 前 |
| `0x1003` | TRACE_TI_SCI_OF_TO_INFO_OK | ti_sci_of_to_info 成功后 |
| `0x1004` | TRACE_TI_SCI_BIND_BEFORE | （定义，本次未使用） |
| `0x1005` | TRACE_TI_SCI_BIND_AFTER | ti-sci-sysreset bind 后 / 失败路径 |
| `0x2001` | TRACE_DM_BIND_ENTER | device_bind_driver_to_node 入口 |
| `0x2002` | TRACE_DM_DRIVER_FOUND | driver lookup 成功后 |
| `0x2003` | TRACE_DM_BIND_RETURN | device_bind_with_driver_data 返回后 |

---

## 6. 与 `uboot_early_abort_patch.md`（反汇编还原版）的差异修正

反汇编还原版在"源码树不在本地"前提下整理，本提交源码确认后有几处需更正：

1. **文件位置**：`device_bind_driver_to_node()` 在 `drivers/core/lists.c`，不是 `drivers/core/device.c`。
2. **expected_gd 计算**：源码用 `SYS_INIT_SP_ADDR`（并经 `CONFIG_IS_ENABLED(SYS_MALLOC_F)` 判断是否减 `CONFIG_VAL(SYS_MALLOC_F_LEN)`），不是还原文档里的 `CONFIG_CUSTOM_SYS_INIT_SP_ADDR`。
3. **read_far 实现**：源码用 `switch` 按 EC 分类（`0x20/0x21/0x22/0x24/0x25/0x34/0x35`），再按 `current_el >> 2` 选 FAR_EL1/FAR_EL2；不是还原文档里的 64 位 bitmask 写法。
4. **early_crash.h 非 ARM64 no-op**：源码在头文件里对非 `CONFIG_ARM64` 提供 `static inline early_trace()` 空实现（还原文档未体现）。
5. 其余（魔数值、结构体布局、one-shot、`-ffixed-x18`、`.data`/`.bss` 段要求）与还原文档一致。

---

## 7. 关键地址速查（patched build，沿用 dump 分析结论）

| 项 | 地址 | 段 |
|---|---|---|
| `early_crash_record` | 0x80908C38 | .data |
| `efi_gd_state` | 0x8090F874 | .data |
| `efi_gd` | 0x80951B58 | .bss |
| gd（运行时） | 0x80477EA0 | DDR |
| 首发现场 ELR | 0x808B8440 | text |

> 详细现场解析见 `uboot_early_abort_dump_analysis.md`；诊断计划见 `uboot_early_abort_diagnostic_plan.md`。
