fix: rebalance HX GNSS OTA partitions

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