# TDA4VH U-Boot 早期异常 — Patch 详细说明

> 本文件是 `uboot_early_abort_diagnostic_plan.md` 的落地实现记录。
> 代码由 **patched build 的反汇编逐条还原**（源码树不在本地），函数拆分、变量命名、行号可能与实际源码略有出入，但**逻辑、常量、结构体字段偏移与反汇编逐一对应**，可作为维护/移植/审查的参考。
>
> 验证对象：`tmp/error_tiboot3/build/a72/u-boot`（ELF）+ `System.map`。

---

## 1. 改动文件清单

| 文件 | 类型 | 内容 |
|---|---|---|
| `lib/efi_loader/efi_boottime.c` | 修改 | efi_gd 增加 `.data` cookie 状态 + 只读 getter |
| `include/efi_loader.h` | 修改 | 声明 `efi_get_saved_gd_raw()` |
| `include/early_crash.h` | **新增** | crash record 结构、trace 结构、常量、接口声明 |
| `arch/arm/lib/early_crash.c` | **新增** | crash record 定义 + capture/halt/trace 实现 |
| `arch/arm/lib/Makefile` | 修改 | 增加 `early_crash.o` |
| `arch/arm/lib/interrupts_64.c` | 修改 | `do_sync()` 先捕获首发、再恢复 gd、加 one-shot 停机 |
| `drivers/core/device.c` | 修改 | `device_bind_driver_to_node()` 加 3 个 DM trace |
| `drivers/firmware/ti_sci.c` | 修改 | `ti_sci_probe()` 加 4 个 TI-SCI trace |

---

## 2. 关键常量

| 常量 | 值 | 含义 |
|---|---|---|
| `EFI_GD_STATE_UNSAVED` | `0x45464930` | "EFI0"，efi_gd 未保存 |
| `EFI_GD_STATE_SAVED` | `0x45464947` | "EFIG"，efi_gd 已保存 |
| `EARLY_CRASH_MAGIC` | `0x5542435241534831` | "UBCRASH1" |
| `EARLY_CRASH_STATE_READY` | `0x52454144` | "READ" |
| `EARLY_CRASH_STATE_WRITING` | `0x57524954` | "WRIT" |
| `EARLY_CRASH_STATE_VALID` | `0x56414C44` | "VALD" |
| `EARLY_TRACE_COUNT` | 16 | trace ring 容量 |
| `TRACE_TI_SCI_PROBE_ENTER` | `0x1001` | |
| `TRACE_TI_SCI_OF_TO_INFO_ENTER` | `0x1002` | |
| `TRACE_TI_SCI_OF_TO_INFO_OK` | `0x1003` | |
| `TRACE_TI_SCI_BIND_BEFORE` | `0x1004` | 定义但本次实现未使用 |
| `TRACE_TI_SCI_BIND_AFTER` | `0x1005` | |
| `TRACE_DM_BIND_ENTER` | `0x2001` | |
| `TRACE_DM_DRIVER_FOUND` | `0x2002` | |
| `TRACE_DM_BIND_RETURN` | `0x2003` | |

`expected_gd`（编译期常量）= `CONFIG_CUSTOM_SYS_INIT_SP_ADDR - CONFIG_SYS_MALLOC_F_LEN - sizeof(gd_t)` = `0x80480000 - 0x8000 - 0x160` = **`0x80477EA0`**。

---

## 3. 各文件代码

### 3.1 `lib/efi_loader/efi_boottime.c`

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

static gd_t *efi_gd;                              /* 仍留在 BSS，允许 */
static u32 efi_gd_state = EFI_GD_STATE_UNSAVED;   /* 非零初值 → 进 .data */

void efi_save_gd(void)
{
#if defined(CONFIG_ARM) || defined(CONFIG_RISCV)
    efi_gd = gd;
    asm volatile("dmb sy" : : : "memory");
    efi_gd_state = EFI_GD_STATE_SAVED;   /* 最后标记有效 */
#endif
}

