From 79c154579c2b3a36bb9cbe72ee5cba2fcfba00b1 Mon Sep 17 00:00:00 2001 From: chenjintao Date: Mon, 28 Sep 2026 14:19:42 +0800 Subject: [PATCH] fix: rebalance HX GNSS OTA partitions --- build_rel.bat | 6 +- docs/OTA_READY_FACTORY_BASELINE.md | 59 +++++++-------- .../threadx/config/common/app_linkscript.ld | 4 +- .../interface/custom_app/inc/ota_baseline.h | 12 ++-- .../custom_app/lib/hal/build_macro.h | 4 +- .../config/EG800AKCN_11LG/customer_fs.bin | 4 +- ...ELRH_QUEC_FLASH_LAYOUT_SS_04M_HX_GNSS.json | 4 +- tools/verify_ota_ready_baseline.py | 71 +++++++++++++------ 8 files changed, 97 insertions(+), 67 deletions(-) diff --git a/build_rel.bat b/build_rel.bat index df3a3e0..0b93cee 100644 --- a/build_rel.bat +++ b/build_rel.bat @@ -4,8 +4,8 @@ for /f %%i in ('powershell -NoProfile -Command "Get-Date -Format yyyyMMddHHmmss" set appbuildrev=NULL set appbuildmodel=EG800AKCN_11LG set appbuildver=beta1 -set otalayoutid=eg800akcn_11lg-fs640-app2c2000 -set otabuildprofile=ota_ready_fs640 +set otalayoutid=eg800akcn_11lg-hxgnss-fs576-app2e5000 +set otabuildprofile=ota_ready_hxgnss_fs576 set pythonexe=ql-cross-tool\win32\owtoolchain\Python\python.exe if /i not "%1" == "" ( @@ -37,7 +37,7 @@ call build.bat app call build.bat kernel call build.bat firmware -"%pythonexe%" tools\verify_ota_ready_baseline.py --app-bin ql-application\threadx\build\app.bin +"%pythonexe%" tools\verify_ota_ready_baseline.py --app-bin ql-application\threadx\build\app.bin --kernel-bin ql-kernel\threadx\build\release_kernel.bin if errorlevel 1 ( echo "OTA-ready baseline artifact verification failed" exit /b 1 diff --git a/docs/OTA_READY_FACTORY_BASELINE.md b/docs/OTA_READY_FACTORY_BASELINE.md index 5abd727..f93cccf 100644 --- a/docs/OTA_READY_FACTORY_BASELINE.md +++ b/docs/OTA_READY_FACTORY_BASELINE.md @@ -8,9 +8,9 @@ OTA 所依赖的底层条件。 本基线完成: -- 将 `customer_fs` 从 256 KiB 扩大到 640 KiB; -- 保持 `customer_app` 为 460 KiB; -- 将 APP 物理/XIP 地址固定为 `0x002C2000` / `0x802C2000`; +- 将 HX_GNSS 的 `customer_fs` 从 256 KiB 扩大到 576 KiB; +- 将 `customer_app` 从 460 KiB 调整为 384 KiB; +- 将 APP 物理/XIP 地址固定为 `0x002E5000` / `0x802E5000`; - 保留 SDK 已有的 `QUEC_APP_FULL_FOTA` 与 FULLOTA2 公共 API; - 将现有 OTA 命令切换为最小本地暂存式 FULLOTA2; - 为设备和构建产物加入唯一布局身份; @@ -34,22 +34,22 @@ OTA 所依赖的底层条件。 设备布局身份: ```text -eg800akcn_11lg-fs640-app2c2000 +eg800akcn_11lg-hxgnss-fs576-app2e5000 ``` 构建 profile: ```text -ota_ready_fs640 +ota_ready_hxgnss_fs576 ``` 4 MiB 内部 Flash 的尾部分区固定如下: | 分区 | 起始地址 | 结束地址 | 大小 | | --- | ---: | ---: | ---: | -| reserved | `0x00287000` | `0x002C2000` | 236 KiB | -| customer_app | `0x002C2000` | `0x00335000` | 460 KiB | -| customer_fs | `0x00335000` | `0x003D5000` | 640 KiB | +| reserved(按当前镜像估算) | `0x002D0000` | `0x002E5000` | 84 KiB | +| customer_app | `0x002E5000` | `0x00345000` | 384 KiB | +| customer_fs | `0x00345000` | `0x003D5000` | 576 KiB | | nvm | `0x003D5000` | `0x003F5000` | 128 KiB | | erase_rd | `0x003F5000` | `0x003F7000` | 8 KiB | | fota_param | `0x003F7000` | `0x003FA000` | 12 KiB | @@ -63,36 +63,37 @@ ota_ready_fs640 ### 3.1 分区和链接地址 - `CRANELRH_QUEC_FLASH_LAYOUT_SS_04M_HX_GNSS.json`:当前 `CC11XXW_GPS` 构建实际选择的 - 分区表,`customer_fs = 640KiB`; + 分区表,`customer_app = 384KiB`、`customer_fs = 576KiB`; - `CRANELRH_QUEC_FLASH_LAYOUT_SS_04M_OPEN.json`:同步保持 `customer_fs = 640KiB`,避免 无 GNSS variant 回退到旧布局; -- `app_linkscript.ld`:ROM 起始地址改为 `0x802C2000`,最大 ROM 大小收紧为 `0x73000`; +- `app_linkscript.ld`:ROM 起始地址改为 `0x802E5000`,最大 ROM 大小收紧为 `0x60000`; - `customer_fs.bin`:从原 256 KiB LittleFS 中提取全部 14 个音频文件,以 4096 字节块、 - 160 个块重新生成 640 KiB LittleFS; + 144 个块重新生成 576 KiB LittleFS; - 新文件系统镜像 SHA-256: - `19bd849dbcc6816f6dfffd1e585645a3474a2265f96a926b5dd073f0affb69f9`。 + `45350804d65fc6ded24caf9b215b71e89f9160d10d4998c5d57d4f4496d6000d`。 ### 3.2 运行身份 `ota_baseline.h` 固定布局身份、APP/FS 地址和分区大小。应用启动时打印: ```text -[OTA-BASELINE] layout=eg800akcn_11lg-fs640-app2c2000 ... slot=single +[OTA-BASELINE] layout=eg800akcn_11lg-hxgnss-fs576-app2e5000 ... slot=single [OTA-BASELINE] U: total=... free=... fota2_reserve=65536 ``` -该日志用于产线和联调确认设备没有混入旧的 fs256 基线,不改变任何业务状态。 +该日志用于产线和联调确认设备没有混入旧的 fs256 或错误的 fs640 OPEN 基线,不改变任何业务状态。 ### 3.3 构建保护 `build_rel.bat` 在构建前运行源码校验,在生成 APP 后运行容量校验。任何一项不满足都会中止: -- `customer_fs` 必须为 640 KiB; -- APP 地址必须为 `0x002C2000`; -- LittleFS 必须为 4096 字节块、160 块且镜像哈希一致; +- HX_GNSS `customer_fs` 必须为 576 KiB; +- APP 必须为 384 KiB,地址必须为 `0x002E5000`; +- LittleFS 必须为 4096 字节块、144 块且镜像哈希一致; +- 当前 kernel、DSP、RF 和 GNSS 镜像到 APP 之间必须至少保留 64 KiB; - kernel 必须继续定义 `QUEC_APP_FULL_FOTA`; - FULLOTA2 所需的 SDK API 必须存在; -- `app.bin + 8192` 不得超过 460 KiB。 +- `app.bin + 8192` 不得超过 384 KiB。 - MQTT OTA 入口必须调用 `banban_fullfota2_start()`,不得回退到 `ql_fullfota_app_start()`。 @@ -101,7 +102,8 @@ ota_ready_fs640 ```powershell ql-cross-tool\win32\owtoolchain\Python\python.exe tools\verify_ota_ready_baseline.py ql-cross-tool\win32\owtoolchain\Python\python.exe tools\verify_ota_ready_baseline.py ` - --app-bin ql-application\threadx\build\app.bin + --app-bin ql-application\threadx\build\app.bin ` + --kernel-bin ql-kernel\threadx\build\release_kernel.bin ``` ### 3.4 最小 FULLOTA2 执行路径 @@ -113,7 +115,7 @@ ql-cross-tool\win32\owtoolchain\Python\python.exe tools\verify_ota_ready_baselin 1. 删除上一次残留的 `U:/FotaFile.bin`; 2. HTTP GET 必须返回 200 和非零 `Content-Length`; -3. FBF 必须大于 8 KiB 且不超过 460 KiB; +3. FBF 必须大于 8 KiB 且不超过 384 KiB; 4. `U:` 空闲空间必须不小于 `Content-Length + 64 KiB`; 5. 设置内部 Flash 固定暂存路径,初始化 FOTA context; 6. 以 4096 字节缓冲顺序读取并调用 `ql_fota_image_write()`; @@ -125,7 +127,7 @@ ql-cross-tool\win32\owtoolchain\Python\python.exe tools\verify_ota_ready_baselin 任一步在设置升级标志前失败,都会关闭 HTTP/FOTA context、删除暂存包、撤销固定升级模式、 清除 pending 状态并把 `fota_start` 恢复为 0。旧 APP 未被擦写,可继续运行并接受下一次命令。 -旧实现会在 OTA 前删除服务器管理的提示音。fs640 基线有足够空间后,这版不再主动删除 +旧实现会在 OTA 前删除服务器管理的提示音。fs576 基线有足够空间后,这版不再主动删除 业务资源;若空间不足则明确失败,不通过牺牲业务文件强行升级。 `http_aofei.c` 中未被当前业务调用的旧 HTTP 轮询 OTA 包装函数原本仍可进入 FULLOTA1, @@ -161,10 +163,11 @@ ASR1605/CRANELRH,但量产包必须由 `EG800AKCN_11LG` 配置生成。 ### 5.1 静态和产物测试 1. 运行 `verify_ota_ready_baseline.py`,结果必须通过。 -2. 解包工厂 ZIP,确认其中 `customer_fs.bin` 为 655360 字节且哈希一致。 +2. 解包工厂 ZIP,确认其中 `customer_fs.bin` 为 589824 字节且哈希一致。 3. 使用 Aboot 工具检查 `partition.bin`,确认 APP/FS 地址与本文件一致。 -4. 确认 `customer_app.bin + 8192 <= 471040`。 -5. 确认 FBF 的目标物理地址是 `0x002C2000`,不能是旧地址 `0x00322000`。 +4. 确认 `customer_app.bin + 8192 <= 393216`。 +5. 确认 FBF 的目标物理地址是 `0x002E5000`,不能是旧地址 `0x00322000`、 + 也不能是错误基线地址 `0x002C2000`。 ### 5.2 全擦工厂烧录测试 @@ -172,7 +175,7 @@ ASR1605/CRANELRH,但量产包必须由 `EG800AKCN_11LG` 配置生成。 1. 下载器无越界、校验或 partition 错误。 2. USB 重新枚举,`AT`/`ATI` 正常。 -3. 串口打印正确 layout、地址和 640 KiB 文件系统容量。 +3. 串口打印正确 layout、地址和 576 KiB 文件系统容量。 4. 14 个预置音频均可读取和播放。 5. IMEI、SIM、驻网、数据连接和 GNSS 正常。 6. MQTT 注册、命令收发、心跳和状态上报正常。 @@ -226,7 +229,7 @@ ASR1605/CRANELRH,但量产包必须由 `EG800AKCN_11LG` 配置生成。 ```json { "product": "EG800AKCN_11LG", - "layout_id": "eg800akcn_11lg-fs640-app2c2000", + "layout_id": "eg800akcn_11lg-hxgnss-fs576-app2e5000", "version": "1.9.0", "release_seq": 10900, "min_release_seq": 10800, @@ -262,8 +265,8 @@ ASR1605/CRANELRH,但量产包必须由 `EG800AKCN_11LG` 配置生成。 ## 8. 发布和回退策略 -- 新生产设备只允许使用 fs640 工厂包和 `0x002C2000` APP/FBF。 -- 禁止向 fs640 设备发布旧地址 `0x00322000` 的 FBF。 +- 新生产设备只允许使用 HX_GNSS fs576 工厂包和 `0x002E5000` APP/FBF。 +- 禁止向 fs576 设备发布旧地址 `0x00322000` 或错误地址 `0x002C2000` 的 FBF。 - 如生产前验证失败,可回退源码分支和工厂包重新全擦烧录;不得混烧新旧布局制品。 - 量产后若只是业务问题,可通过 APP OTA 修复;若分区或 kernel 基线有问题,需要返厂或有线 完整烧录,不能假定业务 APP OTA 能安全修改分区表。 diff --git a/ql-application/threadx/config/common/app_linkscript.ld b/ql-application/threadx/config/common/app_linkscript.ld index fe83dba..3e976de 100644 --- a/ql-application/threadx/config/common/app_linkscript.ld +++ b/ql-application/threadx/config/common/app_linkscript.ld @@ -1,6 +1,6 @@ -__ROM_START_ADDR = 0x802C2000; -__MAX_ROM_SIZE = 0x73000; +__ROM_START_ADDR = 0x802E5000; +__MAX_ROM_SIZE = 0x60000; __RAM_START_ADDR = 0x7e040800; __MAX_RAM_SIZE = 0x100000; diff --git a/ql-application/threadx/interface/custom_app/inc/ota_baseline.h b/ql-application/threadx/interface/custom_app/inc/ota_baseline.h index 4c6f163..83dfc9d 100644 --- a/ql-application/threadx/interface/custom_app/inc/ota_baseline.h +++ b/ql-application/threadx/interface/custom_app/inc/ota_baseline.h @@ -4,14 +4,14 @@ #include #include "build_macro.h" -#define BANBAN_OTA_EXPECTED_LAYOUT_ID "eg800akcn_11lg-fs640-app2c2000" +#define BANBAN_OTA_EXPECTED_LAYOUT_ID "eg800akcn_11lg-hxgnss-fs576-app2e5000" #define BANBAN_OTA_LAYOUT_ID CUSTOM_APP_OTA_LAYOUT_ID #define BANBAN_OTA_BUILD_PROFILE CUSTOM_APP_OTA_BUILD_PROFILE -#define BANBAN_OTA_APP_PHYSICAL_ADDRESS UINT32_C(0x002C2000) -#define BANBAN_OTA_APP_XIP_ADDRESS UINT32_C(0x802C2000) -#define BANBAN_OTA_APP_PARTITION_SIZE UINT32_C(0x00073000) -#define BANBAN_OTA_FS_PHYSICAL_ADDRESS UINT32_C(0x00335000) -#define BANBAN_OTA_FS_PARTITION_SIZE UINT32_C(0x000A0000) +#define BANBAN_OTA_APP_PHYSICAL_ADDRESS UINT32_C(0x002E5000) +#define BANBAN_OTA_APP_XIP_ADDRESS UINT32_C(0x802E5000) +#define BANBAN_OTA_APP_PARTITION_SIZE UINT32_C(0x00060000) +#define BANBAN_OTA_FS_PHYSICAL_ADDRESS UINT32_C(0x00345000) +#define BANBAN_OTA_FS_PARTITION_SIZE UINT32_C(0x00090000) #define BANBAN_OTA_STAGING_RESERVE_BYTES UINT32_C(0x00010000) #if !defined(CUSTOM_APP_OTA_LAYOUT_ID) || !defined(CUSTOM_APP_OTA_BUILD_PROFILE) diff --git a/ql-application/threadx/interface/custom_app/lib/hal/build_macro.h b/ql-application/threadx/interface/custom_app/lib/hal/build_macro.h index d4865c5..68ac43e 100644 --- a/ql-application/threadx/interface/custom_app/lib/hal/build_macro.h +++ b/ql-application/threadx/interface/custom_app/lib/hal/build_macro.h @@ -4,6 +4,6 @@ #define CUSTOM_APP_BUILD_REV "NULL" #define CUSTOM_APP_BUILD_MODEL "EG800AKCN_11LG" #define CUSTOM_APP_BUILD_VER "1.8.0" -#define CUSTOM_APP_OTA_LAYOUT_ID "eg800akcn_11lg-fs640-app2c2000" -#define CUSTOM_APP_OTA_BUILD_PROFILE "ota_ready_fs640" +#define CUSTOM_APP_OTA_LAYOUT_ID "eg800akcn_11lg-hxgnss-fs576-app2e5000" +#define CUSTOM_APP_OTA_BUILD_PROFILE "ota_ready_hxgnss_fs576" #endif diff --git a/ql-config/config/EG800AKCN_11LG/customer_fs.bin b/ql-config/config/EG800AKCN_11LG/customer_fs.bin index 6093ff2..dbce40e 100644 --- a/ql-config/config/EG800AKCN_11LG/customer_fs.bin +++ b/ql-config/config/EG800AKCN_11LG/customer_fs.bin @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:19bd849dbcc6816f6dfffd1e585645a3474a2265f96a926b5dd073f0affb69f9 -size 655360 +oid sha256:45350804d65fc6ded24caf9b215b71e89f9160d10d4998c5d57d4f4496d6000d +size 589824 diff --git a/ql-config/quec-project/aboot/config/partition/CRANELRH_QUEC_FLASH_LAYOUT_SS_04M_HX_GNSS.json b/ql-config/quec-project/aboot/config/partition/CRANELRH_QUEC_FLASH_LAYOUT_SS_04M_HX_GNSS.json index d998fb2..7ed7800 100644 --- a/ql-config/quec-project/aboot/config/partition/CRANELRH_QUEC_FLASH_LAYOUT_SS_04M_HX_GNSS.json +++ b/ql-config/quec-project/aboot/config/partition/CRANELRH_QUEC_FLASH_LAYOUT_SS_04M_HX_GNSS.json @@ -61,12 +61,12 @@ { "name": "customer_app", "format": "raw", - "size": "460KiB" + "size": "384KiB" }, { "name": "customer_fs", "format": "raw", - "size": "640KiB" + "size": "576KiB" }, { "name": "nvm", diff --git a/tools/verify_ota_ready_baseline.py b/tools/verify_ota_ready_baseline.py index bea1bb5..9640db1 100644 --- a/tools/verify_ota_ready_baseline.py +++ b/tools/verify_ota_ready_baseline.py @@ -18,6 +18,10 @@ FLASH_TYPE = ROOT / "ql-config/config/QuecFLashType.ini" CHIP_PLATFORM = ROOT / "ql-config/config/CHIP_PLAT.ini" LINKER = ROOT / "ql-application/threadx/config/common/app_linkscript.ld" FS_IMAGE = ROOT / "ql-config/config/EG800AKCN_11LG/customer_fs.bin" +DSP_IMAGE = ROOT / "ql-config/soc_platform/CRANELRH/images/DSP_Reduce_SuLog_Buffer/dsp.bin" +RF_IMAGE = ROOT / "ql-config/config/EG800AKCN_11LG/rf.bin" +GNSS_BOOT_IMAGE = ROOT / "ql-config/config/EG800AKCN_11LG/gnss_boot.pkg" +GNSS_FIRM_IMAGE = ROOT / "ql-config/config/EG800AKCN_11LG/gnss_firm.pkg" KERNEL_FLAGS = ROOT / "ql-kernel/threadx/config/common/default_dflags_1602.mk" FOTA_HEADER = ROOT / "ql-application/threadx/common/include/ql_fota.h" BASELINE_HEADER = ROOT / "ql-application/threadx/interface/custom_app/inc/ota_baseline.h" @@ -30,15 +34,15 @@ LEGACY_HTTP_SOURCE = ROOT / "ql-application/threadx/interface/custom_app/http_ao FLASH_SIZE = 0x400000 EXPECTED = { - "app_start": 0x002C2000, - "app_size": 0x00073000, - "fs_start": 0x00335000, - "fs_size": 0x000A0000, - "reserved_size": 0x0003B000, - "xip_start": 0x802C2000, + "app_start": 0x002E5000, + "app_size": 0x00060000, + "fs_start": 0x00345000, + "fs_size": 0x00090000, + "xip_start": 0x802E5000, } FBF_HEADER_ALLOWANCE = 8192 -EXPECTED_FS_SHA256 = "19bd849dbcc6816f6dfffd1e585645a3474a2265f96a926b5dd073f0affb69f9" +MINIMUM_SYSTEM_MARGIN = 64 * 1024 +EXPECTED_FS_SHA256 = "45350804d65fc6ded24caf9b215b71e89f9160d10d4998c5d57d4f4496d6000d" def fail(message: str) -> None: @@ -76,9 +80,9 @@ def verify_layout() -> None: tail = flattened_tail() sizes = dict(tail) if sizes.get("customer_app") != EXPECTED["app_size"]: - fail("customer_app must be 460 KiB") + fail("customer_app must be 384 KiB") if sizes.get("customer_fs") != EXPECTED["fs_size"]: - fail("customer_fs must be 640 KiB") + fail("customer_fs must be 576 KiB") suffix = 0 starts: dict[str, int] = {} @@ -93,20 +97,15 @@ def verify_layout() -> None: if starts.get("customer_fs") != EXPECTED["fs_start"]: fail(f"customer_fs starts at 0x{starts.get('customer_fs', 0):08X}, expected 0x{EXPECTED['fs_start']:08X}") - # The system image ends at 0x287000 for this accepted 4 MiB CRANELRH baseline. - reserved = EXPECTED["app_start"] - 0x00287000 - if reserved != EXPECTED["reserved_size"]: - fail("reserved partition calculation changed") - def verify_linker() -> None: text = LINKER.read_text(encoding="utf-8") rom = re.search(r"__ROM_START_ADDR\s*=\s*(0x[0-9A-Fa-f]+)", text) maximum = re.search(r"__MAX_ROM_SIZE\s*=\s*(0x[0-9A-Fa-f]+)", text) if not rom or int(rom.group(1), 16) != EXPECTED["xip_start"]: - fail("linker ROM address is not 0x802C2000") + fail("linker ROM address is not 0x802E5000") if not maximum or int(maximum.group(1), 16) != EXPECTED["app_size"]: - fail("linker ROM limit is not the 460 KiB customer_app partition") + fail("linker ROM limit is not the 384 KiB customer_app partition") def verify_fs_image() -> None: @@ -123,8 +122,33 @@ def verify_fs_image() -> None: disk_version, block_size, block_count = struct.unpack_from("> 16 != 2: fail(f"unsupported LittleFS disk version 0x{disk_version:08X}") - if block_size != 4096 or block_count != 160: - fail(f"LittleFS geometry is block_size={block_size}, block_count={block_count}; expected 4096/160") + if block_size != 4096 or block_count != 144: + fail(f"LittleFS geometry is block_size={block_size}, block_count={block_count}; expected 4096/144") + + +def align_up(value: int, alignment: int) -> int: + return (value + alignment - 1) // alignment * alignment + + +def verify_system_fit(kernel_path: Path) -> None: + images = (kernel_path, DSP_IMAGE, RF_IMAGE, GNSS_BOOT_IMAGE, GNSS_FIRM_IMAGE) + for path in images: + if not path.is_file(): + fail(f"system image does not exist: {path}") + + # bootloader=88 KiB; system begins with ptable=4 KiB and rd=16 KiB. + system_end = (88 + 4 + 16) * 1024 + for path in images: + system_end = align_up(system_end, 4096) + system_end += path.stat().st_size + system_end = align_up(system_end, 4096) + + margin = EXPECTED["app_start"] - system_end + if margin < MINIMUM_SYSTEM_MARGIN: + fail( + f"system ends at 0x{system_end:08X}; margin before customer_app is " + f"{margin} bytes, expected at least {MINIMUM_SYSTEM_MARGIN}" + ) def verify_fota_contract() -> None: @@ -143,12 +167,12 @@ def verify_fota_contract() -> None: if symbol not in api: fail(f"SDK FULLOTA2 API {symbol} is missing") identity = BASELINE_HEADER.read_text(encoding="utf-8") - if "eg800akcn_11lg-fs640-app2c2000" not in identity: + if "eg800akcn_11lg-hxgnss-fs576-app2e5000" not in identity: fail("application layout identity is missing") generated = BUILD_MACRO.read_text(encoding="utf-8", errors="replace") - if '#define CUSTOM_APP_OTA_LAYOUT_ID "eg800akcn_11lg-fs640-app2c2000"' not in generated: + if '#define CUSTOM_APP_OTA_LAYOUT_ID "eg800akcn_11lg-hxgnss-fs576-app2e5000"' not in generated: fail("generated build macro has the wrong OTA layout identity") - if '#define CUSTOM_APP_OTA_BUILD_PROFILE "ota_ready_fs640"' not in generated: + if '#define CUSTOM_APP_OTA_BUILD_PROFILE "ota_ready_hxgnss_fs576"' not in generated: fail("generated build macro has the wrong OTA build profile") makefile = APP_MAKEFILE.read_text(encoding="utf-8", errors="replace") @@ -200,6 +224,7 @@ def verify_app(path: Path) -> None: def main() -> int: parser = argparse.ArgumentParser() parser.add_argument("--app-bin", type=Path, help="also enforce the application/FBF capacity gate") + parser.add_argument("--kernel-bin", type=Path, help="also enforce the GNSS system/customer_app margin") args = parser.parse_args() verify_layout() verify_linker() @@ -207,7 +232,9 @@ def main() -> int: verify_fota_contract() if args.app_bin: verify_app(args.app_bin if args.app_bin.is_absolute() else ROOT / args.app_bin) - print("OTA-ready baseline verified: fs640, app@0x002C2000, FULLOTA2 available") + if args.kernel_bin: + verify_system_fit(args.kernel_bin if args.kernel_bin.is_absolute() else ROOT / args.kernel_bin) + print("OTA-ready baseline verified: hxgnss fs576, app384@0x002E5000, FULLOTA2 available") return 0