sync: WIN10-I9-64G 2026-10-19
This commit is contained in:
@@ -0,0 +1,143 @@
|
||||
---
|
||||
name: release_zynq7k
|
||||
description: Package a hardware-verified Zynq-7000 (PetaLinux) product version into a release for colleagues — PetaLinux BSP, QSPI flash files and a whole-flash image, eMMC/SD flash files and deploy script, the final merged rootfs, PL bitstream, PS runtime files, app sources (PS and companion MCUs such as i.MX6), a README and SHA-256 manifest — under ./release/<name>_<version>/. Use when the user asks to make/build/prepare a release package, "release 包", "打包 release", "制作发布包", "交付给同事", hand a Zynq-7000 version to another team, or names a requirement doc (e.g. Notes/PRxx.md) listing release items for a Zynq-7000 board. Not for building or deploying the system itself (petalinux-qspi / petalinux-emmc / petalinux-ps / petalinux-pl do that).
|
||||
---
|
||||
|
||||
# release_zynq7k:Zynq-7000 release 打包
|
||||
|
||||
把一个**已经在硬件上验证过**的版本,整理成同事拿到就能部署、能重建的 release 包。
|
||||
release 只做收集、整理、合成、校验和说明,不重新设计,也不改变已验证的产物。
|
||||
凡是没在硬件上验证过的东西(新脚本、新合成的文件),README 里必须如实写明。
|
||||
|
||||
## 0. 先读
|
||||
|
||||
- **项目约定**:项目 adapter(AGENTS.md / CLAUDE.md)、全局与本项目 bmem 记忆、用户指定的需求文档(如 `Notes/PRxx.md`)。
|
||||
需求文档列出的条目就是 release 必须交付的条目;它明确排除的内容(例如 "除了 vivado 工程源码")不放进包里。
|
||||
- **机器参数**:交叉编译 VM 的地址和账号、PetaLinux / Vivado 路径都从 `~\skills_env.md` 读取,不写死在任何地方。
|
||||
plink/pscp 第一次连接时会报未知主机指纹,从报错里取出指纹,用 `-hostkey` 传入。
|
||||
- **参考案例**:[references/emv93-dvbt2.md](references/emv93-dvbt2.md) 是第一次用本流程做的 release(EMV9.3 DVB-T2),
|
||||
里面记录了版式、证据来源,以及这次踩过的坑。
|
||||
|
||||
## 1. 确定发布哪个版本(门禁 R0)
|
||||
|
||||
1. **定位版本**:在 git tag、bmem 恢复入口和交付目录里找到要发布的版本,列出每个产物的 SHA-256。
|
||||
2. **核对产物来源**:每个产物都必须有证据表明它就是硬件验收时用的那份。
|
||||
证据可以是:交付目录里的哈希、板上读回的哈希,或 VM `images/linux` 里与交付一致的哈希。
|
||||
没有证据的文件不进 release;若它确实必需,就在 README 里标成"未验证"。
|
||||
3. **源码 tag**:源码有 git 就用 tag。没有 git 先问用户;若用户同意,`git init` 后提交并打 tag。
|
||||
新建仓库要做三件事:
|
||||
- 设置 `core.autocrlf=false`,否则板上的 shell 脚本会被转成 CRLF;
|
||||
- 在 `.gitignore` 里排除构建目录和大体积抓包;
|
||||
- 排除 Windows 保留文件名(如 `COM3.log`),git 无法打开这类文件。
|
||||
4. **板子不是必需的**:打包只用已验证的文件。不要为了打包去改板上的状态,也不要重启板子。
|
||||
|
||||
## 2. 目录版式
|
||||
|
||||
`./release/<NAME>_v<版本>/`(目录名与 git tag 一一对应):
|
||||
|
||||
| 目录 | 内容 |
|
||||
|---|---|
|
||||
| `01_petalinux_bsp/` | 每个 PetaLinux 工程一个 `.bsp`(正式系统 + QSPI 救援系统) |
|
||||
| `02_qspi_flash/` | BOOT.BIN、QSPI 内核 FIT、整片镜像 `qspi_full_0x0.bin`、`uboot_scripts/` |
|
||||
| `03_emmc_flash/` | `p1_boot_fat/`(FIT + PL bin)、`emmc_deploy.sh`、`EMMC_SHA256SUMS`、`rescue_scripts/`(原来验证过的脚本) |
|
||||
| `04_rootfs/` | 最终根文件系统 tar.gz,以及生成它的 spec 和脚本 |
|
||||
| `05_pl_bit/` | 板上实际加载的 `.bin`,同次构建的 `.bit` 和 `.hdf` / `.xsa` |
|
||||
| `06_ps_runtime/` | 应用二进制、init 脚本、配置、内核模块、运行数据,按板上目录分子目录 |
|
||||
| `07_ps_app_src/` | 用 `git archive <tag> <dir>` 导出的应用源码和 PetaLinux meta-user 层 |
|
||||
| `08_<mcu>_app_src/`、`09_<mcu>_runtime/` | 其它处理器(如 i.MX6)的源码和部署件;没有就省略 |
|
||||
| `10_pc_tools/` | 可选:PC 端验证工具 |
|
||||
| `README.md` | 说明文档,从 [templates/README_template.md](templates/README_template.md) 开始写 |
|
||||
| `00_SHA256SUMS` | 全部文件的校验和,`sha256sum -c` 格式 |
|
||||
|
||||
另在 `./release/` 下放 `<目录名>.tar.gz` 和对应的 `.sha256`。`.tar.gz` 不入 git(它只是目录的打包,可以随时重新生成),目录本身入 git。
|
||||
|
||||
## 3. 逐项制作
|
||||
|
||||
### BSP(R1)
|
||||
|
||||
- 在 VM 上执行 `petalinux-package --bsp -p <proj> --output <name>.bsp`。
|
||||
- **先同步应用配方**:打包前确认 `project-spec/meta-user` 里各应用配方的 `files/src` 是本次发布的源码。
|
||||
若不是,先把旧配方 tar 备份,再用发布源码替换,并去掉 CR。替换后读回配方里关键文件的 sha 核对。
|
||||
- 每个归档解到**各自的空目录**,避免两个包混在同一个目录里。
|
||||
- **HDF 一致性**:比较 BSP 中 `hw-description/ps7_init.c` 与发布 `.hdf` 里的 `ps7_init.c`。
|
||||
- 相同:说明只有 PL 不同(PL 由 U-Boot 加载),README 写明"换 PL 不需要重建 Linux";
|
||||
- 不同:必须告诉用户,由用户决定是否用发布的 HDF 重建。
|
||||
- 检查 `tar tzf` 的结果,确认 BSP 里包含配方、DT 和 U-Boot 补丁,并且没有 `build/` 和 `images/`。
|
||||
|
||||
### QSPI(R2)
|
||||
|
||||
- **布局从证据取**:U-Boot 烧写脚本、`/proc/mtd`、flash 布局 json,不要猜。
|
||||
- 生成整片镜像和布局 json:
|
||||
```
|
||||
python scripts/qspi_image.py qspi_full_0x0.bin 0x0:BOOT.BIN <KOFF>:image.ub.qspi --erase <ENVOFF>:<ENVLEN> --layout qspi_layout.json
|
||||
```
|
||||
每段的 CRC32 必须与证据记录的一致。
|
||||
- 用 [templates/qspi_program.txt](templates/qspi_program.txt) 填好长度和 CRC,IP 留占位符;原来验证过的脚本一并附上。
|
||||
|
||||
### eMMC / SD(R3)
|
||||
|
||||
- **分区计划从证据取**:`fdisk -l`、`/proc/partitions`、`blkid`,记录扇区边界、文件系统、卷标和 FAT 容量。
|
||||
- 从 [templates/emmc_deploy.sh](templates/emmc_deploy.sh) 改出本板脚本:只改顶部 profile 变量,其余照搬已验证脚本的步骤顺序。
|
||||
- 写成 LF 换行,在 VM 上跑 `sh -n` 和 `busybox sh -n`;
|
||||
- **不要用 `! cmd` 当检查**:`set -e` 不会因为它失败而退出,要写成显式的 `if`。
|
||||
- 脚本没在板上整体跑过,就在 README 写"未上板实测,首次对照原脚本"。
|
||||
- `EMMC_SHA256SUMS` 只写文件名,与 TFTP 下载到板上的文件对应。
|
||||
|
||||
### rootfs(R4)
|
||||
|
||||
- 写一个 spec json,交给 [scripts/merge_rootfs.py](scripts/merge_rootfs.py) 合成。
|
||||
- spec 要还原已验证部署脚本的效果:基础 rootfs、覆盖包、替换的文件(带权限)、符号链接、目录。
|
||||
- spec 的 `expect` 里写每个关键文件的已验证 sha。
|
||||
- 运行 `merge_rootfs.py spec.json out.tar.gz --verify`,结果必须全部 OK。
|
||||
- 用 tarfile 合成,不在 Windows 上解开再重新打包,否则会丢失属主、权限、符号链接和设备节点。
|
||||
- 生成后在 VM 上跑一次 `tar tzf`,确认 GNU tar 能读,成员数与预期一致。
|
||||
- **不放每板唯一的数据**:DPD/校准曲线、主机密钥、序列号、MAC 都不放。若 rootfs 自带这类文件,用 spec 的 `remove` 去掉,并在 README 写明新板如何生成。
|
||||
|
||||
### PL / PS 运行文件 / 源码(R5–R7)
|
||||
|
||||
- **PL**:发布板上实际加载的 `.bin`(或 bootgen 产物),再附同次构建的 `.bit` 和 `.hdf`。
|
||||
- **PS 运行文件**:从交付目录或合成的 rootfs 里取,哈希与 rootfs 中的成员一致。
|
||||
- **源码**:用 `git archive <tag> <dirs> | tar x`,只导出纳入版本控制的源码。
|
||||
- 用 `release_check.py scan --src-dirs` 检查源码目录里没有 ELF、`.o` 或 `.ko`;
|
||||
- 需求文档排除的部分(如 Vivado 工程)不导出。
|
||||
|
||||
### README(R8)
|
||||
|
||||
按模板写,必须包括:
|
||||
- 版本清单(板上位置 + sha 前 16 位)、存储布局表;
|
||||
- 从全新或出厂板开始的部署步骤,以及冷启动验收的期望值;
|
||||
- 现场相关设置:每板唯一值和站点值,逐个写出所在文件;
|
||||
- 从源码构建的方法、已知限制。
|
||||
|
||||
写完后自己对照脚本和包内实际内容核对每一步,例如 `install.sh` 是否已经做了某一步。别把旧板专用的步骤写成通用步骤。
|
||||
|
||||
## 4. 检查与收尾(R9)
|
||||
|
||||
1. **扫描**:
|
||||
```
|
||||
python scripts/release_check.py scan <dir> --env ~/skills_env.md --mac <测试板 MAC> ... --src-dirs 07_ps_app_src,08_xxx_app_src
|
||||
```
|
||||
- 退出码 2 表示找到了凭据值或 `user:pass@` URL,必须清除后重做,不得发布;
|
||||
- 工具脚本里写死的口令,改成从环境变量读取,并在源头(仓库)修正后重新导出,不要只改 release 副本;
|
||||
- 例外:厂商出厂默认账户(随固件发布,常常就是品牌名,比如网页里的品牌 URL)。确认属实后,用 `--allow <KEY>` 把它归为 acknowledged,并在 README 写明"出厂默认账户,现场建议修改";
|
||||
- 工具状态文件(`*.upstate.json` 等)从源码目录里删掉;
|
||||
- 测试板 MAC:发布用的配置文件要留空或改成占位符,源码和旧脚本里残留的要在 README 列出;
|
||||
- 开发机路径、私钥(例如 mini_httpd 自带的示例 pem):逐项判断后写进 README。
|
||||
2. **生成并核对校验和**:`release_check.py sums <dir>`,然后 `release_check.py verify <dir>` 必须 0 bad、0 not listed。
|
||||
3. **打包**:`tar czf` 生成整包,再生成 `.sha256`。
|
||||
4. **git**:提交 release 目录,打 tag `release-v<版本>`,`.gitignore` 加上 `release/*.tar.gz`。
|
||||
5. **bmem**:用 `bmem add --prj` 记一条:
|
||||
- release 位置、tag、整包 sha;
|
||||
- 各目录的要点、VM 上做过的改动(备份位置)、未验证项;
|
||||
- 然后执行 `bmem sync`。
|
||||
6. **向用户汇报**:目录表、未验证项、需要同事注意的每板值和站点值、仓库与 bmem 状态。
|
||||
有硬件异常(例如打包中途板子失联)要说明,但不要擅自重启板子。
|
||||
|
||||
## 脚本
|
||||
|
||||
| 脚本 | 用途 |
|
||||
|---|---|
|
||||
| `scripts/qspi_image.py` | 按偏移拼整片 QSPI 镜像(空隙 0xFF),检查重叠,输出每段的 CRC32 和布局 json |
|
||||
| `scripts/merge_rootfs.py` | 按 spec 无损、可复现地合成 rootfs tar.gz;`--verify` 核对 expect 哈希和符号链接 |
|
||||
| `scripts/release_check.py` | `sums` / `verify` 生成和核对 `00_SHA256SUMS`;`scan` 查凭据、MAC、开发机路径、源码目录里的二进制、保留文件名 |
|
||||
| `templates/` | README 骨架、U-Boot QSPI 烧写脚本、eMMC 部署脚本模板 |
|
||||
@@ -0,0 +1,4 @@
|
||||
interface:
|
||||
display_name: "Zynq-7000 release"
|
||||
short_description: "Package a hardware-verified Zynq-7000 PetaLinux version into a release"
|
||||
default_prompt: "Use $release_zynq7k to package the current verified version into ./release per the requirement doc."
|
||||
@@ -0,0 +1,84 @@
|
||||
# Worked example: EMV9.3 DVB-T2 release v2026.10.15
|
||||
|
||||
First release built with this procedure (project prjID `emv93-dvbt2`; its bmem
|
||||
entry records the location and the git tag `release-v2026.10.15`). Use it as a model.
|
||||
Board values below belong to EMV9.3 only and must not be reused for another board.
|
||||
|
||||
## Platform
|
||||
|
||||
- Zynq xc7z100 (PL + PS) plus an i.MX6ULL board controller that has its own app and web UI.
|
||||
- Toolchain: PetaLinux 2018.1, Vivado 2018.1.
|
||||
- Two PetaLinux projects on the cross-build VM:
|
||||
- `emv93_t2`: production; FIT on eMMC, PL bin loaded by U-Boot `plload`.
|
||||
- `emv93_qspi`: rescue initramfs in QSPI.
|
||||
- The VM host and credentials come from `~\skills_env.md` (`CROSS_VM_*`).
|
||||
|
||||
## Requirement doc
|
||||
|
||||
`Notes/PR02.md` listed nine items:
|
||||
1. BSP
|
||||
2. QSPI flash files
|
||||
3. eMMC flash files
|
||||
4. rootfs
|
||||
5. PL bit on the rootfs
|
||||
6. PS runtime
|
||||
7. PS app source
|
||||
8. i.MX6 app source
|
||||
9. a README
|
||||
|
||||
The Vivado (PL) sources were explicitly excluded.
|
||||
|
||||
The i.MX6 runtime and the PC test tools were added as directories 09 and 10, because item 8 cannot be deployed without them.
|
||||
|
||||
## Layouts recorded from evidence
|
||||
|
||||
QSPI layout (from the proven U-Boot scripts `g4_stage1.txt` and `g4g5_write.txt`):
|
||||
|
||||
| Region | Offset | Content length | Erase length | CRC32 |
|
||||
|---|---|---|---|---|
|
||||
| BOOT.BIN | 0x0 | 0x89df0 | 0x500000 | 03cd9c7b |
|
||||
| U-Boot env | 0x500000 | — | 0x20000, left blank | — |
|
||||
| image.ub.qspi | 0x520000 | 0x9bbca0 | 0x1800000 | 0653b9e8 |
|
||||
| Full image | 0x0 | 0xEDBCA0 | — | 183d24ec |
|
||||
|
||||
The full image was built with:
|
||||
|
||||
```
|
||||
qspi_image.py qspi_full_0x0.bin 0x0:BOOT.BIN 0x520000:image.ub.qspi --erase 0x500000:0x20000
|
||||
```
|
||||
|
||||
eMMC layout:
|
||||
- Device `/dev/mmcblk1`, 7.3 GiB, the factory DOS partition table.
|
||||
- The partition boundaries came from `/proc/partitions` (179,1 = 125016 KiB) and a `fdisk -l` line ("16 250047 250032 122.1M b W95 FAT32").
|
||||
- p1: FAT32, sectors 16–250047. It holds only 120 MB: P-0059 recorded that with seven bit files on it, `cp` failed silently.
|
||||
- p2: ext4, sector 250048 to the end.
|
||||
|
||||
## Pieces and where they came from
|
||||
|
||||
| Release dir | Source |
|
||||
|---|---|
|
||||
| 01 BSP | `petalinux-package --bsp -p <proj> --output <name>.bsp` on the VM, for both projects. Before packaging, the t2ps recipe's `files/src` was replaced with the released 1.8 sources; the old recipe was tarred as a backup first. |
|
||||
| 02 / 03 / 05 | The verified delivery directory (`Notes/auto/delivery_t2_dpd_20261014`). The VM `images/linux` hashes were checked against it, and they matched. |
|
||||
| 04 rootfs | Merged from the base `rootfs.tar.gz`, the T2 runtime overlay, the released binaries and configs, and the C23 symlinks. Each key member hash was checked against the board-verified files, and GNU tar on the VM listed all 1229 members. |
|
||||
| 06 | Copied from the delivery dirs; the stream files were extracted from the overlay tarball. |
|
||||
| 07 / 08 | `git archive <tag> ps` and `git archive <tag> imx6`, giving 325 files with no build output. |
|
||||
|
||||
## What the scan / review caught (and how it was handled)
|
||||
|
||||
- **i.MX6 MACs.**
|
||||
- `network.conf` carried the test board's MAC. It was blanked in the release copy, with a comment.
|
||||
- The source copy and an old deploy script held two other boards' MACs; these were listed in the README.
|
||||
- Deriving the MAC from OTP does not work on that image, so the README says each board's MAC must be filled in.
|
||||
- **TP receiver address.** `t2ps.conf` holds the receiving PC's MAC/IP for the TP IQ stream; this is documented as a site setting.
|
||||
- **DPD curve.** It depends on the board and its loopback, so it was left out; a new board runs a full DPD instead.
|
||||
- **Development-machine paths.** The PC tools contain dev-machine paths; this is documented under the known limits.
|
||||
- **Credentials.** The product's own web account (`usr.conf`) is part of the firmware and stays. No infrastructure password appeared.
|
||||
- **Hardware not used.** The board went silent during packaging. That was harmless, because nothing in the release was taken from the live board.
|
||||
|
||||
## Script mistakes made the first time (now handled by the skill's scripts)
|
||||
|
||||
- **Two archives unpacked into one directory.** The recipe tarball and the source tarball both unpacked into `t2ps/`, mixing their contents. Unpack each archive into its own empty directory.
|
||||
- **Shell `set -e` ignores a negated command.** `! grep` does not stop the script; write the test as an explicit `if`.
|
||||
- **Over-eager path normalisation.** `n.lstrip("./")` also strips the leading dot of names such as `./.ssh`. `merge_rootfs.py` removes only the `./` prefixes.
|
||||
- **CRLF from Windows editors.** Shell scripts written on Windows can get CRLF line endings. Strip them, then run `sh -n` and `busybox sh -n` on the VM.
|
||||
- **Archive bytes are not reproducible.** The gzip header mtime and the mtime of added files changed the archive hash on every run. `merge_rootfs.py` is now deterministic, so compare member hashes rather than archive bytes across tool versions.
|
||||
@@ -0,0 +1,116 @@
|
||||
#!/usr/bin/env python3
|
||||
"""merge_rootfs.py -- assemble a release root filesystem tarball from a JSON spec.
|
||||
|
||||
python merge_rootfs.py <spec.json> <out.tar.gz> [--verify]
|
||||
|
||||
Works on Windows or Linux without root: tar members are copied as TarInfo, so
|
||||
owners, modes, symlinks, hard links and device nodes survive exactly (an
|
||||
extract-and-retar on Windows, or as non-root on Linux, would lose them).
|
||||
Later layers override earlier ones by path; the output is deterministic
|
||||
(fixed mtime for added entries, gzip header mtime 0), so the same spec gives
|
||||
the same archive bytes.
|
||||
|
||||
spec.json (paths relative to the spec file unless absolute):
|
||||
{
|
||||
"mtime": 1792022400, # added entries; optional
|
||||
"layers": [ "rootfs.tar.gz", "overlay.tar.gz" ], # applied in order
|
||||
"remove": [ "etc/dropbear/dropbear_rsa_host_key" ], # paths (and their subtrees)
|
||||
"files": [ {"path": "usr/bin/app", "src": "bin/app", "mode": "0755"} ],
|
||||
"links": [ {"path": "etc/app/current.cfg", "target": "/etc/app/a.cfg"} ],
|
||||
"dirs": [ "aw/dpd_curve" ],
|
||||
"expect": { "usr/bin/app": "<sha256 or a prefix>" } # checked with --verify
|
||||
}
|
||||
"""
|
||||
import gzip, hashlib, io, json, os, sys, tarfile
|
||||
|
||||
|
||||
def norm(n):
|
||||
while n.startswith("./"):
|
||||
n = n[2:]
|
||||
n = n.strip("/")
|
||||
return "./" + n if n not in ("", ".") else "./"
|
||||
|
||||
|
||||
class Tree:
|
||||
def __init__(self):
|
||||
self.m, self.order = {}, []
|
||||
|
||||
def put(self, ti, data):
|
||||
ti.name = norm(ti.name)
|
||||
if ti.islnk():
|
||||
ti.linkname = norm(ti.linkname)
|
||||
if ti.name not in self.m:
|
||||
self.order.append(ti.name)
|
||||
self.m[ti.name] = (ti, data)
|
||||
|
||||
def remove(self, p):
|
||||
p = norm(p)
|
||||
for n in list(self.m):
|
||||
if n == p or n.startswith(p + "/"):
|
||||
del self.m[n]
|
||||
self.order = [n for n in self.order if n in self.m]
|
||||
|
||||
|
||||
def info(name, kind, mode, mtime):
|
||||
ti = tarfile.TarInfo(name); ti.type = kind; ti.mode = mode; ti.mtime = mtime
|
||||
ti.uid = ti.gid = 0; ti.uname = ti.gname = "root"
|
||||
return ti
|
||||
|
||||
|
||||
def main(argv):
|
||||
if len(argv) < 3:
|
||||
sys.exit(__doc__)
|
||||
spec_path, out = argv[1], argv[2]
|
||||
base = os.path.dirname(os.path.abspath(spec_path))
|
||||
rel = lambda p: p if os.path.isabs(p) else os.path.join(base, p)
|
||||
spec = json.load(open(spec_path))
|
||||
mt = int(spec.get("mtime", 1700000000))
|
||||
t = Tree()
|
||||
for layer in spec.get("layers", []):
|
||||
with tarfile.open(rel(layer), "r:*") as tf:
|
||||
for ti in tf.getmembers():
|
||||
t.put(ti, tf.extractfile(ti).read() if ti.isfile() else None)
|
||||
for p in spec.get("remove", []):
|
||||
t.remove(p)
|
||||
for f in spec.get("files", []):
|
||||
d = open(rel(f["src"]), "rb").read()
|
||||
ti = info(f["path"], tarfile.REGTYPE, int(f.get("mode", "0644"), 8), mt); ti.size = len(d)
|
||||
t.put(ti, d)
|
||||
for l in spec.get("links", []):
|
||||
ti = info(l["path"], tarfile.SYMTYPE, 0o777, mt); ti.linkname = l["target"]
|
||||
t.put(ti, None)
|
||||
for d in spec.get("dirs", []):
|
||||
parts = norm(d)[2:].split("/")
|
||||
for k in range(1, len(parts) + 1): # parents first, keep existing ones
|
||||
p = norm("/".join(parts[:k]))
|
||||
if p not in t.m:
|
||||
t.put(info(p, tarfile.DIRTYPE, 0o755, mt), None)
|
||||
raw = io.BytesIO()
|
||||
with tarfile.open(fileobj=raw, mode="w", format=tarfile.GNU_FORMAT) as tf:
|
||||
for n in t.order:
|
||||
ti, data = t.m[n]
|
||||
tf.addfile(ti, io.BytesIO(data) if data is not None else None)
|
||||
with open(out, "wb") as fo, gzip.GzipFile(filename="", mode="wb", fileobj=fo, mtime=0) as gz:
|
||||
gz.write(raw.getvalue())
|
||||
n_dev = sum(1 for n in t.order if t.m[n][0].isdev())
|
||||
print(f"{len(t.order)} members ({n_dev} device nodes) -> {out}")
|
||||
print("sha256", hashlib.sha256(open(out, "rb").read()).hexdigest())
|
||||
if "--verify" in argv:
|
||||
bad = 0
|
||||
for p, want in spec.get("expect", {}).items():
|
||||
e = t.m.get(norm(p))
|
||||
got = hashlib.sha256(e[1]).hexdigest() if e and e[1] is not None else None
|
||||
ok = got is not None and got.startswith(want.lower())
|
||||
bad += not ok
|
||||
print(("OK " if ok else "BAD ") + (got or "missing")[:16], p)
|
||||
for l in spec.get("links", []):
|
||||
e = t.m.get(norm(l["path"]))
|
||||
ok = e is not None and e[0].issym() and e[0].linkname == l["target"]
|
||||
bad += not ok
|
||||
print(("OK " if ok else "BAD ") + "link", l["path"], "->", l["target"])
|
||||
if bad:
|
||||
sys.exit(f"{bad} check(s) failed")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main(sys.argv)
|
||||
@@ -0,0 +1,68 @@
|
||||
#!/usr/bin/env python3
|
||||
"""qspi_image.py -- build a whole-flash QSPI image for a Zynq-7000 board.
|
||||
|
||||
python qspi_image.py <out.bin> <offset>:<file> [<offset>:<file> ...]
|
||||
[--erase <offset>:<len> ...] [--size <bytes>] [--layout <out.json>]
|
||||
|
||||
Every part is placed at its offset; gaps (and --erase ranges, e.g. the U-Boot
|
||||
environment sector that must stay blank so the default bootcmd is used) are
|
||||
0xFF, exactly what a fresh `sf erase` leaves. The image ends at the last byte
|
||||
of the last part (or --size). Overlapping parts are refused. Prints
|
||||
offset / length / CRC32 of every part and of the image, the numbers to check
|
||||
against `crc32` in U-Boot, and writes them to --layout as JSON.
|
||||
Offsets and lengths accept 0x hex.
|
||||
"""
|
||||
import json, sys, zlib
|
||||
|
||||
def num(s): return int(s, 0)
|
||||
|
||||
def main(argv):
|
||||
if len(argv) < 3:
|
||||
sys.exit(__doc__)
|
||||
out, rest = argv[1], argv[2:]
|
||||
parts, erase, size, layout = [], [], None, None
|
||||
i = 0
|
||||
while i < len(rest):
|
||||
a = rest[i]
|
||||
if a == "--erase":
|
||||
o, l = rest[i + 1].split(":"); erase.append((num(o), num(l))); i += 2
|
||||
elif a == "--size":
|
||||
size = num(rest[i + 1]); i += 2
|
||||
elif a == "--layout":
|
||||
layout = rest[i + 1]; i += 2
|
||||
else:
|
||||
o, f = a.split(":", 1); parts.append((num(o), f)); i += 1
|
||||
blobs = []
|
||||
for o, f in sorted(parts):
|
||||
d = open(f, "rb").read()
|
||||
blobs.append((o, f, d))
|
||||
for (o1, f1, d1), (o2, f2, d2) in zip(blobs, blobs[1:]):
|
||||
if o1 + len(d1) > o2:
|
||||
sys.exit(f"overlap: {f1} 0x{o1:x}+0x{len(d1):x} runs into {f2} at 0x{o2:x}")
|
||||
end = max(o + len(d) for o, _, d in blobs) # erased tails need no bytes: blank anyway
|
||||
if size is not None:
|
||||
if size < end:
|
||||
sys.exit(f"--size 0x{size:x} smaller than the content end 0x{end:x}")
|
||||
end = size
|
||||
img = bytearray(b"\xff" * end)
|
||||
for o, _, d in blobs:
|
||||
img[o:o + len(d)] = d
|
||||
for o, l in erase:
|
||||
for o2, f2, d2 in blobs:
|
||||
if o < o2 + len(d2) and o2 < o + l:
|
||||
sys.exit(f"--erase 0x{o:x}:0x{l:x} overlaps {f2}")
|
||||
open(out, "wb").write(img)
|
||||
rep = {"image": out, "length": len(img), "crc32": "%08x" % zlib.crc32(img), "parts": [],
|
||||
"erased": [{"offset": hex(o), "length": hex(l)} for o, l in erase]}
|
||||
for o, f, d in blobs:
|
||||
rep["parts"].append({"file": f, "offset": hex(o), "length": hex(len(d)),
|
||||
"crc32": "%08x" % zlib.crc32(d)})
|
||||
print(f"0x{o:08x} len 0x{len(d):x} crc32 {zlib.crc32(d):08x} {f}")
|
||||
for o, l in erase:
|
||||
print(f"0x{o:08x} len 0x{l:x} erased (0xFF)")
|
||||
print(f"image {out}: len 0x{len(img):x} crc32 {rep['crc32']}")
|
||||
if layout:
|
||||
json.dump(rep, open(layout, "w"), indent=1)
|
||||
|
||||
if __name__ == "__main__":
|
||||
main(sys.argv)
|
||||
@@ -0,0 +1,149 @@
|
||||
#!/usr/bin/env python3
|
||||
"""release_check.py -- checksum, verify and scan a release directory.
|
||||
|
||||
python release_check.py sums <release_dir> # (re)write 00_SHA256SUMS
|
||||
python release_check.py verify <release_dir> # check every listed file
|
||||
python release_check.py scan <release_dir> [--env ~/skills_env.md] [--mac AA:BB:..]...
|
||||
[--src-dirs 07_ps_app_src,08_imx6_app_src] [--allow KEY]...
|
||||
|
||||
scan reports (never prints a secret value, only the file and the key name):
|
||||
* values of *PASS* / *PASSWORD* / *TOKEN* / *SECRET* / *KEY* keys in --env found
|
||||
in any text file, and "user:password@" URLs; --allow KEY moves that key's hits
|
||||
to "acknowledged" (e.g. a vendor factory-default account that ships in the
|
||||
firmware and is also the brand name) -- still listed, no failure;
|
||||
* tool state files (*.upstate.json) inside --src-dirs;
|
||||
* private keys (-----BEGIN ... PRIVATE KEY-----);
|
||||
* the given board MACs (--mac, e.g. a test board's) and any other MAC literal;
|
||||
* development-machine absolute paths (X:\\..., /home/<user>, /Users/<user>);
|
||||
* ELF / object / archive files inside --src-dirs (build output in a source tree);
|
||||
* Windows reserved names (CON, PRN, AUX, NUL, COM1-9, LPT1-9) that break copies.
|
||||
Exit status 0 = scan finished (read the report), 2 = a secret was found.
|
||||
"""
|
||||
import hashlib, os, re, sys
|
||||
|
||||
SUMS = "00_SHA256SUMS"
|
||||
RESERVED = re.compile(r"^(con|prn|aux|nul|com[0-9]|lpt[0-9])(\..*)?$", re.I)
|
||||
MAC = re.compile(r"\b[0-9A-Fa-f]{2}(?::[0-9A-Fa-f]{2}){5}\b")
|
||||
WINPATH = re.compile(r"\b[A-Za-z]:[\\/](?:[^\s\"'<>|]+)")
|
||||
NIXHOME = re.compile(r"/(?:home|Users)/[A-Za-z0-9_.-]+")
|
||||
URLCRED = re.compile(r"[a-z][a-z0-9+.-]*://[^/\s:@]+:[^/\s@]+@", re.I)
|
||||
PRIVKEY = re.compile(r"-----BEGIN [A-Z ]*PRIVATE KEY-----")
|
||||
|
||||
|
||||
def files(root):
|
||||
for d, _, fs in os.walk(root):
|
||||
for f in fs:
|
||||
yield os.path.join(d, f)
|
||||
|
||||
|
||||
def relp(root, p):
|
||||
return "./" + os.path.relpath(p, root).replace(os.sep, "/")
|
||||
|
||||
|
||||
def sha(p):
|
||||
h = hashlib.sha256()
|
||||
with open(p, "rb") as f:
|
||||
for b in iter(lambda: f.read(1 << 20), b""):
|
||||
h.update(b)
|
||||
return h.hexdigest()
|
||||
|
||||
|
||||
def is_text(b):
|
||||
return b"\0" not in b[:8192]
|
||||
|
||||
|
||||
def cmd_sums(root):
|
||||
lines = [f"{sha(p)} {relp(root, p)}" for p in sorted(files(root))
|
||||
if os.path.basename(p) != SUMS]
|
||||
open(os.path.join(root, SUMS), "w", newline="\n").write("\n".join(lines) + "\n")
|
||||
print(f"{len(lines)} files -> {SUMS}")
|
||||
|
||||
|
||||
def cmd_verify(root):
|
||||
bad = n = 0
|
||||
listed = set()
|
||||
for line in open(os.path.join(root, SUMS)):
|
||||
h, p = line.rstrip("\n").split(" ", 1)
|
||||
listed.add(p); n += 1
|
||||
fp = os.path.join(root, p[2:])
|
||||
if not os.path.exists(fp) or sha(fp) != h:
|
||||
bad += 1; print("BAD", p)
|
||||
extra = [relp(root, p) for p in files(root)
|
||||
if os.path.basename(p) != SUMS and relp(root, p) not in listed]
|
||||
for p in extra:
|
||||
print("NOT LISTED", p)
|
||||
print(f"{n} listed, {bad} bad, {len(extra)} not listed")
|
||||
sys.exit(1 if bad or extra else 0)
|
||||
|
||||
|
||||
def env_secrets(path):
|
||||
out = {}
|
||||
if not path or not os.path.exists(path):
|
||||
return out
|
||||
for line in open(path, encoding="utf-8", errors="replace"):
|
||||
m = re.match(r"\s*([A-Z0-9_]*(?:PASS|PASSWORD|TOKEN|SECRET|KEY)[A-Z0-9_]*)\s*=\s*(\S+)", line)
|
||||
if m and len(m.group(2)) >= 4:
|
||||
out[m.group(1)] = m.group(2)
|
||||
return out
|
||||
|
||||
|
||||
def cmd_scan(root, args):
|
||||
env = os.path.expanduser(args[args.index("--env") + 1]) if "--env" in args else None
|
||||
macs = [args[i + 1].lower() for i, a in enumerate(args) if a == "--mac"]
|
||||
srcd = args[args.index("--src-dirs") + 1].split(",") if "--src-dirs" in args else []
|
||||
allow = {args[i + 1] for i, a in enumerate(args) if a == "--allow"}
|
||||
secrets = env_secrets(env)
|
||||
found_secret = False
|
||||
rep = {k: [] for k in ("secret", "acknowledged", "url_cred", "privkey", "board_mac", "mac", "dev_path",
|
||||
"src_binary", "reserved")}
|
||||
for p in files(root):
|
||||
r = relp(root, p)
|
||||
if RESERVED.match(os.path.basename(p)):
|
||||
rep["reserved"].append(r)
|
||||
b = open(p, "rb").read()
|
||||
if any(r.startswith("./" + s.strip("/") + "/") for s in srcd):
|
||||
if p.endswith(".upstate.json"):
|
||||
rep["src_binary"].append(r + " (tool state file, remove)")
|
||||
elif b[:4] == b"\x7fELF" or p.endswith((".o", ".a", ".so", ".ko")):
|
||||
rep["src_binary"].append(r)
|
||||
if not is_text(b):
|
||||
continue
|
||||
t = b.decode("utf-8", "replace")
|
||||
for k, v in secrets.items():
|
||||
if v in t:
|
||||
if k in allow:
|
||||
rep["acknowledged"].append(f"{r} (value of {k})")
|
||||
else:
|
||||
rep["secret"].append(f"{r} (value of {k})"); found_secret = True
|
||||
if URLCRED.search(t):
|
||||
rep["url_cred"].append(r); found_secret = True
|
||||
if PRIVKEY.search(t):
|
||||
rep["privkey"].append(r)
|
||||
ms = {m.lower() for m in MAC.findall(t)}
|
||||
for m in ms:
|
||||
(rep["board_mac"] if m in macs else rep["mac"]).append(f"{r} {m}")
|
||||
dp = sorted(set(WINPATH.findall(t)) | set(NIXHOME.findall(t)))
|
||||
if dp:
|
||||
rep["dev_path"].append(f"{r} ({len(dp)}: {dp[0][:60]}{' ...' if len(dp) > 1 else ''})")
|
||||
titles = {"secret": "SECRET VALUES (must be removed)",
|
||||
"acknowledged": "acknowledged values (--allow; say so in the README)",
|
||||
"url_cred": "user:password@ URLs",
|
||||
"privkey": "private keys (check: product key or leaked?)",
|
||||
"board_mac": "given board MACs (must not ship)", "mac": "other MAC literals (review)",
|
||||
"dev_path": "development-machine paths (review / document)",
|
||||
"src_binary": "binaries inside source dirs", "reserved": "Windows reserved names"}
|
||||
for k, title in titles.items():
|
||||
print(f"== {title}: {len(rep[k])}")
|
||||
for x in rep[k][:40]:
|
||||
print(" ", x)
|
||||
if len(rep[k]) > 40:
|
||||
print(f" ... {len(rep[k]) - 40} more")
|
||||
sys.exit(2 if found_secret else 0)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
if len(sys.argv) < 3:
|
||||
sys.exit(__doc__)
|
||||
c, root = sys.argv[1], sys.argv[2]
|
||||
{"sums": lambda: cmd_sums(root), "verify": lambda: cmd_verify(root),
|
||||
"scan": lambda: cmd_scan(root, sys.argv[3:])}[c]()
|
||||
@@ -0,0 +1,82 @@
|
||||
# <产品/板卡> <功能> Release <版本>
|
||||
|
||||
<一句话:平台(Zynq-7000 型号 + 其它处理器)、本版本的功能要点。>
|
||||
|
||||
本包内容对应工程 <git 仓库/路径> 的 tag `<tag>`,<日期> 在 <哪块板> 上实测:<验收结论,逐项>。
|
||||
每个文件的完整 SHA-256 见 `00_SHA256SUMS`,部署后请逐个读回核对。
|
||||
<若有发布需求文档(如 Notes/PRxx.md),写明按哪一份制作,以及哪些内容按要求不在包内。>
|
||||
|
||||
## 1. 目录
|
||||
|
||||
| 目录 | 需求项 | 内容 |
|
||||
|---|---|---|
|
||||
| `01_petalinux_bsp/` | BSP | <正式系统 .bsp>、<救援系统 .bsp>,PetaLinux <版本> |
|
||||
| `02_qspi_flash/` | QSPI 烧写文件 | BOOT.BIN、image.ub.qspi、`qspi_full_0x0.bin`、`uboot_scripts/` |
|
||||
| `03_emmc_flash/` | eMMC/SD 烧写文件 | `p1_boot_fat/`、`emmc_deploy.sh`、`EMMC_SHA256SUMS`、原始已验证脚本 |
|
||||
| `04_rootfs/` | rootfs | `<名>_rootfs.tar.gz`(最终合成),合成脚本与 spec |
|
||||
| `05_pl_bit/` | PL bit | `<top>.bin`(板上加载的)、同次构建 `.bit` / `.hdf`(或 `.xsa`) |
|
||||
| `06_ps_runtime/` | PS 运行文件 | 应用、初始化脚本、配置、内核模块、运行数据 |
|
||||
| `07_ps_app_src/` | PS app 源码 | 应用源码 + PetaLinux meta-user 层 |
|
||||
| `08_<mcu>_app_src/` | 其它处理器源码 | <可选> |
|
||||
| `09_<mcu>_runtime/` | 部署所需 | <可选> |
|
||||
| `10_pc_tools/` | 可选 | PC 端验证工具 |
|
||||
| `README.md` | 说明 | 本文件 |
|
||||
|
||||
## 2. 版本清单
|
||||
|
||||
| 层 | 文件 | 板上位置 | sha256 前 16 位 |
|
||||
|---|---|---|---|
|
||||
| QSPI | BOOT.BIN(FSBL + U-Boot <配置>) | QSPI 0x0 | |
|
||||
| QSPI | image.ub.qspi(救援) | QSPI <偏移> | |
|
||||
| 启动分区 | image.ub | /image.ub | |
|
||||
| 启动分区 | <top>.bin(PL) | / | |
|
||||
| 根分区 | <rootfs>.tar.gz | / | |
|
||||
| rootfs | <应用 / 配置,逐个列出> | | |
|
||||
|
||||
工具链:PetaLinux <版本>,Vivado <版本>,<交叉编译器>。
|
||||
|
||||
## 3. 存储布局
|
||||
|
||||
QSPI(`sf`):
|
||||
|
||||
| 偏移 | 长度 | 内容 |
|
||||
|---|---|---|
|
||||
| 0x000000 | | BOOT.BIN,CRC32 |
|
||||
| <环境区> | | U-Boot 环境区,保持擦除(全 0xFF),使用默认 bootcmd |
|
||||
| <内核区> | | image.ub.qspi,CRC32 |
|
||||
|
||||
- `qspi_full_0x0.bin`:上表按偏移拼好的整片镜像,空隙填 0xFF,从 0x0 写入;长度 <>,CRC32 <>。
|
||||
- 默认 bootcmd = `<...>`;救援启动命令:`<...>`。
|
||||
|
||||
eMMC/SD(`<设备>`,<容量>,<分区表类型>):
|
||||
|
||||
| 分区 | 扇区 | 文件系统 | 内容 |
|
||||
|---|---|---|---|
|
||||
|
||||
## 4. 部署步骤(全新 / 出厂状态的板子)
|
||||
|
||||
0. 准备:
|
||||
- 先备份出厂内容(QSPI、eMMC、其它处理器);
|
||||
- 串口参数;
|
||||
- PC 上的 TFTP / HTTP 服务;
|
||||
- 默认网络地址表。
|
||||
1. QSPI:二选一,U-Boot 脚本或编程器写整片镜像。
|
||||
2. eMMC:
|
||||
- 进入救援 Linux,执行 `sh emmc_deploy.sh <PC_IP> [--partition]`。
|
||||
- 若该脚本未在板上整体跑过,在此明确写出来。
|
||||
3. <其它处理器> 部署。
|
||||
4. 冷启动与验收:寄存器 / 日志 / 网页 / 业务功能,逐项写出期望值。
|
||||
|
||||
## 5. 现场相关设置(换环境必须改)
|
||||
|
||||
<每板唯一值(MAC、序列号、校准/DPD 曲线),以及站点值(IP、接收端 MAC/IP),逐个写出所在文件和处理方法。>
|
||||
|
||||
## 6. <本版本的主要功能说明>
|
||||
|
||||
## 7. 从源码构建
|
||||
|
||||
<BSP → petalinux-create -s / build / package --boot;应用单独编译与单元测试;rootfs 重新合成方法;BSP 的 hw-description 与发布 bit 的关系。>
|
||||
|
||||
## 8. 已知限制
|
||||
|
||||
<未验证项、只做了部分验证的项、测试环境的异常、工具里的开发机路径等。>
|
||||
@@ -0,0 +1,74 @@
|
||||
#!/bin/sh
|
||||
# emmc_deploy.sh -- write a Zynq-7000 production system (FAT boot + ext4 root) to
|
||||
# eMMC/SD from the QSPI rescue Linux. TEMPLATE from the release_zynq7k skill:
|
||||
# fill the variables below from the board's evidence (fdisk -l / /proc/partitions,
|
||||
# the project's proven deploy script), then test it once on the board and say so
|
||||
# in the README until that happened.
|
||||
#
|
||||
# sh emmc_deploy.sh <PC_IP> [--partition]
|
||||
#
|
||||
# The PC serves, by TFTP: every file in BOOT_FILES, ROOTFS and SUMS.
|
||||
set -e
|
||||
# ---------------------------------------------------------------- board profile
|
||||
DEV=/dev/mmcblk1 # verified device node (NOT inferred from the name)
|
||||
P1_START=16 # DOS partition plan, sectors (only used with --partition)
|
||||
P1_SIZE=250032
|
||||
P1_TYPE=b # b = FAT32
|
||||
P2_START=250048 # p2 runs to the end of the device
|
||||
BOOT_FILES="image.ub system_top.bin" # copied to p1 (FAT); keep FAT capacity in mind
|
||||
ROOTFS=rootfs.tar.gz # extracted to p2 (ext4)
|
||||
SUMS=EMMC_SHA256SUMS # sha256sum -c format for BOOT_FILES + ROOTFS
|
||||
CHECK_IN_ROOT="usr/bin/app etc/init.d/app-init" # read back after writing
|
||||
# ------------------------------------------------------------------------------
|
||||
PC=${1:?usage: emmc_deploy.sh <PC_IP> [--partition]}
|
||||
W=/tmp/e2
|
||||
|
||||
if grep " / " /proc/mounts | grep -q "$(basename $DEV)"; then
|
||||
echo "running from $DEV: boot the QSPI rescue system"; exit 1
|
||||
fi
|
||||
[ -b $DEV ] || { echo "$DEV missing"; exit 1; }
|
||||
cat /sys/block/$(basename $DEV)/device/type /sys/block/$(basename $DEV)/device/name /sys/block/$(basename $DEV)/size
|
||||
|
||||
mkdir -p $W && cd $W
|
||||
for f in $SUMS $BOOT_FILES $ROOTFS; do
|
||||
rm -f $f; tftp -g -r $f $PC
|
||||
done
|
||||
sha256sum -c $SUMS # a failed transfer stops here, before any write
|
||||
echo SHA_OK
|
||||
|
||||
[ -f /etc/udev/rules.d/automount.rules ] && mv /etc/udev/rules.d/automount.rules /tmp/automount.rules.off && udevadm control --reload-rules || true
|
||||
umount ${DEV}p1 2>/dev/null || true
|
||||
umount ${DEV}p2 2>/dev/null || true
|
||||
if grep -q "$(basename $DEV)" /proc/mounts; then echo "$DEV still mounted"; exit 1; fi
|
||||
|
||||
if [ "$2" = "--partition" ]; then
|
||||
printf 'label: dos\nstart=%s, size=%s, type=%s\nstart=%s, type=83\n' \
|
||||
$P1_START $P1_SIZE $P1_TYPE $P2_START | sfdisk $DEV
|
||||
sleep 1
|
||||
fi
|
||||
[ -b ${DEV}p1 ] && [ -b ${DEV}p2 ] || { echo "no p1/p2 on $DEV (use --partition)"; exit 1; }
|
||||
|
||||
mkfs.vfat -F 32 -n BOOT ${DEV}p1
|
||||
mkfs.ext4 -F -q -L rootfs ${DEV}p2
|
||||
mkdir -p /mnt/b /mnt/r
|
||||
mount -t vfat ${DEV}p1 /mnt/b
|
||||
mount -t ext4 ${DEV}p2 /mnt/r
|
||||
for f in $BOOT_FILES; do cp $W/$f /mnt/b/$f; done
|
||||
tar xzf $W/$ROOTFS -C /mnt/r
|
||||
sync
|
||||
cd /
|
||||
umount /mnt/b
|
||||
umount /mnt/r
|
||||
sync
|
||||
fsck.vfat -n ${DEV}p1 | tail -n 1
|
||||
e2fsck -fn ${DEV}p2 | tail -n 1
|
||||
|
||||
echo 3 > /proc/sys/vm/drop_caches
|
||||
mount -o ro -t vfat ${DEV}p1 /mnt/b
|
||||
mount -o ro -t ext4 ${DEV}p2 /mnt/r
|
||||
for f in $BOOT_FILES; do sha256sum /mnt/b/$f; done
|
||||
for f in $CHECK_IN_ROOT; do sha256sum /mnt/r/$f; done
|
||||
umount /mnt/b
|
||||
umount /mnt/r
|
||||
[ -f /tmp/automount.rules.off ] && mv /tmp/automount.rules.off /etc/udev/rules.d/automount.rules || true
|
||||
echo "EMMC_DEPLOY_DONE -- compare the hashes above with 00_SHA256SUMS, then power-cycle"
|
||||
@@ -0,0 +1,27 @@
|
||||
# U-Boot commands: program the Zynq-7000 QSPI -- TEMPLATE from release_zynq7k.
|
||||
# Fill the lengths, CRC32 and offsets from scripts/qspi_image.py --layout output
|
||||
# and the board's flash layout; keep the IP addresses as placeholders.
|
||||
# Paste one line at a time on the PS console after stopping autoboot, and check
|
||||
# every crc32 / cmp.b result. A failed tftpboot must stop the sequence: never
|
||||
# let stale DDR content be written.
|
||||
# BOOT.BIN len <LEN_BOOT> crc32 <CRC_BOOT> -> QSPI 0x0
|
||||
# (bootenv) <ENV_OFF>, <ENV_LEN> erased -> default bootcmd
|
||||
# image.ub.qspi len <LEN_KERN> crc32 <CRC_KERN> -> QSPI <KERN_OFF>
|
||||
setenv ipaddr <BOARD_IP>
|
||||
setenv serverip <PC_IP>
|
||||
tftpboot 0x10000000 BOOT.BIN
|
||||
crc32 0x10000000 <LEN_BOOT>
|
||||
tftpboot 0x11000000 image.ub.qspi
|
||||
crc32 0x11000000 <LEN_KERN>
|
||||
sf probe 0
|
||||
sf erase 0x0 <BOOT_ERASE_LEN>
|
||||
sf write 0x10000000 0x0 <LEN_BOOT>
|
||||
sf read 0x12000000 0x0 <LEN_BOOT>
|
||||
cmp.b 0x10000000 0x12000000 <LEN_BOOT>
|
||||
crc32 0x12000000 <LEN_BOOT>
|
||||
sf erase <ENV_OFF> <ENV_LEN>
|
||||
sf erase <KERN_OFF> <KERN_ERASE_LEN>
|
||||
sf write 0x11000000 <KERN_OFF> <LEN_KERN>
|
||||
sf read 0x13000000 <KERN_OFF> <LEN_KERN>
|
||||
cmp.b 0x11000000 0x13000000 <LEN_KERN>
|
||||
crc32 0x13000000 <LEN_KERN>
|
||||
Reference in New Issue
Block a user