Files
skills-win/fpga_update/references/protocol.md
T
2026-07-14 18:18:17 +08:00

138 lines
6.9 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# FPGA 远程升级协议(TCP 1234)
## 首先:这条路和网页升级毫无关系
设备网页 `sys_set.html` 的 **Remote Update** 框是给 **imx6 固件包**用的,不是给 FPGA 用的。
把 `.mcs` 传上去等于扔进垃圾桶:
```
upload.cgi → 存到 /aw/upgrade/ → 通知 app(WEB_UPGRADE)
app: start_upgrade() → set_upgrade_flg(1) → imx_up_thr → check_package.sh
check_package.sh: find /aw/upgrade -name '*.tar.gz' ← 只认 tar.gz
找不到 → ret=3 → rm -rf /aw/upgrade/* ← 你的 mcs 被删了
```
(`start_upgrade()` 还会在 `ctrl==CTRL_LOCAL` 时直接 return 0 —— 本地模式下网页升级本就不生效。)
FPGA 走的是**另一条独立的路**:app 里的 `fpga_upgrade_task()` 在 **TCP 1234** 上起了个 server
(`inc/config.h: UPGRADE_LISTEN_PORT 1234`),跑私有帧协议。原厂用 `tools/Xfpga_ru.exe` 驱动它
(一个 16 KB 的 .NET GUI,没有命令行)。`scripts/fpga_ru.py` 就是它的替代品。
服务端权威实现:`my_exciter/imx6/src/atsc_app/task/tcp_task.c` :: `fpga_upgrade_process()`。
## 帧格式
```
请求 AA | cmd | datalen:u16 小端 | payload | sum | BB
应答 AA | ack | 01 | 00 | ack | sum | BB (固定 7 字节)
sum 从帧头到 sum 之前所有字节的 8-bit 累加和(get_check_sum,见 bsp/general_api.c)
```
服务端校验 `rxdata[0]==0xAA && rxdata[datalen+5]==0xBB`,再比对 `get_check_sum(rxdata, datalen+4)`。
应答里 ack 同时出现在 `[1]` 和 `[4]`;校验和错时 `[1]` 被改写,所以**读 `[4]`**。
| cmd | 值 | ack | 值 |
|---|---|---|---|
| `UPGRADE_START` | 0x01 | `START_OK` | 0x01 |
| `POLL_ERASER` | 0x02 | `ERASER_ING` / `ERASER_END` | 0x02 / 0x03 |
| `W_DATA_BLOCK` | 0x03 | `READY` / `NOT_READY` | 0x04 / 0x05 |
| `W_DATA_BLOCK_LAST` | 0x04 | `ABORT_SUCCESS` / `ABORT_ERROR` | 0x06 / 0x07 |
## 时序
1. `UPGRADE_START`(空负载)→ `START_OK`。
服务端往 FPGA 寄存器 `0xE0` 写 `RESET_EN(bit0)=1` 再写 0,放开 `SpiFlashProgrammer`。
2. 轮询 `POLL_ERASER` 直到 `ERASER_END`。擦 8 MB 要几十秒。
3. 逐帧 `W_DATA_BLOCK`,负载是 **mcs 的原始 ASCII 文本行**。
- `READY` → 发下一帧
- `NOT_READY` → 等 1 ms **重发同一帧**
4. 负载里出现 EOF 记录(`:00000001FF`)后,改发 `W_DATA_BLOCK_LAST`(空负载)轮询,
直到 `ABORT_SUCCESS` 或 `ABORT_ERROR`。服务端最多让你轮 100 次。
5. `ABORT_SUCCESS` 后 app 调 `reset_fpga_system()` 并置 `fpga_up_sys_reboot=1` —— **imx6 会重启,
连接会断**。这是正常的,不是错误。
## 三个会把你坑死的硬限制
都在 `recv_upgrade_data_parse()` / `inc/tcp_task.h` 里,超了就 `ABORT_ERROR`:
1. **负载 ≤ 538 字节**。`rxdata[MAX_DATA_LEN=544]` 装的是**整帧**,帧头尾占 6 字节。
2. **每行长度必须在 `[11, 50]` 字节之间**(`UPDATA_RAW_FRAME_MIN/MAX_LEN`),**含行尾 CRLF**。
实际 mcs 只有三种行长:17(ELA)、45(数据)、13(EOF),都合法。
3. **一帧里必须是整行**。服务端不看换行符,它用 `strchr(payload, ':')` 重新切分,
最后一行按 `strlen` 量到负载末尾。**一行跨两帧 = 两行畸形行 = 直接中止。**
另外服务端一次 `lwip_recv()` 读一帧,所以客户端要一次 `send()` 发完整帧,并关掉 Nagle。
数据行 `:10 AAAA 00 <32 hex> CC` 里只有那 16 个数据字节被取走;地址和 ELA 记录全被忽略
(详见 `references/quickboot.md`)。8 MB 镜像 = 524288 行 × 16 B,按 11 行/帧 → 约 47663 帧。
## 曾经的"第二次远程升级必失败" —— 已定位并修复
**症状**:JTAG 烧完 RU_golden 后第一次远程升级成功;冷启动后再升级第二次,卡在擦除阶段永不结束。
**根因**:`smod_top.v` 里 flash 的三根 SPI 线**根本没接出去**。
```verilog
// 修复前 —— 只是内部 wire,MISO 还被钉死成常量
wire outSpiCsB, outSpiMosi;
wire inSpiMiso = 1'b1;
// RU_golden_top.v 一直是对的
output outSpiCsB ;
output outSpiMosi ;
input inSpiMiso ;
```
`smod_ioplanning.xdc` 和 `timing.xdc` 早就把 `outSpiCsB`(P18) / `outSpiMosi`(R14) / `inSpiMiso`(R15)
约束好了,但顶层没有这几个端口,于是 Vivado 只是丢了三条 CRITICAL WARNING 就继续:
```
CRITICAL WARNING: [Vivado 12-4739] set_output_delay:No valid object(s) found for '[get_ports outSpiCsB]'
```
后果:my_exciter 里的 `SpiFlashProgrammer` 片选和 MOSI 悬空、MISO 恒为 1,读 flash ID 得到全 `0xFF`,
**在擦除之前就报错退出**。golden 的引脚是接好的,所以由 golden 执行的那一次升级永远成功 ——
两次升级执行擦写的是**不同 bitstream 里的 programmer**,这正是"第一次成、第二次败"的由来。
**修复**:把三个信号提升为 `smod_top` 的顶层端口(已改)。实测 `rstate` 从
`0x7`(ERROR=1, DONE=1, EraseOK=0)变为 `0x0` → `0xc`(Ready=1, EraseOK=1),擦除正常结束。
## ⚠ imx6 缺陷:`POLL_ERASER` 不看 ERROR 位
上面那个 FPGA bug 之所以表现为"卡死"而不是"报错",是因为 `tcp_task.c` 的擦除轮询只判断 `EARSE_OK`:
```c
rstate = fpga_byte_read(0x01, FPGA_UPGRADE_CTRL_ADDR);
if (rstate & FPGA_UP_STATUS_EARSE_OK_MASK) ret_data = ERASER_END;
else ret_data = ERASER_ING; // ERROR 位被无视
```
于是"已经失败"和"还在擦"返回同一个 `ERASER_ING`,客户端只能一直等到超时。原厂那个
`Xfpga_ru.exe` 就是这样毫无提示地转圈。
**这个 bug 还在。** 排查时不要信客户端的"erasing...",直接读设备日志:
```bash
ssh -o HostKeyAlgorithms=+ssh-rsa root@192.168.1.143 \
"grep 'ing|end' /var/module_log/imx_atsc_app.log | tail -3"
```
`rstate=0x7` = programmer 已中止;`rstate=0x0` 才是真的在擦。
**根治**:在 `POLL_ERASER` 分支里加 `if (rstate & FPGA_UPGRADE_STATUS_ERROR_MASK) ret_data = ABORT_ERROR;`。
改完要重新交叉编译并部署 app。
## 尚未证实的风险:DPD 与升级数据流共用 SPI1
这一条**不是**上面那个 bug 的原因,但结构上确实存在,写在这里以免以后踩:
- FPGA 侧升级期间 `inReset_EnableB==0`,`spi_slave32` 常开,SPI1 上任何 32-bit 字都会被当成镜像数据。
`smod_top.v` 自己的注释:`// when doing dpd S&L, MISO is spi read pin`。
- imx6 侧 DPD 的 `ispi_tx_big_block()`(`bsp/dpd_proc.c`)和升级的 `ispi_fpga_send_buf_4Byte()`
**用同一个 `fpga_spi_fd`**(`bsp/isys_config.c`)。
- 互斥量 `g_now_only_upgrade_fpga` 只在 `lcd_task.c` 一处被读,且在 `ERASER_END` 才置位。
另一个标志 `get_upgrade_flg()` 只有网页升级会置位。
实测三次连续升级成功时 `adpc=0`(ADPC_OFF),所以 DPD 的 S&L 没有在跑 —— **这条路径从未被真正验证过**。
稳妥起见,升级前把 ADPC 设为 OFF。