6.9 KiB
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 |
时序
UPGRADE_START(空负载)→START_OK。 服务端往 FPGA 寄存器0xE0写RESET_EN(bit0)=1再写 0,放开SpiFlashProgrammer。- 轮询
POLL_ERASER直到ERASER_END。擦 8 MB 要几十秒。 - 逐帧
W_DATA_BLOCK,负载是 mcs 的原始 ASCII 文本行。READY→ 发下一帧NOT_READY→ 等 1 ms 重发同一帧
- 负载里出现 EOF 记录(
:00000001FF)后,改发W_DATA_BLOCK_LAST(空负载)轮询, 直到ABORT_SUCCESS或ABORT_ERROR。服务端最多让你轮 100 次。 ABORT_SUCCESS后 app 调reset_fpga_system()并置fpga_up_sys_reboot=1—— imx6 会重启, 连接会断。这是正常的,不是错误。
三个会把你坑死的硬限制
都在 recv_upgrade_data_parse() / inc/tcp_task.h 里,超了就 ABORT_ERROR:
- 负载 ≤ 538 字节。
rxdata[MAX_DATA_LEN=544]装的是整帧,帧头尾占 6 字节。 - 每行长度必须在
[11, 50]字节之间(UPDATA_RAW_FRAME_MIN/MAX_LEN),含行尾 CRLF。 实际 mcs 只有三种行长:17(ELA)、45(数据)、13(EOF),都合法。 - 一帧里必须是整行。服务端不看换行符,它用
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 线根本没接出去。
// 修复前 —— 只是内部 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:
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...",直接读设备日志:
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。