Files
boweiandClaude Opus 5 42edf13936 sync: Shanghai-U9 2026-09-03 硬编码审计清掉 0819/0826 挂了两轮的文档类遗留
smod_atsc_dev(SKILL.md / references/imx6.md / references/fpga.md)、
fpga_update(SKILL.md / references/protocol.md)、fpga-imx6-flow(SKILL.md)
正文表格与 shell 示例里的 E:\Xilinx_design\smod\SMOD_ATSC\... 和 192.168.1.143
改成引用 skills_env.md 的 SMOD_REPO_ROOT / SMOD_BOARD_IP;shell 块统一用 $BOARD
并在块内注明取值出处。13 处锚点全部唯一命中,无行为改动。

刻意未改:各 skill 的 YAML description 里的同名字面量(那是触发关键词,不是
脚本取值,改了降低匹配率);脚本里 _env_lookup(KEY, 字面量) 形式的回落默认值;
fpga_flash/SKILL.md 的 SMOD_DTMB_CG 命令示例(属另一个工程,无对应 env 键)。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018FykpdV2Y4RUWXU5PtoLqh
2026-09-03 02:52:05 +08:00

6.9 KiB
Raw Permalink Blame History

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 线根本没接出去。

// 修复前 —— 只是内部 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...",直接读设备日志:

# BOARD = ~\skills_env.md 的 SMOD_BOARD_IP
ssh -o HostKeyAlgorithms=+ssh-rsa root@$BOARD \
  "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。