void efi_restore_gd(void)
{
#if defined(CONFIG_ARM) || defined(CONFIG_RISCV)
    if (efi_gd_state != EFI_GD_STATE_SAVED)
        return;                          /* cookie 无效 → 不写 x18 */
    if (!efi_gd)
        return;
    set_gd(efi_gd);
#endif
}

uintptr_t efi_get_saved_gd_raw(void)
{
#if defined(CONFIG_ARM) || defined(CONFIG_RISCV)
    return (uintptr_t)efi_gd;
#else
    return 0;
#endif
}
```

关键点：pre-relocation 时 `efi_gd_state` 在 `.data` 里确定值为 `UNSAVED`，而 `efi_gd` 在 BSS 未清零可以是任意垃圾——原来的 `if (!efi_gd)` 判断挡不住非零垃圾，cookie 判断能挡住。

### 3.2 `include/efi_loader.h`

```c
uintptr_t efi_get_saved_gd_raw(void);
```

### 3.3 `include/early_crash.h`（新增）

```c
#ifndef __EARLY_CRASH_H
#define __EARLY_CRASH_H

#include <linux/types.h>
#include <asm/ptrace.h>

#define EARLY_CRASH_MAGIC         0x5542435241534831ULL /* UBCRASH1 */
#define EARLY_CRASH_VERSION       1U
#define EARLY_CRASH_STATE_READY   0x52454144U /* READ */
#define EARLY_CRASH_STATE_WRITING 0x57524954U /* WRIT */
#define EARLY_CRASH_STATE_VALID   0x56414C44U /* VALD */

#define EARLY_TRACE_COUNT 16

struct early_trace_entry {
    u32 tag;        /* +0x00 */
    u32 reserved;   /* +0x04 */
    u64 arg0;       /* +0x08 */
    u64 arg1;       /* +0x10 */
    u64 arg2;       /* +0x18 */
};                  /* 0x20 */

struct early_crash_record {
    u64 magic;                 /* +0x00 */
    u32 version;               /* +0x08 */
    u32 state;                 /* +0x0C */
    u32 exception_count;       /* +0x10 */
    u32 trace_index;           /* +0x14 */
    u64 current_el;            /* +0x18 */
    u64 spsr;                  /* +0x20 */
    u64 elr;                   /* +0x28 */
    u64 esr;                   /* +0x30 */
    u64 far;                   /* +0x38 */
    u64 pre_exception_sp;      /* +0x40 */
    u64 x18;                   /* +0x48 */
    u64 x29;                   /* +0x50 */
    u64 x30;                   /* +0x58 */
    u64 pt_regs;               /* +0x60 */
    u64 expected_gd;           /* +0x68 */
    u64 efi_gd_raw;            /* +0x70 */
    struct early_trace_entry trace[EARLY_TRACE_COUNT]; /* +0x78 */
};

bool early_crash_capture(struct pt_regs *regs, u64 far, u64 current_el,
                         u64 expected_gd, u64 efi_gd_raw);
void early_crash_halt(void);
void early_trace(u32 tag, u64 arg0, u64 arg1, u64 arg2);

enum early_trace_tag {
    TRACE_TI_SCI_PROBE_ENTER       = 0x1001,
    TRACE_TI_SCI_OF_TO_INFO_ENTER  = 0x1002,
    TRACE_TI_SCI_OF_TO_INFO_OK     = 0x1003,
    TRACE_TI_SCI_BIND_BEFORE       = 0x1004,
    TRACE_TI_SCI_BIND_AFTER        = 0x1005,
    TRACE_DM_BIND_ENTER            = 0x2001,
    TRACE_DM_DRIVER_FOUND          = 0x2002,
    TRACE_DM_BIND_RETURN           = 0x2003,
};

#endif
```

### 3.4 `arch/arm/lib/early_crash.c`（新增）

```c
#include <common.h>
#include <linux/types.h>
#include <asm/ptrace.h>
#include <efi_loader.h>
#include <early_crash.h>

