概述 Flash 模块是 OpenixCLI 的核心刷写引擎,负责协调 FEL(USB Boot)和 FES(U-Boot)两种模式的固件刷写流程。它处理设备检测、DRAM 初始化、U-Boot 下载、设备重连、分区刷写、Boot 写入等完整流程,是嵌入式固件刷写的典型实现。
模块结构 src/flash/ ├── mod.rs # Flasher 主控制器 ├── fel_handler/ # FEL 模式处理 │ ├── mod.rs # FelHandler 入口 │ ├── dram_init.rs # DRAM 初始化 │ └── uboot_download.rs # U-Boot 下载 └── fes_handler/ # FES 模式处理 ├── mod.rs # FesHandler 入口 ├── constants.rs # 常量定义 ├── erase_flag.rs # Flash 擦除 ├── mbr_download.rs # MBR 写入 ├── ubifs_config.rs # UBIFS 配置 ├── boot_download.rs # Boot 写入 ├── types.rs # PartitionDownloadInfo └── partition/ # 分区刷写 ├── mod.rs ├── mod_impl.rs ├── raw_download.rs └── sparse_parser.rs
核心数据结构 FlashMode 枚举 #[derive(Debug, Clone, Copy, PartialEq)] pub enum FlashMode { Partition, KeepData, PartitionErase, FullErase, }impl FlashMode { pub fn erase_flag (&self ) -> u32 { match self { FlashMode::Partition => 0x0 , FlashMode::KeepData => 0x0 , FlashMode::PartitionErase => 0x1 , FlashMode::FullErase => 0x12 , } } }
FlashOptions 结构 #[derive(Debug, Clone)] pub struct FlashOptions { pub bus: Option <u8 >, pub port: Option <u8 >, pub verify: bool , pub mode: FlashMode, pub partitions: Option <Vec <String >>, pub post_action: String , }
Flasher 主控制器 pub struct Flasher { packer: OpenixPacker, options: FlashOptions, logger: Logger, }
PartitionDownloadInfo 结构 pub struct PartitionDownloadInfo { pub partition_name: String , pub partition_address: u64 , pub download_filename: String , pub download_subtype: String , pub data_offset: u64 , pub data_length: u64 , }
FEL vs FES 模式对比 设备模式识别 let mode = ctx.get_device_mode ();let has_fel = mode == libefex::DeviceMode::Fel;
流程差异
阶段
FEL 模式
FES 模式
DRAM 初始化
需要
不需要(已在 U-Boot 中)
U-Boot 下载
需要
不需要(已运行)
设备重连
需要
不需要
设备查询
需要
需要
Flash 擦除
可选
可选
MBR 写入
需要
需要
分区刷写
需要
需要
Boot 写入
需要
需要
设备重启
需要
需要
Flasher::execute 主流程 完整刷写流程 pub async fn execute (&mut self ) -> FlashResult<()> { let fes_data = self .packer.get_fes ()?; let mut ctx = self .init_device ()?; let mode = ctx.get_device_mode (); let has_fel = mode == libefex::DeviceMode::Fel; if has_fel { let stages = FlashStages::for_fel_mode(); self .logger.define_stages (stages.stages ()); } else { let stages = FlashStages::for_fes_mode(); self .logger.define_stages (stages.stages ()); } self .logger.start_global_progress (); self .logger.begin_stage (StageType::Init); self .logger.info (&format! ("FES data loaded ({} bytes)" , fes_data.len ())); self .logger.complete_stage (); if has_fel { self .logger.begin_stage (StageType::FelDram); let fel_handler = FelHandler::new (&self .logger); fel_handler.handle (&mut ctx, &fes_data).await ?; self .logger.complete_stage (); self .logger.begin_stage (StageType::FelUboot); let uboot_data = self .packer.get_uboot ()?; let dtb_data = self .packer.get_dtb ().ok (); let sysconfig_data = self .packer.get_sys_config_bin ()?; let board_config_data = self .packer.get_board_config ().ok (); fel_handler.download_uboot ( &ctx, &uboot_data, dtb_data.as_deref (), &sysconfig_data, board_config_data.as_deref (), ).await ?; self .logger.complete_stage (); self .logger.begin_stage (StageType::FelReconnect); ctx = self .reconnect_device ().await ?; self .logger.complete_stage (); } let mut fes_handler = FesHandler::new (&mut self .logger); fes_handler.handle (&ctx, &mut self .packer, &self .options).await ?; self .logger.finish_progress (); self .set_device_mode (&ctx).await ?; Ok (()) }
流程图 flowchart TD
A[开始] --> B[加载 FES 数据]
B --> C[初始化 USB 设备]
C --> D{设备模式?}
D -->|FEL| E[FEL 模式处理]
E --> E1[DRAM 初始化]
E1 --> E2[U-Boot 下载]
E2 --> E3[设备重连]
E3 --> F
D -->|FES| F[FES 模式处理]
F --> F1[设备查询]
F1 --> F2{需要擦除?}
F2 -->|是| F3[Flash 擦除]
F2 -->|否| F4
F3 --> F4[MBR 写入]
F4 --> F5[分区刷写]
F5 --> F6[Boot 写入]
F6 --> G[设置设备模式]
G --> H[结束]
FEL Handler 详解 FelHandler 结构 pub struct FelHandler <'a > { logger: &'a Logger, }impl <'a > FelHandler<'a > { pub fn new (logger: &'a Logger) -> Self { Self { logger } } pub async fn handle ( &self , ctx: &mut libefex::Context, fes_data: &[u8 ], ) -> FlashResult<()> { let dram_init = DramInit::new (self .logger); dram_init.execute (ctx, fes_data).await } pub async fn download_uboot ( &self , ctx: &libefex::Context, uboot_data: &[u8 ], dtb_data: Option <&[u8 ]>, sysconfig_data: &[u8 ], board_config_data: Option <&[u8 ]>, ) -> FlashResult<()> { let uboot_download = UbootDownload::new (self .logger); uboot_download.execute (ctx, uboot_data, dtb_data, sysconfig_data, board_config_data).await } }
DRAM 初始化流程 DRAM 初始化使用 FES 数据(Flash Eraser Script)通过 USB 下载到设备内存:
pub struct DramInit <'a > { logger: &'a Logger, }impl <'a > DramInit<'a > { pub async fn execute ( &self , ctx: &mut libefex::Context, fes_data: &[u8 ], ) -> FlashResult<()> { let boot0_header = Boot0Header::parse (fes_data)?; if boot0_header.magic_str () != BOOT0_MAGIC { return Err (FlashError::DramInitFailed); } let run_addr = boot0_header.run_addr; ctx.fel_download (fes_data, run_addr)?; ctx.fel_run (run_addr)?; self .wait_for_dram_init (ctx).await ?; Ok (()) } }
U-Boot 下载流程 pub struct UbootDownload <'a > { logger: &'a Logger, }impl <'a > UbootDownload<'a > { pub async fn execute ( &self , ctx: &libefex::Context, uboot_data: &[u8 ], dtb_data: Option <&[u8 ]>, sysconfig_data: &[u8 ], board_config_data: Option <&[u8 ]>, ) -> FlashResult<()> { let uboot_header = UBootHeader::parse (uboot_data)?; UBootHeader::set_work_mode (uboot_data, WORK_MODE_USB_PRODUCT); let run_addr = uboot_header.uboot_head.run_addr; let uboot_length = uboot_header.uboot_head.uboot_length; ctx.fel_download (uboot_data, run_addr)?; if let Some (dtb) = dtb_data { let dtb_addr = run_addr + uboot_length; ctx.fel_download (dtb, dtb_addr)?; } let sysconfig_addr = run_addr + uboot_length + dtb_data.len (); ctx.fel_download (sysconfig_data, sysconfig_addr)?; if let Some (board_config) = board_config_data { let board_addr = sysconfig_addr + sysconfig_data.len (); ctx.fel_download (board_config, board_addr)?; } ctx.fel_run (run_addr)?; Ok (()) } }
内存布局 ┌─────────────────────────────────────────────────────────────┐ │ 设备内存布局 │ ├─────────────────────────────────────────────────────────────┤ │ run_addr │ │ ┌─────────────────────────────────────────────────────────┐ │ │ │ U-Boot (uboot_length bytes) │ │ │ └─────────────────────────────────────────────────────────┘ │ │ run_addr + uboot_length │ │ ┌─────────────────────────────────────────────────────────┐ │ │ │ DTB (variable size) │ │ │ └─────────────────────────────────────────────────────────┘ │ │ run_addr + uboot_length + dtb_len │ │ ┌─────────────────────────────────────────────────────────┐ │ │ │ sys_config_bin (variable size) │ │ │ └─────────────────────────────────────────────────────────┘ │ │ run_addr + uboot_length + dtb_len + sysconfig_len │ │ ┌─────────────────────────────────────────────────────────┐ │ │ │ board_config (optional) │ │ │ └─────────────────────────────────────────────────────────┘ │ └─────────────────────────────────────────────────────────────┘
设备重连机制 async fn reconnect_device (&self ) -> FlashResult<libefex::Context> { tokio::time::sleep (Duration::from_secs (2 )).await ; let max_retries = 25 ; let mut retries = 0 ; while retries < max_retries { tokio::time::sleep (Duration::from_secs (1 )).await ; let devices = libefex::Context::scan_usb_devices ()?; for dev in devices { let mut new_ctx = libefex::Context::new (); if new_ctx.scan_usb_device_at (dev.bus, dev.port).is_err () { continue ; } if new_ctx.usb_init ().is_err () { continue ; } if new_ctx.efex_init ().is_err () { continue ; } if new_ctx.get_device_mode () == libefex::DeviceMode::Srv { self .logger.debug (&format! ( "Device found at bus {}, port {}" , dev.bus, dev.port )); return Ok (new_ctx); } } retries += 1 ; self .logger.debug (&format! ("Reconnect attempt {}/{}" , retries, max_retries)); } Err (FlashError::ReconnectFailed) }
FES Handler 详解 FesHandler 结构 pub struct FesHandler <'a > { logger: &'a mut Logger, }impl <'a > FesHandler<'a > { pub fn new (logger: &'a mut Logger) -> Self { Self { logger } } pub async fn handle ( &mut self , ctx: &libefex::Context, packer: &mut OpenixPacker, options: &FlashOptions, ) -> FlashResult<()> { self .logger.begin_stage (StageType::FesQuery); let secure = ctx.fes_query_secure ()?; let storage_type = ctx.fes_query_storage ()?; let flash_size = ctx.fes_probe_flash_size ()?; self .logger.complete_stage (); if options.mode != FlashMode::Partition { self .logger.begin_stage (StageType::FesErase); let erase_flag = EraseFlag::new (&*self .logger); erase_flag.execute (ctx, options.mode).await ?; self .logger.complete_stage (); } self .logger.begin_stage (StageType::FesMbr); let mbr_data = packer.get_mbr ()?; let mbr = SunxiMbr::parse (&mbr_data)?; let download_list = self .prepare_partition_download_list (packer, &mbr_info, options)?; let ubifs_config = UbifsConfig::new (&*self .logger); ubifs_config.execute (ctx, packer, &download_list, storage_type)?; let mbr_download = MbrDownload::new (&*self .logger); mbr_download.execute (ctx, &mbr_data).await ?; self .logger.complete_stage (); if !download_list.is_empty () { self .logger.begin_stage (StageType::FesPartitions); let total_bytes : u64 = download_list.iter ().map (|p| p.data_length).sum (); self .logger.set_partition_stage_weight (total_bytes); let mut partition_download = PartitionDownload::new (&mut *self .logger); partition_download.execute (ctx, packer, &download_list, options.verify).await ?; self .logger.complete_stage (); self .logger.begin_stage (StageType::FesBoot); let boot_download = BootDownload::new (&*self .logger); boot_download.execute (ctx, packer, secure, storage_type).await ?; self .logger.complete_stage (); } Ok (()) } }
分区下载列表准备 fn prepare_partition_download_list ( &self , packer: &mut OpenixPacker, mbr_info: &MbrInfo, options: &FlashOptions, ) -> FlashResult<Vec <PartitionDownloadInfo>> { let mut partition_parser = OpenixPartition::new (); if let Ok (data) = packer.get_sys_partition () { partition_parser.parse_from_data (&data); } let mut download_list = Vec ::new (); for mbr_partition in &mbr_info.partitions { let partition_name = &mbr_partition.name; if options.mode == FlashMode::KeepData { let name_lower = partition_name.to_lowercase (); if name_lower == "udisk" || name_lower == "private" || name_lower == "reserve" { self .logger.info (&format! ("Skipping user data partition: {}" , partition_name)); continue ; } } if options.mode == FlashMode::Partition { if let Some (ref partitions) = options.partitions { if !partitions.iter ().any (|p| p == partition_name) { continue ; } } } let config_partition = config_partitions.iter ().find (|p| p.name == *partition_name); let download_filename = config_partition?.downloadfile.clone (); let download_subtype = packer.build_subtype_by_filename (&download_filename); let data_info = packer .get_file_info_by_maintype_subtype ("ITEM_ROOTFSFAT16" , &download_subtype) .or_else (|| packer.get_file_info_by_maintype_subtype ("12345678" , &download_subtype)) .or_else (|| packer.get_file_info_by_filename (&download_filename)); if let Some ((offset, length)) = data_info { download_list.push (PartitionDownloadInfo { partition_name: partition_name.clone (), partition_address: mbr_partition.address (), download_filename, download_subtype, data_offset: offset, data_length: length, }); } } Ok (download_list) }
分区刷写流程 Raw vs Sparse 格式检测 pub struct PartitionDownload <'a > { logger: &'a mut Logger, }impl <'a > PartitionDownload<'a > { pub async fn execute ( &mut self , ctx: &libefex::Context, packer: &mut OpenixPacker, download_list: &[PartitionDownloadInfo], verify: bool , ) -> FlashResult<()> { for partition_info in download_list { self .logger.set_current_partition (&partition_info.partition_name); let partition_data = packer.read_data_at_offset ( partition_info.data_offset as u32 , partition_info.data_length as u32 , )?; if is_sparse_format (&partition_data) { self .download_sparse (ctx, &partition_info, &partition_data, verify).await ?; } else { self .download_raw (ctx, &partition_info, &partition_data, verify).await ?; } } Ok (()) } }
Raw 格式下载 async fn download_raw ( &self , ctx: &libefex::Context, partition_info: &PartitionDownloadInfo, data: &[u8 ], verify: bool , ) -> FlashResult<()> { let address = partition_info.partition_address; let length = data.len (); let chunk_size = 8 * 1024 ; let mut offset = 0 ; while offset < length { let chunk_end = (offset + chunk_size).min (length); let chunk = &data[offset..chunk_end]; ctx.fes_write (address + offset as u64 , chunk)?; self .logger.update_progress_with_speed (chunk_end as u64 ); offset = chunk_end; } if verify { self .verify_partition (ctx, partition_info, data)?; } Ok (()) }
Sparse 格式下载 async fn download_sparse ( &self , ctx: &libefex::Context, partition_info: &PartitionDownloadInfo, data: &[u8 ], verify: bool , ) -> FlashResult<()> { let header = SparseHeader::parse (data)?; let base_address = partition_info.partition_address; let mut chunk_offset = SPARSE_HEADER_SIZE; let mut output_offset = 0u64 ; for _ in 0 ..header.total_chunks { let chunk_header = ChunkHeader::parse (&data[chunk_offset..])?; match chunk_header.chunk_type { CHUNK_TYPE_RAW => { let chunk_data = &data[chunk_offset + CHUNK_HEADER_SIZE..]; let write_address = base_address + output_offset * SECTOR_SIZE; ctx.fes_write (write_address, chunk_data)?; self .logger.update_progress_with_speed (output_offset); } CHUNK_TYPE_FILL => { let fill_value = u32 ::from_le_bytes ([ data[chunk_offset + CHUNK_HEADER_SIZE], data[chunk_offset + CHUNK_HEADER_SIZE + 1 ], data[chunk_offset + CHUNK_HEADER_SIZE + 2 ], data[chunk_offset + CHUNK_HEADER_SIZE + 3 ], ]); ctx.fes_write_fill (base_address + output_offset * SECTOR_SIZE, fill_value, chunk_header.chunk_sz)?; } CHUNK_TYPE_DONT_CARE => { } CHUNK_TYPE_CRC32 => { } } output_offset += chunk_header.chunk_sz as u64 ; chunk_offset += chunk_header.total_sz as usize ; } Ok (()) }
Boot 写入流程 pub struct BootDownload <'a > { logger: &'a Logger, }impl <'a > BootDownload<'a > { pub async fn execute ( &self , ctx: &libefex::Context, packer: &mut OpenixPacker, secure: u32 , storage_type: u32 , ) -> FlashResult<()> { let boot_data = match secure { BOOT_FILE_MODE_PKG => { packer.get_image_data_by_name ("bootpkg" )? } BOOT_FILE_MODE_NORMAL => { match StorageType::from (storage_type) { StorageType::Spinor => packer.get_image_data_by_name ("boot0_nor" )?, _ => packer.get_image_data_by_name ("boot0_card" )?, } } _ => return Ok (()), }; ctx.fes_write_boot (boot_data)?; self .logger.info (&format! ("Boot written ({} bytes)" , boot_data.len ())); Ok (()) } }
设备模式设置 async fn set_device_mode (&self , ctx: &libefex::Context) -> FlashResult<()> { let tool_mode = match self .options.post_action.as_str () { "reboot" => libefex::FesToolMode::Reboot, "poweroff" => libefex::FesToolMode::PowerOff, "shutdown" => libefex::FesToolMode::PowerOff, _ => libefex::FesToolMode::Reboot, }; ctx.fes_tool_mode (libefex::FesToolMode::Normal, tool_mode)?; Ok (()) }
代码亮点 1. 异步重连机制 async fn reconnect_device (&self ) -> FlashResult<libefex::Context> { tokio::time::sleep (Duration::from_secs (2 )).await ; let max_retries = 25 ; while retries < max_retries { tokio::time::sleep (Duration::from_secs (1 )).await ; } }
2. 模式动态选择 if has_fel { let stages = FlashStages::for_fel_mode(); } else { let stages = FlashStages::for_fes_mode(); }
3. 分区过滤逻辑 if options.mode == FlashMode::KeepData { if name_lower == "udisk" || name_lower == "private" { continue ; } }if options.mode == FlashMode::Partition { if !partitions.iter ().any (|p| p == partition_name) { continue ; } }
4. Sparse 格式块处理 match chunk_header.chunk_type { CHUNK_TYPE_RAW => ctx.fes_write (address, chunk_data)?, CHUNK_TYPE_FILL => ctx.fes_write_fill (address, fill_value, size)?, CHUNK_TYPE_DONT_CARE => { }, CHUNK_TYPE_CRC32 => { }, }
完整刷写流程图 sequenceDiagram
participant Flasher as Flasher
participant FEL as FelHandler
participant FES as FesHandler
participant Device as USB Device
Flasher->>Flasher: 加载 FES 数据
Flasher->>Device: 初始化 USB
Device-->>Flasher: 设备模式
alt FEL 模式
Flasher->>FEL: handle(DRAM init)
FEL->>Device: 下载 FES
FEL->>Device: 执行 FES
Device-->>FEL: DRAM 就绪
FEL-->>Flasher: Ok
Flasher->>FEL: download_uboot
FEL->>Device: 下载 U-Boot
FEL->>Device: 下载 DTB
FEL->>Device: 下载 sys_config
FEL->>Device: 执行 U-Boot
FEL-->>Flasher: Ok
Flasher->>Flasher: reconnect_device
Note over Flasher: 等待 FES 模式
Flasher->>Device: 扫描 USB
Device-->>Flasher: FES 模式设备
end
Flasher->>FES: handle
FES->>Device: 查询设备信息
Device-->>FES: secure/storage/flash_size
alt 需要擦除
FES->>Device: 擦除 Flash
end
FES->>Device: 写入 MBR
loop 每个分区
FES->>FES: 检测 Sparse/Raw
alt Raw 格式
FES->>Device: 直接写入
else Sparse 格式
FES->>Device: 逐块写入
end
end
FES->>Device: 写入 Boot
FES-->>Flasher: Ok
Flasher->>Device: 设置模式(reboot/poweroff)
Device-->>Flasher: Ok