external_bootup
DUT 外部启动流程文档
概述
本文档基于 tcpe3_external_bootup.txt 日志文件和相关代码分析,详细梳理了 DUT(Device Under Test)从外部 Flash 冷启动到 Radio 应用完全就绪的完整流程。
核心理解:整个外部启动流程中,电源控制的对象始终是 DUT(无线电设备),而不是接口板。接口板(NIB/TCPE3)是独立供电、始终在线的设备,它只负责转发命令和控制 Flash 模式切换。
用户操作步骤总览
整个外部启动流程从用户视角可以分为以下几个大步骤:
步骤1: DUT断电 + 接口板切换到外部Flash模式 + DUT上电
步骤2: DUT从外部Flash自动启动(BootROM → Linux,无需干预)
步骤3: 等待DUT启动完成,登录DUT
步骤4: 配置DUT网络,建立SSH连接
步骤5: 接口板切换到内部Flash模式(DUT不断电,热切换)
步骤6: DUT上执行 flash-hotswap internal + prepare_onboard_flash
步骤7: 启动Radio应用(mamastart + waitradiohal)
关键区别:步骤1 是"冷切换"(需要断DUT电),步骤5 是"热切换"(DUT保持运行)。
物理连接拓扑
┌──────────┐ GPIB/TCPIP ┌──────────────┐ DC 56V ┌─────────┐
│ 测试PC │ ──────────────────→ │ 电源控制器 │ ──────────────→ │ DUT │
│ │ │ (GPIB addr:5)│ │(hawkowl)│
│ │ └──────────────┘ │ │
│ │ │ │
│ │ Telnet:3000 ┌──────────────┐ Flash总线 │ │
│ │ ──────────────────→ │ 接口板 │ ←─────────────→ │ │
│ │ (控制端口) │ (TCPE3/NIB) │ (ext/int) │ │
│ │ │ 独立供电 │ │ │
│ │ Telnet:3001 │ 始终在线 │ 串口(UART) │ │
│ │ ──────────────────→ │ │ ←─────────────→ │ │
│ │ (串口转发) │ │ │ │
│ │ └──────────────┘ │ │
│ │ │ │
│ │ SSH:22 (配好IP后直连) │ │
│ │ ─────────────────────────────────────────────────────→│ │
│ │ (文件传输) │ │
└──────────┘ └─────────┘
连接说明:
- 电源控制器 → DUT:直接给 DUT 供电(56V DC),控制 DUT 上电/断电
- 接口板(独立供电,始终在线):
- 控制 DUT 从外部还是内部 Flash 启动(Flash 总线切换)
- 将 DUT 的物理串口通过 Telnet 端口暴露给测试 PC
- 测试PC → DUT(SSH):DUT 配好 IP 后直连,用于 SCP 文件传输
详细流程分析
步骤1:DUT 断电 + 切换到外部 Flash 模式 + DUT 上电
用户操作:通过电源控制器断开 DUT 电源 → 通过接口板切换 Flash 模式为外部 → 给 DUT 上电。
对应代码: tcpe3.py - tcpe_switch_2_external_flash()
def tcpe_switch_2_external_flash(power_supply_com, interface_board_connection, logger):
power_supply_com.power_off() # 1. 断开DUT电源
sleep(2)
# must power off first, then switch, power on at last
tcpe_swtich_to_external_flash(logger, interface_board_connection) # 2. 接口板切换到外部Flash
power_supply_com.power_on() # 3. 给DUT上电
NIB 接口板版本:
def nib_switch_2_external_flash(power_supply_com, nib_telnet_connection, nib_ssh_connection, logger):
power_supply_com.power_off() # 1. 断开DUT电源
sleep(0.1)
nib_swtich_to_external_flash(nib_telnet_connection, nib_ssh_connection, logger) # 2. NIB切换到外部Flash
power_supply_com.power_on() # 3. 给DUT上电
日志记录(DUT 上电部分):
[w5 SOUR:curr 20] ← 设置DUT电流限制 20A
[w5 SOUR:VOLT 56] ← 设置DUT电压 56V
[w5 OUTP:STAT 1] ← 打开电源输出(DUT上电)
[w5 meas:curr?] ← 测量DUT实际电流
[r5 5.36840E-1] ← DUT实际电流 0.537A(DUT开始工作)
电源控制对象说明:
[w5 ...]中的5是 GPIB 地址,对应产品配置中'addr':'5'- 这个电源直接给 DUT 供电(56V DC)
- 接口板是独立供电的,不受此电源控制
为什么必须先断电再切换?
代码注释:must power off first, then switch, power on at last, or external flash will be written some other content。如果不断电就切换 Flash 模式,可能会导致外部 Flash 被写入错误内容。
接口板发送的命令:
- TCPE3:
bm ext(boot mode external) - NIB: 通过 Telnet/SSH 发送切换命令
步骤2:DUT 自动启动过程(无需用户干预)
DUT 上电后,以下过程自动发生,用户只需等待(约32秒到达 login):
2a. BootROM 启动
H1.0
INFO: BootROM P/N: CAH1611693/7 A
INFO: bootmode: 00000001 ← 外部启动模式(由接口板的 bm ext 决定)
INFO: LCS: 00000005
2b. Flash 容器加载 + 安全验证
INFO: Flash container loaded from: 000C0000, type: 00000001
INFO: Jump to BL2/FSBL P/N: CXC0000000/0
2c. FSBL 启动 + 系统初始化
=== FSBL Start ===
[Runtime 24215 us]
System Watchdog configured
Global Watchdog configured
2d. Slot 选择
SW Selection: Current slot is 1
SW Selection: Continue the boot on this slot (1)
Try booting from slot 1
2e. PBOOT/XCS-CONFIG 验证
PBOOT (rif): CXP2010367/1 R1A844
XCS-CONFIG (sct): EXT2014511/5 R1A393
HBK verified ok / Signature verified / Hash verified Ok
hw profile: hawkowl_x2_air3286
2f. DDR 内存初始化
DDR PLL set to 2800MHz, data rate 5600MHz
Controller initialized / PI initialized / Quick check done
2g. SPL + U-Boot 启动
U-Boot SPL 2021.10-00034-g321698ce817
CHOSEN: boot-target: spi-nor0.1
CHOSEN: boot-mode: ramdisk
2h. Linux 内核启动 + 系统服务
[ 31.995498] setup-debug-serdes.sh: Loading firmware and initializing SGMII SerDes
[ 32.134948] Toggle eth0 interface down/up after SGMII SerDes initialization
步骤3:等待 DUT 启动完成,登录
用户操作:等待 DUT 出现 login 提示符,然后登录 root。
日志记录:
Wind River Linux LTS 24.33 Update 13 hawkowl ttyS0
hawkowl login: root
Welcome to Factory Application for hawkowl
root@hawkowl:~#
对应代码: common/connections.py - TelnetConnection
# 创建 DUT 串口连接时,自动等待 login 提示符并登录
DUT_connection = TelnetConnection(serial_logger,
config.interface_board.ip_addr, # 接口板IP(不是DUT IP)
config.interface_board.serial_telnet_port, # 串口转发端口 3001
f'{config.product.family} login') # 等待 "hawkowl login"
TelnetConnection 的 initialize_connection() 方法会自动等待 login 并发送 "root":
def initialize_connection(self):
for line in self.write("", wait_for=self.expect_str, timeout=180):
if "login" in line:
self.write("root")
break
说明:
- Telnet 连接的目标是接口板(不是 DUT),接口板把 DUT 的物理串口转发为 Telnet 端口
- TCPE3: 连接
192.168.2.71:3001(DUT 串口转发端口) - NIB: 连接
192.168.2.51:3003(DUT 串口转发端口)
步骤4:配置 DUT 网络,建立 SSH 连接
用户操作:给 DUT 配置 IP 地址,建立 SSH 连接用于后续文件传输。
日志记录:
root@hawkowl:~# ifconfig eth0 192.168.1.20
对应代码: common/radio.py - Radio.__bootstrap_ssh()
def __bootstrap_ssh(self):
# 1. 通过串口检查 DUT 网络
outp = self.serial_connection.write("ifconfig eth0")
# 2. 如果没有 IP,通过串口配置
if self.ip not in outp:
if not self.external_boot or self.product.family != 'krypton':
self.serial_connection.write(f"ifconfig eth0 {self.ip}")
self.serial_connection.write("ping -c1 192.168.1.1")
# 3. 建立 SSH 连接(用于后续文件传输)
target_ip = self.interface_board.ip_addr if self.interface_board.type == InterfaceBoardType.NIB else self.ip
client.connect(target_ip, port=22, username='root', password="", timeout=30)
为什么需要 SSH?
- 串口只能发命令,不能传文件
- SSH/SCP 用于传输 DC 文件等大文件到 DUT
- 这是 Radio 类构造函数中
external_boot=True时执行的最后一步,之后 return
步骤5:接口板切换到内部 Flash 模式(热切换)
用户操作:通过接口板将 Flash 模式从外部切换到内部。此时 DUT 不断电,保持运行状态。
对应代码: tcpe3.py - tcpe_swtich_to_internal_flash()
def tcpe_swtich_to_internal_flash(logger, tcpe_telent_connection):
logger.info("switch to internal flash")
tcpe_telent_connection.write("bm int", "COMMAND_OK") # 接口板切换到内部Flash
关键区别:
| 步骤1(冷切换) | 步骤5(热切换) | |
|---|---|---|
| DUT 电源 | 需要断电再上电 | 保持运行,不断电 |
| 目的 | 让 DUT 从外部 Flash 启动 | 让 DUT 能访问内部 Flash |
| 原因 | DUT 启动时需要从外部 Flash 读取固件 | DUT 已启动完成,Linux 在内存中运行,不再依赖外部 Flash |
步骤6:DUT 上执行 Flash 热切换和板载 Flash 准备
用户操作:在 DUT 上执行一系列命令,让 DUT 识别并准备内部 Flash。
对应代码: tcpe3.py - tcpe_switch_2_internal_flash()
def tcpe_switch_2_internal_flash(interface_board_connection, DUT_connection, radio, loading_file_path):
# 1. 获取当前硬件信息(切换前)
DUT_connection.write("parget --flat /sys/hw/")
DUT_connection.write("cat /proc/mtd")
DUT_connection.write("getenv | grep mtd")
# 2. 接口板切换到内部Flash(步骤5)
tcpe_swtich_to_internal_flash(radio.logger, interface_board_connection)
# 3. 通过SSH传输 DC 文件到 DUT
dc_file_name = get_dc_files(f"{loading_file_path}/pis")
radio.transfer_file(dc_file_name, f"/tmp/{os.path.basename(dc_file_name)}")
# 4. DUT 上执行 flash-hotswap(让Linux识别内部Flash)
DUT_connection.write("flash-hotswap internal")
# 5. 准备板载Flash(根据产品型号创建分区)
DUT_connection.write(f'prepare_onboard_flash "{radio.product.production_number}" "{radio.product.r_state}"')
# 6. 加载MTD环境变量
DUT_connection.write("cat /proc/mtd")
DUT_connection.write("source /run/env/mtdenv")
DUT_connection.reconnect()
DUT_connection.write("getenv | grep mtd")
# 7. hawkowl 产品:写入数据库到Flash
if radio.get_family_name() == 'hawkowl':
DUT_connection.write("dbtool -w -t radiodb")
DUT_connection.write("dbtool -w -t ppar")
日志记录:
root@hawkowl:~# flash-hotswap internal
[ 273.916682] spi-nor spi100.1: unrecognized JEDEC id bytes: 00 00 00 00 00 00
root@hawkowl:~# prepare_onboard_flash "KRD 901 316/2" "R1A"
KRD 901 316/2 R1A matches XCS_CONFIG_CXC2014511_5_P1C41
Creating FLASH partitions according to XCS-config...
root@hawkowl:~# dbtool -w -t radiodb
Radio DB size 283020
SHA256 of radiodb bc086d3c40618db12c0db19965fb1ca7a6faa8de2132bd8d626bd1cb77f53428
root@hawkowl:~# dbtool -w -t ppar
PPAR size 1624
SHA256 of ppar 5747481321ef9fa5783d35fb107a2a9491d15dd597b035afc72cb4bddae026b9
各命令作用:
| 命令 | 作用 |
|---|---|
flash-hotswap internal |
让 Linux 内核重新扫描并识别内部 Flash 设备 |
prepare_onboard_flash "KRD 901 316/2" "R1A" |
根据产品型号和 XCS-Config 创建 Flash 分区表 |
source /run/env/mtdenv |
加载 MTD 环境变量 |
dbtool -w -t radiodb |
写入 Radio 数据库到 Flash |
dbtool -w -t ppar |
写入 PPAR(产品参数)到 Flash |
步骤7:启动 Radio 应用
用户操作:执行 mamastart 启动 Radio 应用,等待 HAL is ready。
日志记录:
root@hawkowl:~# parget --flat /sys/hw/
/sys/hw/pid str "KRD 901 316/2"
/sys/hw/profile str "hawkowl_x2_air3286"
/sys/hw/serial str "EA8D726158"
...
root@hawkowl:~# mamastart
...
[ 1159.052307] radioapp.elf[14429]: Etsw Grpc Server listening on 0.0.0.0:50053
HAL is ready
AIR3286 SW started
Starting O&M interfaces
对应代码: common/radio.py - Radio.startup_mama()
def startup_mama(self):
self.serial_connection.write("refresh_pid") # 刷新产品ID
self.serial_connection.write("db list /freqClassUsage") # 查询频率配置
self.serial_connection.write("cid") # 查询组件ID
self.serial_connection.write("parget --flat /sys/hw/") # 获取硬件信息
self.serial_connection.write("mamastart") # 启动Radio应用
self.serial_connection.write("waitradiohal", wait_for='HAL is ready') # 等待HAL就绪
启动成功标志:串口输出中出现 HAL is ready。
完整流程时序图
时间轴
│
│ ┌─────────────────────────────────────────────────────────────┐
│ │ 步骤1: DUT断电 + 接口板切换外部Flash + DUT上电 │
│ │ │
│ │ 电源控制器 ──→ DUT断电 (OUTP:STAT 0) │
│ │ 测试PC ──→ 接口板 ──→ "bm ext" (切换到外部Flash) │
│ │ 电源控制器 ──→ DUT上电 (SOUR:VOLT 56, OUTP:STAT 1) │
│ └─────────────────────────────────────────────────────────────┘
│
│ ┌─────────────────────────────────────────────────────────────┐
│ │ 步骤2: DUT自动启动(无需干预,约32秒) │
│ │ │
│ │ BootROM → FSBL → DDR初始化 → SPL → U-Boot → Linux │
│ └─────────────────────────────────────────────────────────────┘
│
│ ┌─────────────────────────────────────────────────────────────┐
│ │ 步骤3: 登录DUT │
│ │ │
│ │ 等待 "hawkowl login:" → 输入 "root" │
│ └─────────────────────────────────────────────────────────────┘
│
│ ┌─────────────────────────────────────────────────────────────┐
│ │ 步骤4: 配置网络 + 建立SSH │
│ │ │
│ │ 串口 → "ifconfig eth0 192.168.1.20" │
│ │ 测试PC → SSH连接DUT (用于后续文件传输) │
│ └─────────────────────────────────────────────────────────────┘
│
│ ┌─────────────────────────────────────────────────────────────┐
│ │ 步骤5: 接口板切换内部Flash(热切换,DUT不断电) │
│ │ │
│ │ 测试PC ──→ 接口板 ──→ "bm int" (切换到内部Flash) │
│ └─────────────────────────────────────────────────────────────┘
│
│ ┌─────────────────────────────────────────────────────────────┐
│ │ 步骤6: DUT上准备内部Flash │
│ │ │
│ │ SCP传输DC文件 → flash-hotswap internal → │
│ │ prepare_onboard_flash → dbtool写入数据库 │
│ └─────────────────────────────────────────────────────────────┘
│
│ ┌─────────────────────────────────────────────────────────────┐
│ │ 步骤7: 启动Radio应用 │
│ │ │
│ │ mamastart → waitradiohal → "HAL is ready" ✓ │
│ └─────────────────────────────────────────────────────────────┘
│
▼ DUT完全就绪,可以执行RF校准测试
为什么需要外部启动?
外部启动的场景是:DUT 的内部 Flash 是空的或损坏的。
正常情况下,DUT 从内部 Flash 启动。但在以下场景需要外部启动:
- 全新设备:内部 Flash 没有任何固件
- Flash 损坏:内部 Flash 数据损坏,无法正常启动
- 重新烧录:需要重新初始化内部 Flash 的分区和数据
外部启动的本质是:
- 先从外部 Flash 启动一个最小 Linux 系统(外部 Flash 上有预烧好的固件)
- 在这个系统中,通过
flash-hotswap+prepare_onboard_flash初始化内部 Flash - 然后在内部 Flash 上创建分区、写入数据库
- 最后启动 Radio 应用
类比:就像用 U 盘启动电脑来修复硬盘一样。外部 Flash 就是"U 盘",内部 Flash 就是"硬盘"。
关键发现和问题分析
1. 电源控制对象
- 电源控制的是 DUT,不是接口板
- 接口板独立供电,始终在线
- 日志中
[w5 ...]的 GPIB 地址 5 对应 DUT 电源
2. 两次上电
- 第一次上电(步骤1):DUT 从外部 Flash 启动
- 无第二次断电/上电:步骤5 的 Flash 切换是热切换,DUT 保持运行
3. 产品识别问题
- 日志显示第一次使用
KRC 901 316/2失败,第二次使用KRD 901 316/2成功 - 产品配置中应使用正确的产品 ID
4. JEDEC ID 识别问题
flash-hotswap internal时出现 "unrecognized JEDEC id bytes: 00 00 00 00 00 00"- 可能是内部 Flash 设备未正确识别或需要多次尝试
5. 启动时间分析
| 阶段 | 时间 | 说明 |
|---|---|---|
| DUT 上电到 Linux 登录 | ~32秒 | 自动过程 |
| Flash 切换和准备 | ~10分钟 | 包含文件传输和分区创建 |
| Radio 应用启动 | ~3分钟 | mamastart 到 HAL ready |
| 总时间 | ~15-20分钟 | 完整外部启动流程 |
附录
相关代码文件
external_boot_faap_via_tcpe3.py- TCPE3 接口板外部启动主程序bootup_dut_from_external_nib.py- NIB 接口板外部启动主程序common/radio.py- Radio 类,管理 DUT 启动common/tcpe3.py- TCPE3 接口板控制common/nib.py- NIB 接口板通信common/power_supply.py- 电源控制(控制 DUT 电源)common/connections.py- 连接管理
产品配置示例(AIR3286)
'AIR3286_B25B66': {
'product': {'family': 'hawkowl', 'production_number': 'KRD 901 316/2', 'r_state': 'R1A'},
'interface_board': {'InterfaceBoardType': InterfaceBoardType.TCPE3, 'position': 1},
'power_supply': {'type': PowerSupplyType.DC, 'com_mode': ComMode.GPIB, 'addr': '5', 'vol': 56, 'current': 10},
}
文档版本: 2.0
创建日期: 2026年5月7日
基于日志: tcpe3_external_bootup.txt

浙公网安备 33010602011771号