/* 有非零初值 → 进 .data，不依赖 BSS 清零 */
static volatile struct early_crash_record early_crash_record = {
    .magic = EARLY_CRASH_MAGIC,
    .version = EARLY_CRASH_VERSION,
    .state = EARLY_CRASH_STATE_READY,
};

bool early_crash_capture(struct pt_regs *regs, u64 far, u64 current_el,
                         u64 expected_gd, u64 efi_gd_raw)
{
    volatile struct early_crash_record *rec = &early_crash_record;

    if (rec->state != EARLY_CRASH_STATE_READY) {
        /* 第二次及以后：只计数，不覆盖首发，返回 false */
        rec->exception_count++;
        asm volatile("dsb sy" : : : "memory");
        return false;
    }

    rec->state = EARLY_CRASH_STATE_WRITING;
    rec->exception_count = 1;
    rec->current_el = current_el;
    rec->spsr = regs->spsr;
    rec->elr = regs->elr;
    rec->esr = regs->esr;
    rec->far = far;
    rec->pre_exception_sp = (u64)regs + sizeof(*regs); /* = regs + 0x110 */
    rec->x18 = regs->regs[18];
    rec->x29 = regs->regs[29];
    rec->x30 = regs->regs[30];
    rec->pt_regs = (u64)regs;
    rec->expected_gd = expected_gd;
    rec->efi_gd_raw = efi_gd_raw;
    rec->state = EARLY_CRASH_STATE_VALID;

    asm volatile("dsb sy" : : : "memory");
    return true;
}

void early_crash_halt(void)
{
    asm volatile("msr daifset, #0xf" : : : "memory"); /* 屏蔽所有异常 */
    asm volatile("dsb sy\nisb" : : : "memory");

    for (;;)
        asm volatile("wfe");
}

void early_trace(u32 tag, u64 arg0, u64 arg1, u64 arg2)
{
    volatile struct early_crash_record *rec = &early_crash_record;
    u32 idx = rec->trace_index;

    if (idx >= EARLY_TRACE_COUNT)  /* 满后停止，不循环覆盖 */
        return;

    rec->trace[idx].tag = tag;
    rec->trace[idx].reserved = 0;
    rec->trace[idx].arg0 = arg0;
    rec->trace[idx].arg1 = arg1;
    rec->trace[idx].arg2 = arg2;
    asm volatile("dmb sy" : : : "memory");
    rec->trace_index = idx + 1;
}
```

### 3.5 `arch/arm/lib/Makefile`

```make
obj-y += early_crash.o
```

（放在 `interrupts_64.o` 附近的 ARM64 条件块内。）

### 3.6 `arch/arm/lib/interrupts_64.c` — `do_sync()`

```c
/* 仅当 ESR 的 EC 属于 abort/watchpoint 类（0x20,0x21,0x22,0x24,0x25,0x34,0x35）才读 FAR */
static unsigned long read_far_by_el(unsigned long esr, unsigned long current_el)
{
    unsigned long far = 0;

    if (((0x30003700000000UL) >> ((esr >> 26) & 0x3f)) & 1) {
        if ((current_el >> 2) == 1)
            asm volatile("mrs %0, FAR_EL1" : "=r"(far));
        else if ((current_el >> 2) == 2)
            asm volatile("mrs %0, FAR_EL2" : "=r"(far));
    }
    return far;
}

