|
| 1 | +# NuttX FPB Comp 自动分配问题整改方案 |
| 2 | + |
| 3 | +> 日期: 2026-04-07 |
| 4 | +> 状态: 待实施 |
| 5 | +
|
| 6 | +## 1. 问题描述 |
| 7 | + |
| 8 | +### 1.1 现象 |
| 9 | + |
| 10 | +在 NuttX 平台上使用 dpatch 模式注入时,手动指定 `--comp 1` 后: |
| 11 | +- `fl -c info` 显示 `Slot[0]` 的 COMP 寄存器有值(on),`Slot[1]` 的 COMP 为 0(off) |
| 12 | +- `fl -c enable --comp 1 --enable 1` 返回 `[FLERR] Failed to enable patch 1: -2` |
| 13 | +- 但 patch 代码内部的 `fpb_enable_patch(0, false/true)` 可以正常工作(因为恰好对上了 comp 0) |
| 14 | + |
| 15 | +### 1.2 根因 |
| 16 | + |
| 17 | +NuttX 的 `arm_breakpoint_add`(`arch/arm/src/armv8-m/arm_dbgmonitor.c`)不支持指定硬件 comparator 编号,而是从 comp 0 开始遍历,找到第一个空闲的自动分配: |
| 18 | + |
| 19 | +```c |
| 20 | +// NuttX arm_breakpoint_add 核心逻辑 |
| 21 | +for (i = 0; i < num; i++) { |
| 22 | + uint32_t comp = getreg32(FPB_COMP0 + i * 4); |
| 23 | + if (comp == fpb_comp) // 已设置,返回 |
| 24 | + return 0; |
| 25 | + else if (comp & ENABLE) // 被占用,跳过 |
| 26 | + continue; |
| 27 | + else // 空闲,使用这个 |
| 28 | + putreg32(fpb_comp, FPB_COMP0 + i * 4); |
| 29 | + return 0; |
| 30 | +} |
| 31 | +``` |
| 32 | + |
| 33 | +因此 `fpb_debugmon_nuttx.c` 中 `set_redirect(comp_id=1, ...)` 调用 `up_debugpoint_add` 后,NuttX 实际将断点写入了硬件 FPB_COMP[0](第一个空闲的)。`comp_id` 只是 `g_debugmon_state.redirects[]` 数组的索引,与硬件 comparator 编号不对应。 |
| 34 | + |
| 35 | +而 `fpb_enable_patch(1, true)` 直接操作 `FPB_COMP(1)` 寄存器,该寄存器为空,返回 `FPB_ERR_INVALID_PARAM`。 |
| 36 | + |
| 37 | +### 1.3 影响范围 |
| 38 | + |
| 39 | +| 场景 | 是否受影响 | 说明 | |
| 40 | +|------|:----------:|------| |
| 41 | +| dpatch --comp 0(且 comp 0 空闲) | ✅ 正常 | NuttX 自动分配到 comp 0,恰好对上 | |
| 42 | +| dpatch --comp 1(且 comp 0 空闲) | ❌ 异常 | NuttX 分配到 comp 0,但 enable 操作 comp 1 | |
| 43 | +| dpatch --comp 1(且 comp 0 已占用) | ✅ 正常 | NuttX 跳过 comp 0,分配到 comp 1 | |
| 44 | +| patch/tpatch(FPBv1 REMAP 模式) | ✅ 正常 | 直接写 FPB_COMP 寄存器,不走 NuttX API | |
| 45 | +| patch 代码内 fpb_enable_patch | ✅ 正常 | 操作的是实际有值的硬件 comp | |
| 46 | + |
| 47 | +核心矛盾:用户指定的 `comp_id` 是逻辑槽位号,NuttX 分配的是物理 comparator 号,两者不一定相等。 |
| 48 | + |
| 49 | +## 2. 整改方案 |
| 50 | + |
| 51 | +### 2.1 思路 |
| 52 | + |
| 53 | +不改下位机固件。在上位机侧保证逻辑槽位号与物理 comparator 号一致: |
| 54 | + |
| 55 | +1. **Slot 下拉菜单默认自动模式**:复用 `find_slot_for_target` 的分配逻辑(从 slot 0 开始找第一个空闲),与 NuttX `arm_breakpoint_add` 的分配算法一致 |
| 56 | +2. **Patch 模板中 comp id 参数化**:通过编译宏 `-DFPB_PATCH_COMP_ID=N` 传入,避免源码中硬编码 |
| 57 | + |
| 58 | +### 2.2 整改点一:Slot 下拉菜单增加自动模式 |
| 59 | + |
| 60 | +**文件**: `templates/partials/editor.html` |
| 61 | + |
| 62 | +在 `slotSelect` 下拉菜单最前面增加 "Auto" 选项,value 为 -1,设为默认选中: |
| 63 | + |
| 64 | +```html |
| 65 | +<select id="slotSelect" ...> |
| 66 | + <option value="-1" selected data-i18n="device.slot_auto">Auto</option> |
| 67 | + <option value="0" ...>Slot 0</option> |
| 68 | + ... |
| 69 | +</select> |
| 70 | +``` |
| 71 | + |
| 72 | +**文件**: `static/js/core/slots.js` |
| 73 | + |
| 74 | +- `onSlotSelectChange`:当选择 Auto(-1)时,`state.selectedSlot = -1` |
| 75 | +- `updateSlotUI`:Auto 模式下状态栏显示 "Slot: Auto" |
| 76 | + |
| 77 | +**文件**: `static/js/features/patch.js` — `performInject` |
| 78 | + |
| 79 | +当 `state.selectedSlot === -1` 时,传 `comp: -1` 给后端。后端 `inject()` 已有 `comp < 0` 时调用 `find_slot_for_target` 的逻辑,无需改动。 |
| 80 | + |
| 81 | +**关键点**:`find_slot_for_target` 的分配算法是"找第一个空闲 slot",与 NuttX `arm_breakpoint_add` 的"找第一个空闲 comp"一致,因此自动模式下逻辑槽位号 = 物理 comparator 号。 |
| 82 | + |
| 83 | +**重复注入行为**:`find_slot_for_target` 已实现 Smart Reuse 策略 — 如果同一个 `target_addr` 已在某个 slot 中,直接复用该 slot(先 unpatch 再重新注入),不会新开 slot。 |
| 84 | + |
| 85 | +### 2.3 整改点二:Patch 模板 comp id 参数化 |
| 86 | + |
| 87 | +**现状**(`static/js/features/patch.js` — `generatePatchTemplate`): |
| 88 | + |
| 89 | +```c |
| 90 | +/** |
| 91 | + * Patch for: fl_hello |
| 92 | + * Slot: 0 |
| 93 | + * Original: 0x08001234 |
| 94 | + */ |
| 95 | +... |
| 96 | +fpb_enable_patch(0, false); |
| 97 | +ORIG_FL_HELLO(); |
| 98 | +fpb_enable_patch(0, true); |
| 99 | +``` |
| 100 | +
|
| 101 | +slot 值写死在源码里。如果用户切换 slot 或使用自动模式重新注入,源码里的 comp id 不会更新。 |
| 102 | +
|
| 103 | +**整改后**: |
| 104 | +
|
| 105 | +模板中 `fpb_enable_patch` 改为使用宏,头部注释去掉 Slot 行: |
| 106 | +
|
| 107 | +```c |
| 108 | +/** |
| 109 | + * Patch for: fl_hello |
| 110 | + * Original: 0x08001234 |
| 111 | + */ |
| 112 | +... |
| 113 | +fpb_enable_patch(FPB_PATCH_COMP_ID, false); |
| 114 | +ORIG_FL_HELLO(); |
| 115 | +fpb_enable_patch(FPB_PATCH_COMP_ID, true); |
| 116 | +``` |
| 117 | + |
| 118 | +`FPB_PATCH_COMP_ID` 完全由编译时 `-D` 注入,源码中不定义默认值。未定义时编译报错,这是期望行为 — 强制要求通过构建系统传入,避免静默使用错误的 comp id。 |
| 119 | + |
| 120 | +**文件**: `core/compiler.py` — `compile_inject` |
| 121 | + |
| 122 | +新增 `comp_id` 参数,在构建编译命令时追加 `-DFPB_PATCH_COMP_ID=N`: |
| 123 | + |
| 124 | +```python |
| 125 | +def compile_inject( |
| 126 | + ... |
| 127 | + comp_id: int = -1, # 新增 |
| 128 | +) -> Tuple[...]: |
| 129 | +``` |
| 130 | + |
| 131 | +```python |
| 132 | +# 在 cmd 构建完成后 |
| 133 | +if comp_id >= 0: |
| 134 | + cmd.extend(["-D", f"FPB_PATCH_COMP_ID={comp_id}"]) |
| 135 | +``` |
| 136 | + |
| 137 | +**文件**: `fpb_inject.py` — `inject` |
| 138 | + |
| 139 | +将 `actual_comp` 传递给第二次 `compile_inject`(此时 comp 已确定): |
| 140 | + |
| 141 | +```python |
| 142 | +# 第二次编译(已知 actual_comp) |
| 143 | +data, inject_symbols, error = self.compile_inject( |
| 144 | + ... |
| 145 | + comp_id=actual_comp, |
| 146 | +) |
| 147 | +``` |
| 148 | + |
| 149 | +第一次编译(用于计算 code_size,base_addr=0x20000000)也需要传一个临时值。由于第一次编译只是为了确定大小,comp_id 不影响二进制大小,传 0 即可: |
| 150 | + |
| 151 | +```python |
| 152 | +# 第一次编译(确定大小) |
| 153 | +data, inject_symbols, error = self.compile_inject( |
| 154 | + ... |
| 155 | + comp_id=0, # 临时值,不影响 code_size |
| 156 | +) |
| 157 | +``` |
| 158 | + |
| 159 | +## 3. 兼容性 |
| 160 | + |
| 161 | +| 场景 | 影响 | |
| 162 | +|------|------| |
| 163 | +| 已有 patch 源码(硬编码 comp id) | 不受影响,`-D` 不会覆盖源码中的字面量 | |
| 164 | +| 已有 patch 源码(使用 `FPB_PATCH_COMP_ID` 宏) | 由 `-D` 自动赋值,正常工作 | |
| 165 | +| MCP 工具注入(comp=-1) | 已支持自动分配,无需改动 | |
| 166 | +| 手动选择 Slot 0~7 | 行为不变,传指定的 comp id | |
| 167 | +| NuttX dpatch 自动模式 | 修复,逻辑槽位 = 物理 comp | |
| 168 | + |
| 169 | +## 4. 涉及文件 |
| 170 | + |
| 171 | +| 文件 | 改动 | |
| 172 | +|------|------| |
| 173 | +| `templates/partials/editor.html` | slotSelect 增加 Auto 选项(默认选中) | |
| 174 | +| `static/js/core/slots.js` | 支持 selectedSlot = -1,状态栏显示 "Auto" | |
| 175 | +| `static/js/features/patch.js` | 模板使用 `FPB_PATCH_COMP_ID` 宏,去掉 Slot 注释行 | |
| 176 | +| `core/compiler.py` | 新增 comp_id 参数,编译时注入 `-DFPB_PATCH_COMP_ID=N` | |
| 177 | +| `fpb_inject.py` | 两次 compile_inject 调用传递 comp_id | |
0 commit comments