void do_sync(struct pt_regs *regs)
{
    unsigned long current_el;
    unsigned long far;
    bool first;

    asm volatile("mrs %0, CurrentEL" : "=r"(current_el));
    far = read_far_by_el(regs->esr, current_el);

    /* 先捕获首发，再恢复 gd / 打印 —— 顺序关键 */
    first = early_crash_capture(regs, far, current_el,
                                CONFIG_CUSTOM_SYS_INIT_SP_ADDR -
                                CONFIG_SYS_MALLOC_F_LEN - sizeof(gd_t),
                                efi_get_saved_gd_raw());
    if (!first)
        early_crash_halt();          /* 第二次异常：直接停机，不递归 */

    efi_restore_gd();

    printf("Synchronous Abort ...", regs->esr);
    dump_far(regs);
    show_regs(regs);
    show_efi_loaded_images(regs);
    panic("...");
}
```

变更要点：
1. 原 `do_sync()` 第一步就是 `efi_restore_gd()`，现在**先 `early_crash_capture()` 保存干净现场，再 `efi_restore_gd()`**。
2. `early_crash_capture()` 返回 false（第二次进入）时，`early_crash_halt()` 直接 `wfe` 停机，阻断递归。

### 3.7 `drivers/core/device.c` — `device_bind_driver_to_node()`

```c
static int device_bind_driver_to_node(struct udevice *parent,
                                      const char *drv_name,
                                      const char *dev_name,
                                      ofnode node, struct udevice **devp)
{
    ...
    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);
    return ret;
}
```

注意：`devp` 可能为 NULL，`DRIVER_FOUND`/`BIND_RETURN` 的 trace 代码必须先判空，不能为打日志引入 NULL 解引用。

### 3.8 `drivers/firmware/ti_sci.c` — `ti_sci_probe()`

本次实现共 4 个 trace 点（`0x1004 BIND_BEFORE` 未使用）：

```c
static int ti_sci_probe(struct udevice *dev)
{
    struct ti_sci_info *info;
    int ret;

    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)dev_ofnode(dev).of_offset);

    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);
    ...
}
```

> 注：`ti_sci_probe()` 内 4 个 trace 的具体插入位置以实际源码为准（反汇编只能确认 tag 值 0x1001/0x1002/0x1003/0x1005 及各自三个实参的含义，无法精确还原源码行位置）。

---

## 4. 符号地址验证（patched build）

```bash
nm -n build/a72/u-boot | grep -E 'early_crash|efi_gd_state|efi_gd$|efi_save_gd|efi_restore_gd|efi_get_saved_gd_raw'
```

| 符号 | 地址 | 段 | 说明 |
|---|---|---|---|
| `early_crash_record` | 0x80908C38 | .data | 静态对象，`d`（局部） |
| `efi_gd_state` | 0x8090F874 | .data | cookie，`d`（局部） |
| `efi_gd` | 0x80951B58 | .bss | 仍留在 BSS，`b` |
| `efi_save_gd` | 0x8088B4C4 | text | |
| `efi_restore_gd` | 0x8088B4E4 | text | |
| `efi_get_saved_gd_raw` | 0x8088B520 | text | |
| `early_crash_capture` | 0x80802E8C | text | |
| `early_crash_halt` | 0x80802F38 | text | |
| `early_trace` | 0x80802F4C | text | |
| `do_sync` | 0x80802CC4 | text | |

段位检查（必须满足）：

```bash
objdump -t build/a72/u-boot | grep -E 'efi_gd_state|early_crash_record'
objdump -h build/a72/u-boot | grep -E '\.data|\.bss'
```

- `efi_gd_state` 必须在 `.data`（不能进 BSS）；
- `early_crash_record` 必须在 `.data`。

---

## 5. 编译期验证

```bash
# 固定寄存器参数（x18 不可被普通 C 代码用作临时寄存器）
grep -- '-ffixed-x18' build/a72/arch/arm/lib/.early_crash.o.cmd
grep -- '-ffixed-x18' build/a72/arch/arm/lib/.interrupts_64.o.cmd
```

两者都必须包含 `-ffixed-x18`。

可选：加静态断言保证结构体大小与异常帧大小一致

```c
_Static_assert(sizeof(struct pt_regs) == 0x110, "unexpected pt_regs size");
_Static_assert(sizeof(struct early_crash_record) == 0x278,
               "unexpected early_crash_record size");
```