<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
  <author>
    <name>柚木 鉉</name>
  </author>
  <generator uri="https://hexo.io/">Hexo</generator>
  <id>https://gloomyghost.com/</id>
  <link href="https://gloomyghost.com/" rel="alternate"/>
  <link href="https://gloomyghost.com/atom.xml" rel="self"/>
  <rights>All rights reserved 2026, 柚木 鉉</rights>
  <subtitle>Ne me plaignez pas C'est pour cela que je suis née</subtitle>
  <title>柚木鉉の空間</title>
  <updated>2026-05-29T20:33:31.220Z</updated>
  <entry>
    <author>
      <name>柚木 鉉</name>
    </author>
    <category term="Rust" scheme="https://gloomyghost.com/tags/Rust/"/>
    <category term="OpenixCLI" scheme="https://gloomyghost.com/tags/OpenixCLI/"/>
    <category term="重构" scheme="https://gloomyghost.com/tags/%E9%87%8D%E6%9E%84/"/>
    <content>
      <![CDATA[<h2 id="从一个-README-错误说起" data-id="从一个-README-错误说起" class="notion-h"><a href="#从一个-README-错误说起" class="headerlink" title="从一个 README 错误说起"></a>从一个 README 错误说起</h2><p>改这个之前，README 里有个例子一直跑不通：</p><figure class="highlight bash"><table><tbody><tr><td class="code"><pre><code class="hljs bash">openixcli flash firmware.img --verify <span class="hljs-literal">false</span><br></code></pre></td></tr></tbody></table></figure><p><code>--verify</code> 在 clap 里是个 flag，<code>false</code> 会被当成多余参数报错。问题不大，但修着修着发现类似的”小问题”还有一堆——<code>FlashMode</code> 定义了两份、<code>post_action</code> 用字符串传来传去、CLI 和 TUI 各自加载固件……</p><p>单个看都是修修补补能解决的事，但凑一起就让人想动刀子了。于是有了这个提交：</p><figure class="highlight plaintext"><table><tbody><tr><td class="code"><pre><code class="hljs plaintext">f0ce24a Refactor flash request flow<br></code></pre></td></tr></tbody></table></figure><h2 id="改动前长什么样" data-id="改动前长什么样" class="notion-h"><a href="#改动前长什么样" class="headerlink" title="改动前长什么样"></a>改动前长什么样</h2><p>先看张图：</p><pre class="mermaid">flowchart TD    CLI[CLI flash args] --&gt; Main[main.rs 解析 mode/partitions/post_action]    Main --&gt; CmdFlash[commands::flash::execute]    CmdFlash --&gt; CmdMode[commands::FlashMode]    CmdMode --&gt; ConvertMode[转换为 flash::FlashMode]    TUI[TUI FirmwareState] --&gt; Bridge[tui::bridge::run_flash]    Bridge --&gt; TuiConvert[转换 CmdFlashMode 到 flash::FlashMode]    CmdFlash --&gt; PackerA[OpenixPacker 加载固件]    Bridge --&gt; PackerB[OpenixPacker 加载固件和分区名]    ConvertMode --&gt; Options[FlashOptions]    TuiConvert --&gt; Options    Options --&gt; Flasher[Flasher::execute]    Flasher --&gt; GlobalProgress[GlobalProgress 全局进度]    GlobalProgress --&gt; CLIProgress[CLI indicatif 进度条]    GlobalProgress --&gt; TUIPoll[TUI 轮询 snapshot]    Flasher --&gt; FES[FesHandler::handle]    FES --&gt; InlinePlanner[内联生成分区下载列表]    FES --&gt; PartitionDownload[写入分区]    TerminalGlobal[terminal 全局 TUI log sender] --&gt; TUILog[TUI 日志窗口]</pre><p>几个比较烦人的点：</p><ol><li><strong>类型转来转去</strong> —— <code>FlashMode</code> 在命令层一份、刷机层一份，TUI 还要再转一次</li><li><strong>字符串到处跑</strong> —— <code>post_action</code> 就是个 <code>String</code>，写错了运行时才知道</li><li><strong>逻辑重复</strong> —— CLI 和 TUI 都在读 MBR、提取分区信息</li><li><strong>全局状态到处飞</strong> —— TUI 日志靠全局 channel，进度靠轮询 <code>GlobalProgress</code></li><li><strong>职责边界模糊</strong> —— FES handler 又管协议又管分区计划</li></ol><p>改之前也没想太多，就是觉得每次加功能都要在三个地方补转换逻辑，心累。</p><h2 id="改完之后" data-id="改完之后" class="notion-h"><a href="#改完之后" class="headerlink" title="改完之后"></a>改完之后</h2><pre class="mermaid">flowchart TD    CLI[CLI flash args] --&gt; SharedRequest[FlashRequest]    TUI[TUI FirmwareState] --&gt; SharedRequest    CLI --&gt; LoadedFirmware[LoadedFirmware::load]    TUI --&gt; LoadedFirmware    LoadedFirmware --&gt; Packer[OpenixPacker]    LoadedFirmware --&gt; Metadata[image info + partition names]    SharedRequest --&gt; Flasher[Flasher]    Packer --&gt; Flasher    Flasher --&gt; OpenDevice[打开指定设备或首个设备]    OpenDevice --&gt; DetectMode[检测 FEL/FES]    DetectMode --&gt;|FEL| FelPrep[prepare_fel_mode]    FelPrep --&gt; Reconnect[重连到 FES]    Reconnect --&gt; FesRun[run_fes_mode]    DetectMode --&gt;|FES| FesRun    FesRun --&gt; FesHandler[FesHandler::handle]    FesHandler --&gt; Planner[PartitionPlanner]    Planner --&gt; DownloadList[PartitionDownloadInfo list]    DownloadList --&gt; PartitionDownload[写入 raw/sparse 分区]    Flasher --&gt; PostAction[强类型 PostAction]    PostAction --&gt; DeviceMode[设置 reboot/poweroff/shutdown]    Flasher --&gt; Logger[Logger]    Logger --&gt; FlashEvent[FlashEvent]    FlashEvent --&gt; CLIOutput[CLI 终端输出/进度条]    FlashEvent --&gt; TUIChannel[TUI app channel]    TUIChannel --&gt; TUIState[TUI UI state]</pre><p>主线清晰多了：两个入口都往 <code>FlashRequest</code> 填数据，固件加载走统一入口，刷机过程通过事件对外广播。TUI 不用再轮询全局状态，直接消费事件就行。</p><h2 id="具体改了什么" data-id="具体改了什么" class="notion-h"><a href="#具体改了什么" class="headerlink" title="具体改了什么"></a>具体改了什么</h2><h3 id="强类型请求模型" data-id="强类型请求模型" class="notion-h"><a href="#强类型请求模型" class="headerlink" title="强类型请求模型"></a>强类型请求模型</h3><p>新建了 <code>src/flash/request.rs</code>，把这几个东西放一起：</p><ul><li><code>FlashMode</code></li><li><code>PostAction</code>（原来的 <code>post_action</code> 字符串）</li><li><code>DeviceSelector</code></li><li><code>FlashRequest</code></li></ul><p>最爽的是 <code>PostAction</code> 终于有类型了：</p><figure class="highlight rust"><table><tbody><tr><td class="code"><pre><code class="hljs rust"><span class="hljs-keyword">let</span> <span class="hljs-variable">tool_mode</span> = <span class="hljs-keyword">self</span>.request.post_action.<span class="hljs-title function_ invoke__">fes_tool_mode</span>();<br></code></pre></td></tr></tbody></table></figure><p>非法值在参数解析阶段就会报错，不用等到刷机收尾才发现用户写了 <code>"rebot"</code> 而不是 <code>"reboot"</code>。</p><h3 id="固件加载归一" data-id="固件加载归一" class="notion-h"><a href="#固件加载归一" class="headerlink" title="固件加载归一"></a>固件加载归一</h3><p><code>LoadedFirmware</code> 把之前散落的逻辑收拢了：</p><ul><li>打包 <code>OpenixPacker</code></li><li>读取镜像信息</li><li>从 MBR 提分区名</li></ul><p>CLI 用它打印固件信息，TUI 用它填分区选择界面。不用两边各自写一遍读 MBR 的代码了。</p><h3 id="刷机流程拆阶段" data-id="刷机流程拆阶段" class="notion-h"><a href="#刷机流程拆阶段" class="headerlink" title="刷机流程拆阶段"></a>刷机流程拆阶段</h3><p><code>Flasher::execute</code> 之前是一坨，现在拆成了：</p><figure class="highlight rust"><table><tbody><tr><td class="code"><pre><code class="hljs rust"><span class="hljs-title function_ invoke__">open_device</span>()<br><span class="hljs-title function_ invoke__">prepare_fel_mode</span>()  <span class="hljs-comment">// 如果设备在 FEL 模式</span><br><span class="hljs-title function_ invoke__">run_fes_mode</span>()<br><span class="hljs-title function_ invoke__">apply_post_action</span>()<br></code></pre></td></tr></tbody></table></figure><p>读起来像状态机，不像流水账了。</p><h3 id="分区计划器独立出来" data-id="分区计划器独立出来" class="notion-h"><a href="#分区计划器独立出来" class="headerlink" title="分区计划器独立出来"></a>分区计划器独立出来</h3><p><code>PartitionPlanner</code> 负责：</p><ul><li>读 MBR 分区表</li><li>读 <code>sys_partition</code> 配置</li><li>根据 <code>FlashMode</code> 和用户选择过滤分区</li><li>生成下载列表</li></ul><p>FES handler 现在只管协议步骤，不用再夹带私货。</p><h3 id="事件驱动" data-id="事件驱动" class="notion-h"><a href="#事件驱动" class="headerlink" title="事件驱动"></a>事件驱动</h3><p><code>FlashEvent</code> 定义了这些事件：</p><ul><li>日志</li><li>阶段切换</li><li>分区下载开始/进度/结束</li><li>整体完成</li></ul><p><code>Logger</code> 两头写：CLI 模式输出到终端和 indicatif，TUI 模式发送到 app channel。</p><p>全局状态少了一堆，TUI 也不用轮询了。</p><h3 id="顺带修的边角料" data-id="顺带修的边角料" class="notion-h"><a href="#顺带修的边角料" class="headerlink" title="顺带修的边角料"></a>顺带修的边角料</h3><p><strong><code>--verify</code> 参数</strong>：改成显式 <code>--verify true/false</code>，README 终于不用骗人了。</p><p><strong>分区配置解析</strong>：<code>OpenixPartition</code> 之前遇到新 section 会直接退出，导致最后一个分区丢掉。现在会先把当前分区存好再退出：</p><figure class="highlight ini"><table><tbody><tr><td class="code"><pre><code class="hljs ini"><span class="hljs-section">[partition]</span><br><span class="hljs-attr">name</span> = vendor<br><span class="hljs-attr">downloadfile</span> = vendor.img<br><br><span class="hljs-section">[other]</span>  <span class="hljs-comment">; 之前这后面的 vendor 会被丢掉</span><br>...<br></code></pre></td></tr></tbody></table></figure><h2 id="改完的感受" data-id="改完的感受" class="notion-h"><a href="#改完的感受" class="headerlink" title="改完的感受"></a>改完的感受</h2><table><thead><tr><th>改动前</th><th>改动后</th></tr></thead><tbody><tr><td><code>FlashMode</code> 定义两份 + TUI 转换</td><td>统一在 <code>flash::request</code></td></tr><tr><td><code>post_action</code> 字符串传递</td><td><code>PostAction</code> 强类型</td></tr><tr><td>CLI/TUI 各自加载固件</td><td><code>LoadedFirmware</code> 统一</td></tr><tr><td>分区计划写在 FES handler 里</td><td><code>PartitionPlanner</code> 独立</td></tr><tr><td>TUI 日志靠全局 channel</td><td><code>FlashEvent</code> 广播</td></tr><tr><td>TUI 进度轮询</td><td>订阅事件</td></tr><tr><td>README 和代码行为不一致</td><td><code>--verify true/false</code> 可用</td></tr></tbody></table><p>测试也补了一些：请求解析、分区过滤、配置解析边界情况。之前基本是裸奔状态。</p><h2 id="后续" data-id="后续" class="notion-h"><a href="#后续" class="headerlink" title="后续"></a>后续</h2><p>这次改的核心思路是把”约定”变成”边界”——参数是 <code>FlashRequest</code>，固件是 <code>LoadedFirmware</code>，分区计划是 <code>PartitionPlanner</code>，进度是 <code>FlashEvent</code>。</p><p>底层 USB/FEL/FES 协议调用顺序没动，用户侧行为也保持兼容。但以后要加 dry-run、取消任务、刷机报告这些功能，现在有清晰的接入点了。</p><p>代码地址：<a href="https://github.com/YuzukiTsuru/OpenixCLI">OpenixCLI</a></p>]]>
    </content>
    <id>https://gloomyghost.com/live/2026-05-30-openixcli-refactor.aspx</id>
    <link href="https://gloomyghost.com/live/2026-05-30-openixcli-refactor.aspx"/>
    <published>2026-05-29T16:00:00.000Z</published>
    <summary>
      <![CDATA[<h2 id="从一个-README-错误说起" data-id="从一个-README-错误说起" class="notion-h"><a href="#从一个-README-错误说起" class="headerlink" title="从一个 README 错误说起"></]]>
    </summary>
    <title>OpenixCLI 刷机流程重构：把散落的拼图拼回去</title>
    <updated>2026-05-29T20:33:31.220Z</updated>
  </entry>
  <entry>
    <author>
      <name>柚木 鉉</name>
    </author>
    <category term="USB" scheme="https://gloomyghost.com/tags/USB/"/>
    <category term="嵌入式" scheme="https://gloomyghost.com/tags/%E5%B5%8C%E5%85%A5%E5%BC%8F/"/>
    <category term="C" scheme="https://gloomyghost.com/tags/C/"/>
    <category term="libefex" scheme="https://gloomyghost.com/tags/libefex/"/>
    <category term="全志芯片" scheme="https://gloomyghost.com/tags/%E5%85%A8%E5%BF%97%E8%8A%AF%E7%89%87/"/>
    <content>
      <![CDATA[<h1 id="libefex-模块架构" data-id="libefex-模块架构" class="notion-h"><a href="#libefex-模块架构" class="headerlink" title="libefex 模块架构"></a>libefex 模块架构</h1><h2 id="层级架构" data-id="层级架构" class="notion-h"><a href="#层级架构" class="headerlink" title="层级架构"></a>层级架构</h2><p>libefex 采用清晰的分层架构设计，从底层 USB 驱动到高层 API 逐层抽象：</p><pre class="mermaid">graph TB    subgraph Application["应用层"]        A1[CLI Tools]        A2[Test Programs]        A3[Rust Bindings]    end    subgraph API["API 层"]        B1[efex-fel.h<br>FEL 操作接口]        B2[efex-fes.h<br>FES 操作接口]        B3[efex-common.h<br>通用接口]    end    subgraph Protocol["协议层"]        C1[efex-fel.c<br>FEL 协议实现]        C2[efex-fes.c<br>FES 协议实现]        C3[efex-payloads.c<br>Payload 调度]    end    subgraph USB["USB 通信层"]        D1[efex-usb.c<br>请求/响应处理]        D2[efex-protocol.h<br>数据包定义]    end    subgraph Backend["USB 后端层"]        E1[usb_layer.c<br>后端调度器]        E2[usb_layer_libusb.c<br>libusb 后端]        E3[usb_layer_winusb.c<br>WinUSB 后端]    end    subgraph External["外部依赖"]        F1[libusb-1.0]        F2[Windows SetupAPI]    end    A1 --&gt; B1    A1 --&gt; B2    A2 --&gt; B1    A2 --&gt; B2    A3 --&gt; B1    A3 --&gt; B2    B1 --&gt; C1    B2 --&gt; C2    B3 --&gt; C1    C1 --&gt; D1    C2 --&gt; D1    C3 --&gt; C1    D1 --&gt; E1    D2 --&gt; D1    E1 --&gt; E2    E1 --&gt; E3    E2 --&gt; F1    E3 --&gt; F2</pre><h2 id="核心数据结构" data-id="核心数据结构" class="notion-h"><a href="#核心数据结构" class="headerlink" title="核心数据结构"></a>核心数据结构</h2><h3 id="上下文结构-sunxi-efex-ctx-t" data-id="上下文结构-sunxi-efex-ctx-t" class="notion-h"><a href="#上下文结构-sunxi-efex-ctx-t" class="headerlink" title="上下文结构 (sunxi_efex_ctx_t)"></a>上下文结构 (<code>sunxi_efex_ctx_t</code>)</h3><p>源码位置: <code>includes/efex-protocol.h:240-247</code></p><figure class="highlight c"><table><tbody><tr><td class="code"><pre><code class="hljs c"><span class="hljs-class"><span class="hljs-keyword">struct</span> <span class="hljs-title">sunxi_efex_ctx_t</span> {</span><br>    <span class="hljs-type">void</span> *hdl;                       <span class="hljs-comment">// USB 设备句柄</span><br>    <span class="hljs-type">void</span> *usb_context;               <span class="hljs-comment">// libusb 上下文</span><br>    <span class="hljs-type">char</span> *dev_name;                  <span class="hljs-comment">// 设备路径/名称</span><br>    <span class="hljs-type">int</span> epout;                       <span class="hljs-comment">// OUT 端点地址</span><br>    <span class="hljs-type">int</span> epin;                        <span class="hljs-comment">// IN 端点地址</span><br>    <span class="hljs-class"><span class="hljs-keyword">struct</span> <span class="hljs-title">sunxi_efex_device_resp_t</span> <span class="hljs-title">resp</span>;</span>  <span class="hljs-comment">// 设备响应信息</span><br>};<br></code></pre></td></tr></tbody></table></figure><p>该结构体是所有 API 操作的核心上下文，保存了：</p><ul><li>USB 设备句柄和上下文</li><li>设备端点配置</li><li>设备返回的响应信息</li></ul><h3 id="设备响应结构-sunxi-efex-device-resp-t" data-id="设备响应结构-sunxi-efex-device-resp-t" class="notion-h"><a href="#设备响应结构-sunxi-efex-device-resp-t" class="headerlink" title="设备响应结构 (sunxi_efex_device_resp_t)"></a>设备响应结构 (<code>sunxi_efex_device_resp_t</code>)</h3><p>源码位置: <code>includes/efex-protocol.h:229-238</code></p><figure class="highlight c"><table><tbody><tr><td class="code"><pre><code class="hljs c"><span class="hljs-class"><span class="hljs-keyword">struct</span> <span class="hljs-title">sunxi_efex_device_resp_t</span> {</span><br>    <span class="hljs-type">char</span> magic[<span class="hljs-number">8</span>];                   <span class="hljs-comment">// "AWUSBEFEX"</span><br>    <span class="hljs-type">uint32_t</span> id;                     <span class="hljs-comment">// 芯片 ID</span><br>    <span class="hljs-type">uint32_t</span> firmware;               <span class="hljs-comment">// 固件版本</span><br>    <span class="hljs-type">uint16_t</span> mode;                   <span class="hljs-comment">// 设备模式 (FEL/FES)</span><br>    <span class="hljs-type">uint8_t</span> data_flag;               <span class="hljs-comment">// 数据标志</span><br>    <span class="hljs-type">uint8_t</span> data_length;             <span class="hljs-comment">// 数据长度</span><br>    <span class="hljs-type">uint32_t</span> data_start_address;     <span class="hljs-comment">// 数据起始地址</span><br>    <span class="hljs-type">uint8_t</span> reserved[<span class="hljs-number">8</span>];             <span class="hljs-comment">// 保留字段</span><br>};<br></code></pre></td></tr></tbody></table></figure><p>设备初始化时返回此结构，包含芯片 ID、固件版本、当前模式等重要信息。</p><h3 id="设备模式枚举" data-id="设备模式枚举" class="notion-h"><a href="#设备模式枚举" class="headerlink" title="设备模式枚举"></a>设备模式枚举</h3><p>源码位置: <code>includes/efex-protocol.h:97-103</code></p><figure class="highlight c"><table><tbody><tr><td class="code"><pre><code class="hljs c"><span class="hljs-class"><span class="hljs-keyword">enum</span> <span class="hljs-title">sunxi_verify_device_mode_t</span> {</span><br>    DEVICE_MODE_NULL = <span class="hljs-number">0x0</span>,          <span class="hljs-comment">// 无效模式</span><br>    DEVICE_MODE_FEL = <span class="hljs-number">0x1</span>,           <span class="hljs-comment">// FEL 模式</span><br>    DEVICE_MODE_SRV = <span class="hljs-number">0x2</span>,           <span class="hljs-comment">// FES/SRV 模式</span><br>    DEVICE_MODE_UPDATE_COOL = <span class="hljs-number">0x3</span>,   <span class="hljs-comment">// 冷更新模式</span><br>    DEVICE_MODE_UPDATE_HOT = <span class="hljs-number">0x4</span>,    <span class="hljs-comment">// 热更新模式</span><br>};<br></code></pre></td></tr></tbody></table></figure><h2 id="模块交互流程" data-id="模块交互流程" class="notion-h"><a href="#模块交互流程" class="headerlink" title="模块交互流程"></a>模块交互流程</h2><p>当用户调用 FEL 内存读取操作时，数据流经以下模块：</p><pre class="mermaid">sequenceDiagram    participant App as 应用程序    participant FEL as efex-fel.c    participant USB as efex-usb.c    participant Layer as usb_layer.c    participant Libusb as libusb    participant Device as USB 设备    App-&gt;&gt;FEL: sunxi_efex_fel_read(addr, buf, len)    FEL-&gt;&gt;FEL: 分块处理 (max 64KB)        loop 每个数据块        FEL-&gt;&gt;USB: sunxi_send_efex_request(EFEX_CMD_FEL_READ)        USB-&gt;&gt;USB: 构建请求包 (AWUC magic)        USB-&gt;&gt;Layer: sunxi_usb_bulk_send(请求包)        Layer-&gt;&gt;Libusb: libusb_bulk_transfer(EP_OUT)        Libusb-&gt;&gt;Device: USB 批量传输        Device--&gt;&gt;Libusb: 响应数据        Libusb--&gt;&gt;Layer: 返回数据        Layer--&gt;&gt;USB: sunxi_usb_bulk_recv(响应)        USB--&gt;&gt;FEL: 解析响应数据    end        FEL--&gt;&gt;App: 返回读取的数据</pre><h2 id="模块职责详解" data-id="模块职责详解" class="notion-h"><a href="#模块职责详解" class="headerlink" title="模块职责详解"></a>模块职责详解</h2><h3 id="1-Core-Common-efex-common-c-h" data-id="1-Core-Common-efex-common-c-h" class="notion-h"><a href="#1-Core-Common-efex-common-c-h" class="headerlink" title="1. Core Common (efex-common.c/h)"></a>1. Core Common (<code>efex-common.c/h</code>)</h3><p><strong>核心职责</strong>: 设备初始化与错误处理</p><p>源码位置: <code>includes/efex-common.h:31-96</code></p><figure class="highlight c"><table><tbody><tr><td class="code"><pre><code class="hljs c"><span class="hljs-comment">/**</span><br><span class="hljs-comment"> * @brief Send an EFEX request to the device</span><br><span class="hljs-comment"> */</span><br><span class="hljs-type">int</span> <span class="hljs-title function_">sunxi_send_efex_request</span><span class="hljs-params">(<span class="hljs-type">const</span> <span class="hljs-keyword">struct</span> <span class="hljs-type">sunxi_efex_ctx_t</span> *ctx, </span><br><span class="hljs-params">                            <span class="hljs-keyword">enum</span> <span class="hljs-type">sunxi_efex_cmd_t</span> type, </span><br><span class="hljs-params">                            <span class="hljs-type">uint32_t</span> addr, <span class="hljs-type">uint32_t</span> length)</span>;<br><br><span class="hljs-comment">/**</span><br><span class="hljs-comment"> * @brief Read the EFEX status from the device</span><br><span class="hljs-comment"> */</span><br><span class="hljs-type">int</span> <span class="hljs-title function_">sunxi_read_efex_status</span><span class="hljs-params">(<span class="hljs-type">const</span> <span class="hljs-keyword">struct</span> <span class="hljs-type">sunxi_efex_ctx_t</span> *ctx)</span>;<br><br><span class="hljs-comment">/**</span><br><span class="hljs-comment"> * @brief Scan for a USB device matching the specified vendor and product IDs</span><br><span class="hljs-comment"> */</span><br><span class="hljs-type">int</span> <span class="hljs-title function_">sunxi_scan_usb_device</span><span class="hljs-params">(<span class="hljs-keyword">struct</span> <span class="hljs-type">sunxi_efex_ctx_t</span> *ctx)</span>;<br><br><span class="hljs-comment">/**</span><br><span class="hljs-comment"> * @brief Get the device mode from the EFEX context</span><br><span class="hljs-comment"> */</span><br><span class="hljs-keyword">enum</span> <span class="hljs-type">sunxi_verify_device_mode_t</span> <span class="hljs-title function_">sunxi_efex_get_device_mode</span><span class="hljs-params">(</span><br><span class="hljs-params">    <span class="hljs-type">const</span> <span class="hljs-keyword">struct</span> <span class="hljs-type">sunxi_efex_ctx_t</span> *ctx)</span>;<br><br><span class="hljs-comment">/**</span><br><span class="hljs-comment"> * @brief Initialize the EFEX context</span><br><span class="hljs-comment"> */</span><br><span class="hljs-type">int</span> <span class="hljs-title function_">sunxi_efex_init</span><span class="hljs-params">(<span class="hljs-keyword">struct</span> <span class="hljs-type">sunxi_efex_ctx_t</span> *ctx)</span>;<br><br><span class="hljs-comment">/**</span><br><span class="hljs-comment"> * @brief Get error message string for a given error code</span><br><span class="hljs-comment"> */</span><br><span class="hljs-type">const</span> <span class="hljs-type">char</span> *<span class="hljs-title function_">sunxi_efex_strerror</span><span class="hljs-params">(<span class="hljs-type">int</span> error_code)</span>;<br></code></pre></td></tr></tbody></table></figure><h3 id="错误码定义" data-id="错误码定义" class="notion-h"><a href="#错误码定义" class="headerlink" title="错误码定义"></a>错误码定义</h3><p>源码位置: <code>includes/efex-protocol.h:17-59</code></p><figure class="highlight c"><table><tbody><tr><td class="code"><pre><code class="hljs c"><span class="hljs-class"><span class="hljs-keyword">enum</span> <span class="hljs-title">sunxi_efex_error_t</span> {</span><br>    <span class="hljs-comment">/* Generic Errors */</span><br>    EFEX_ERR_SUCCESS = <span class="hljs-number">0</span>,        <span class="hljs-comment">/**&lt; Success */</span><br>    EFEX_ERR_INVALID_PARAM = <span class="hljs-number">-1</span>, <span class="hljs-comment">/**&lt; Invalid parameter */</span><br>    EFEX_ERR_NULL_PTR = <span class="hljs-number">-2</span>,      <span class="hljs-comment">/**&lt; Null pointer error */</span><br>    EFEX_ERR_MEMORY = <span class="hljs-number">-3</span>,        <span class="hljs-comment">/**&lt; Memory allocation error */</span><br>    EFEX_ERR_NOT_SUPPORT = <span class="hljs-number">-4</span>,   <span class="hljs-comment">/**&lt; Operation not supported */</span><br><br>    <span class="hljs-comment">/* USB Communication Errors */</span><br>    EFEX_ERR_USB_INIT = <span class="hljs-number">-10</span>,             <span class="hljs-comment">/**&lt; USB initialization failed */</span><br>    EFEX_ERR_USB_DEVICE_NOT_FOUND = <span class="hljs-number">-11</span>, <span class="hljs-comment">/**&lt; Device not found */</span><br>    EFEX_ERR_USB_OPEN = <span class="hljs-number">-12</span>,             <span class="hljs-comment">/**&lt; Failed to open device */</span><br>    EFEX_ERR_USB_TRANSFER = <span class="hljs-number">-13</span>,         <span class="hljs-comment">/**&lt; USB transfer failed */</span><br>    EFEX_ERR_USB_TIMEOUT = <span class="hljs-number">-14</span>,          <span class="hljs-comment">/**&lt; USB transfer timeout */</span><br>    EFEX_ERR_USB_WRONG_DRIVER = <span class="hljs-number">-15</span>,     <span class="hljs-comment">/**&lt; Wrong USB driver installed */</span><br><br>    <span class="hljs-comment">/* Protocol Errors */</span><br>    EFEX_ERR_PROTOCOL = <span class="hljs-number">-20</span>,            <span class="hljs-comment">/**&lt; Protocol error */</span><br>    EFEX_ERR_INVALID_RESPONSE = <span class="hljs-number">-21</span>,    <span class="hljs-comment">/**&lt; Invalid response from device */</span><br>    EFEX_ERR_UNEXPECTED_STATUS = <span class="hljs-number">-22</span>,   <span class="hljs-comment">/**&lt; Unexpected status code */</span><br>    EFEX_ERR_INVALID_DEVICE_MODE = <span class="hljs-number">-24</span>, <span class="hljs-comment">/**&lt; Invalid device mode */</span><br><br>    <span class="hljs-comment">/* Flash Related Errors */</span><br>    EFEX_ERR_FLASH_ACCESS = <span class="hljs-number">-40</span>,     <span class="hljs-comment">/**&lt; Flash access error */</span><br>    EFEX_ERR_FLASH_SIZE_PROBE = <span class="hljs-number">-41</span>, <span class="hljs-comment">/**&lt; Flash size probing failed */</span><br>    EFEX_ERR_FLASH_SET_ONOFF = <span class="hljs-number">-42</span>,  <span class="hljs-comment">/**&lt; Failed to set flash on/off */</span><br><br>    <span class="hljs-comment">/* Verification Errors */</span><br>    EFEX_ERR_VERIFICATION = <span class="hljs-number">-50</span>, <span class="hljs-comment">/**&lt; Verification failed */</span><br>    EFEX_ERR_CRC_MISMATCH = <span class="hljs-number">-51</span>, <span class="hljs-comment">/**&lt; CRC mismatch error */</span><br><br>    <span class="hljs-comment">/* File Operation Errors */</span><br>    EFEX_ERR_FILE_OPEN = <span class="hljs-number">-60</span>,  <span class="hljs-comment">/**&lt; Failed to open file */</span><br>    EFEX_ERR_FILE_READ = <span class="hljs-number">-61</span>,  <span class="hljs-comment">/**&lt; Failed to read file */</span><br>    EFEX_ERR_FILE_WRITE = <span class="hljs-number">-62</span>, <span class="hljs-comment">/**&lt; Failed to write file */</span><br>};<br></code></pre></td></tr></tbody></table></figure><h3 id="2-USB-Protocol-efex-usb-c-h" data-id="2-USB-Protocol-efex-usb-c-h" class="notion-h"><a href="#2-USB-Protocol-efex-usb-c-h" class="headerlink" title="2. USB Protocol (efex-usb.c/h)"></a>2. USB Protocol (<code>efex-usb.c/h</code>)</h3><p><strong>核心职责</strong>: USB 协议数据包处理</p><p>源码位置: <code>includes/efex-usb.h</code></p><figure class="highlight c"><table><tbody><tr><td class="code"><pre><code class="hljs c"><span class="hljs-type">int</span> <span class="hljs-title function_">sunxi_usb_write</span><span class="hljs-params">(<span class="hljs-type">const</span> <span class="hljs-keyword">struct</span> <span class="hljs-type">sunxi_efex_ctx_t</span> *ctx,</span><br><span class="hljs-params">                    <span class="hljs-type">const</span> <span class="hljs-type">void</span> *buf, <span class="hljs-type">size_t</span> len)</span>;<br>                    <br><span class="hljs-type">int</span> <span class="hljs-title function_">sunxi_usb_read</span><span class="hljs-params">(<span class="hljs-type">const</span> <span class="hljs-keyword">struct</span> <span class="hljs-type">sunxi_efex_ctx_t</span> *ctx,</span><br><span class="hljs-params">                   <span class="hljs-type">void</span> *buf, <span class="hljs-type">size_t</span> len)</span>;<br><br><span class="hljs-type">int</span> <span class="hljs-title function_">sunxi_usb_fes_xfer</span><span class="hljs-params">(<span class="hljs-type">const</span> <span class="hljs-keyword">struct</span> <span class="hljs-type">sunxi_efex_ctx_t</span> *ctx,</span><br><span class="hljs-params">                       <span class="hljs-keyword">enum</span> <span class="hljs-type">sunxi_usb_fes_xfer_type_t</span> type,</span><br><span class="hljs-params">                       <span class="hljs-type">uint32_t</span> cmd, <span class="hljs-type">const</span> <span class="hljs-type">char</span> *request_buf,</span><br><span class="hljs-params">                       <span class="hljs-type">ssize_t</span> request_len, <span class="hljs-type">const</span> <span class="hljs-type">char</span> *buf,</span><br><span class="hljs-params">                       <span class="hljs-type">ssize_t</span> len)</span>;<br></code></pre></td></tr></tbody></table></figure><h3 id="3-FEL-Operations-efex-fel-c-h" data-id="3-FEL-Operations-efex-fel-c-h" class="notion-h"><a href="#3-FEL-Operations-efex-fel-c-h" class="headerlink" title="3. FEL Operations (efex-fel.c/h)"></a>3. FEL Operations (<code>efex-fel.c/h</code>)</h3><p><strong>核心职责</strong>: FEL 模式内存操作</p><p>源码位置: <code>includes/efex-fel.h</code></p><figure class="highlight c"><table><tbody><tr><td class="code"><pre><code class="hljs c"><span class="hljs-type">int</span> <span class="hljs-title function_">sunxi_efex_fel_read</span><span class="hljs-params">(<span class="hljs-type">const</span> <span class="hljs-keyword">struct</span> <span class="hljs-type">sunxi_efex_ctx_t</span> *ctx,</span><br><span class="hljs-params">                        <span class="hljs-type">uint32_t</span> addr, <span class="hljs-type">char</span> *buf, <span class="hljs-type">ssize_t</span> len)</span>;<br><br><span class="hljs-type">int</span> <span class="hljs-title function_">sunxi_efex_fel_write</span><span class="hljs-params">(<span class="hljs-type">const</span> <span class="hljs-keyword">struct</span> <span class="hljs-type">sunxi_efex_ctx_t</span> *ctx,</span><br><span class="hljs-params">                         <span class="hljs-type">uint32_t</span> addr, <span class="hljs-type">const</span> <span class="hljs-type">char</span> *buf, <span class="hljs-type">ssize_t</span> len)</span>;<br><br><span class="hljs-type">int</span> <span class="hljs-title function_">sunxi_efex_fel_exec</span><span class="hljs-params">(<span class="hljs-type">const</span> <span class="hljs-keyword">struct</span> <span class="hljs-type">sunxi_efex_ctx_t</span> *ctx,</span><br><span class="hljs-params">                        <span class="hljs-type">uint32_t</span> addr)</span>;<br></code></pre></td></tr></tbody></table></figure><h3 id="4-FES-Operations-efex-fes-c-h" data-id="4-FES-Operations-efex-fes-c-h" class="notion-h"><a href="#4-FES-Operations-efex-fes-c-h" class="headerlink" title="4. FES Operations (efex-fes.c/h)"></a>4. FES Operations (<code>efex-fes.c/h</code>)</h3><p><strong>核心职责</strong>: FES 模式闪存操作</p><p>源码位置: <code>includes/efex-fes.h</code></p><figure class="highlight c"><table><tbody><tr><td class="code"><pre><code class="hljs c"><span class="hljs-type">int</span> <span class="hljs-title function_">sunxi_efex_fes_query_storage</span><span class="hljs-params">(<span class="hljs-type">const</span> <span class="hljs-keyword">struct</span> <span class="hljs-type">sunxi_efex_ctx_t</span> *ctx,</span><br><span class="hljs-params">                                 <span class="hljs-type">uint32_t</span> *storage_type)</span>;<br><br><span class="hljs-type">int</span> <span class="hljs-title function_">sunxi_efex_fes_down</span><span class="hljs-params">(<span class="hljs-type">const</span> <span class="hljs-keyword">struct</span> <span class="hljs-type">sunxi_efex_ctx_t</span> *ctx,</span><br><span class="hljs-params">                        <span class="hljs-type">const</span> <span class="hljs-type">char</span> *buf, <span class="hljs-type">ssize_t</span> len,</span><br><span class="hljs-params">                        <span class="hljs-type">uint32_t</span> addr, <span class="hljs-keyword">enum</span> <span class="hljs-type">sunxi_fes_data_type_t</span> type)</span>;<br><br><span class="hljs-type">int</span> <span class="hljs-title function_">sunxi_efex_fes_up</span><span class="hljs-params">(<span class="hljs-type">const</span> <span class="hljs-keyword">struct</span> <span class="hljs-type">sunxi_efex_ctx_t</span> *ctx,</span><br><span class="hljs-params">                      <span class="hljs-type">const</span> <span class="hljs-type">char</span> *buf, <span class="hljs-type">ssize_t</span> len,</span><br><span class="hljs-params">                      <span class="hljs-type">uint32_t</span> addr, <span class="hljs-keyword">enum</span> <span class="hljs-type">sunxi_fes_data_type_t</span> type)</span>;<br></code></pre></td></tr></tbody></table></figure><h3 id="5-Payloads-efex-payloads-c-h" data-id="5-Payloads-efex-payloads-c-h" class="notion-h"><a href="#5-Payloads-efex-payloads-c-h" class="headerlink" title="5. Payloads (efex-payloads.c/h)"></a>5. Payloads (<code>efex-payloads.c/h</code>)</h3><p><strong>核心职责</strong>: 架构特定的机器码 Payload</p><p>源码位置: <code>includes/efex-payloads.h</code></p><figure class="highlight c"><table><tbody><tr><td class="code"><pre><code class="hljs c"><span class="hljs-type">int</span> <span class="hljs-title function_">sunxi_efex_fel_payloads_init</span><span class="hljs-params">(<span class="hljs-keyword">enum</span> sunxi_efex_fel_payloads_arch arch)</span>;<br><br><span class="hljs-type">int</span> <span class="hljs-title function_">sunxi_efex_fel_payloads_readl</span><span class="hljs-params">(<span class="hljs-type">const</span> <span class="hljs-keyword">struct</span> <span class="hljs-type">sunxi_efex_ctx_t</span> *ctx,</span><br><span class="hljs-params">                                  <span class="hljs-type">uint32_t</span> addr, <span class="hljs-type">uint32_t</span> *val)</span>;<br><br><span class="hljs-type">int</span> <span class="hljs-title function_">sunxi_efex_fel_payloads_writel</span><span class="hljs-params">(<span class="hljs-type">const</span> <span class="hljs-keyword">struct</span> <span class="hljs-type">sunxi_efex_ctx_t</span> *ctx,</span><br><span class="hljs-params">                                   <span class="hljs-type">uint32_t</span> value, <span class="hljs-type">uint32_t</span> addr)</span>;<br><br><span class="hljs-keyword">struct</span> payloads_ops *<span class="hljs-title function_">sunxi_efex_fel_get_current_payload</span><span class="hljs-params">(<span class="hljs-type">void</span>)</span>;<br></code></pre></td></tr></tbody></table></figure><p><strong>支持的架构</strong>:</p><table><thead><tr><th>架构</th><th>文件</th><th>状态</th></tr></thead><tbody><tr><td>ARM32 (ARMv7)</td><td><code>src/arch/arm.c</code></td><td>完整实现</td></tr><tr><td>AARCH64 (ARMv8)</td><td><code>src/arch/aarch64.c</code></td><td>待实现</td></tr><tr><td>RISC-V (E907)</td><td><code>src/arch/riscv.c</code></td><td>完整实现</td></tr></tbody></table><h3 id="6-USB-Backend-src-usb-c" data-id="6-USB-Backend-src-usb-c" class="notion-h"><a href="#6-USB-Backend-src-usb-c" class="headerlink" title="6. USB Backend (src/usb/*.c)"></a>6. USB Backend (<code>src/usb/*.c</code>)</h3><p><strong>核心职责</strong>: 跨平台 USB 驱动抽象</p><p>源码位置: <code>includes/usb_layer.h</code></p><figure class="highlight c"><table><tbody><tr><td class="code"><pre><code class="hljs c"><span class="hljs-type">int</span> <span class="hljs-title function_">sunxi_usb_bulk_send</span><span class="hljs-params">(<span class="hljs-type">void</span> *handle, <span class="hljs-type">int</span> ep, <span class="hljs-type">const</span> <span class="hljs-type">char</span> *buf, <span class="hljs-type">ssize_t</span> len)</span>;<br><span class="hljs-type">int</span> <span class="hljs-title function_">sunxi_usb_bulk_recv</span><span class="hljs-params">(<span class="hljs-type">void</span> *handle, <span class="hljs-type">int</span> ep, <span class="hljs-type">char</span> *buf, <span class="hljs-type">ssize_t</span> len)</span>;<br><span class="hljs-type">int</span> <span class="hljs-title function_">sunxi_scan_usb_device</span><span class="hljs-params">(<span class="hljs-keyword">struct</span> <span class="hljs-type">sunxi_efex_ctx_t</span> *ctx)</span>;<br><span class="hljs-type">int</span> <span class="hljs-title function_">sunxi_usb_init</span><span class="hljs-params">(<span class="hljs-keyword">struct</span> <span class="hljs-type">sunxi_efex_ctx_t</span> *ctx)</span>;<br><span class="hljs-type">int</span> <span class="hljs-title function_">sunxi_usb_exit</span><span class="hljs-params">(<span class="hljs-keyword">struct</span> <span class="hljs-type">sunxi_efex_ctx_t</span> *ctx)</span>;<br><span class="hljs-type">int</span> <span class="hljs-title function_">sunxi_efex_set_usb_backend</span><span class="hljs-params">(<span class="hljs-keyword">enum</span> usb_backend_type backend)</span>;<br></code></pre></td></tr></tbody></table></figure><h2 id="API-表面概览" data-id="API-表面概览" class="notion-h"><a href="#API-表面概览" class="headerlink" title="API 表面概览"></a>API 表面概览</h2><h3 id="初始化与设备管理" data-id="初始化与设备管理" class="notion-h"><a href="#初始化与设备管理" class="headerlink" title="初始化与设备管理"></a>初始化与设备管理</h3><figure class="highlight c"><table><tbody><tr><td class="code"><pre><code class="hljs c"><span class="hljs-comment">// 设备扫描与初始化</span><br><span class="hljs-type">int</span> <span class="hljs-title function_">sunxi_scan_usb_device</span><span class="hljs-params">(<span class="hljs-keyword">struct</span> <span class="hljs-type">sunxi_efex_ctx_t</span> *ctx)</span>;<br><span class="hljs-type">int</span> <span class="hljs-title function_">sunxi_usb_init</span><span class="hljs-params">(<span class="hljs-keyword">struct</span> <span class="hljs-type">sunxi_efex_ctx_t</span> *ctx)</span>;<br><span class="hljs-type">int</span> <span class="hljs-title function_">sunxi_efex_init</span><span class="hljs-params">(<span class="hljs-keyword">struct</span> <span class="hljs-type">sunxi_efex_ctx_t</span> *ctx)</span>;<br><span class="hljs-type">void</span> <span class="hljs-title function_">sunxi_efex_close</span><span class="hljs-params">(<span class="hljs-keyword">struct</span> <span class="hljs-type">sunxi_efex_ctx_t</span> *ctx)</span>;<br><br><span class="hljs-comment">// 错误处理</span><br><span class="hljs-type">const</span> <span class="hljs-type">char</span> *<span class="hljs-title function_">sunxi_efex_strerror</span><span class="hljs-params">(<span class="hljs-type">int</span> error_code)</span>;<br></code></pre></td></tr></tbody></table></figure><h3 id="FEL-模式-API" data-id="FEL-模式-API" class="notion-h"><a href="#FEL-模式-API" class="headerlink" title="FEL 模式 API"></a>FEL 模式 API</h3><figure class="highlight c"><table><tbody><tr><td class="code"><pre><code class="hljs c"><span class="hljs-type">int</span> <span class="hljs-title function_">sunxi_efex_fel_read</span><span class="hljs-params">(<span class="hljs-type">const</span> <span class="hljs-keyword">struct</span> <span class="hljs-type">sunxi_efex_ctx_t</span> *ctx,</span><br><span class="hljs-params">                        <span class="hljs-type">uint32_t</span> addr, <span class="hljs-type">char</span> *buf, <span class="hljs-type">ssize_t</span> len)</span>;<br><span class="hljs-type">int</span> <span class="hljs-title function_">sunxi_efex_fel_write</span><span class="hljs-params">(<span class="hljs-type">const</span> <span class="hljs-keyword">struct</span> <span class="hljs-type">sunxi_efex_ctx_t</span> *ctx,</span><br><span class="hljs-params">                         <span class="hljs-type">uint32_t</span> addr, <span class="hljs-type">const</span> <span class="hljs-type">char</span> *buf, <span class="hljs-type">ssize_t</span> len)</span>;<br><span class="hljs-type">int</span> <span class="hljs-title function_">sunxi_efex_fel_exec</span><span class="hljs-params">(<span class="hljs-type">const</span> <span class="hljs-keyword">struct</span> <span class="hljs-type">sunxi_efex_ctx_t</span> *ctx, <span class="hljs-type">uint32_t</span> addr)</span>;<br></code></pre></td></tr></tbody></table></figure><h3 id="FES-模式-API" data-id="FES-模式-API" class="notion-h"><a href="#FES-模式-API" class="headerlink" title="FES 模式 API"></a>FES 模式 API</h3><figure class="highlight c"><table><tbody><tr><td class="code"><pre><code class="hljs c"><span class="hljs-type">int</span> <span class="hljs-title function_">sunxi_efex_fes_query_storage</span><span class="hljs-params">(<span class="hljs-type">const</span> <span class="hljs-keyword">struct</span> <span class="hljs-type">sunxi_efex_ctx_t</span> *ctx,</span><br><span class="hljs-params">                                 <span class="hljs-type">uint32_t</span> *storage_type)</span>;<br><span class="hljs-type">int</span> <span class="hljs-title function_">sunxi_efex_fes_down</span><span class="hljs-params">(<span class="hljs-type">const</span> <span class="hljs-keyword">struct</span> <span class="hljs-type">sunxi_efex_ctx_t</span> *ctx,</span><br><span class="hljs-params">                        <span class="hljs-type">const</span> <span class="hljs-type">char</span> *buf, <span class="hljs-type">ssize_t</span> len, <span class="hljs-type">uint32_t</span> addr,</span><br><span class="hljs-params">                        <span class="hljs-keyword">enum</span> <span class="hljs-type">sunxi_fes_data_type_t</span> type)</span>;<br><span class="hljs-type">int</span> <span class="hljs-title function_">sunxi_efex_fes_up</span><span class="hljs-params">(<span class="hljs-type">const</span> <span class="hljs-keyword">struct</span> <span class="hljs-type">sunxi_efex_ctx_t</span> *ctx,</span><br><span class="hljs-params">                      <span class="hljs-type">const</span> <span class="hljs-type">char</span> *buf, <span class="hljs-type">ssize_t</span> len, <span class="hljs-type">uint32_t</span> addr,</span><br><span class="hljs-params">                      <span class="hljs-keyword">enum</span> <span class="hljs-type">sunxi_fes_data_type_t</span> type)</span>;<br></code></pre></td></tr></tbody></table></figure><h3 id="Payload-API" data-id="Payload-API" class="notion-h"><a href="#Payload-API" class="headerlink" title="Payload API"></a>Payload API</h3><figure class="highlight c"><table><tbody><tr><td class="code"><pre><code class="hljs c"><span class="hljs-type">int</span> <span class="hljs-title function_">sunxi_efex_fel_payloads_init</span><span class="hljs-params">(<span class="hljs-keyword">enum</span> sunxi_efex_fel_payloads_arch arch)</span>;<br><span class="hljs-type">int</span> <span class="hljs-title function_">sunxi_efex_fel_payloads_readl</span><span class="hljs-params">(<span class="hljs-type">const</span> <span class="hljs-keyword">struct</span> <span class="hljs-type">sunxi_efex_ctx_t</span> *ctx,</span><br><span class="hljs-params">                                  <span class="hljs-type">uint32_t</span> addr, <span class="hljs-type">uint32_t</span> *val)</span>;<br><span class="hljs-type">int</span> <span class="hljs-title function_">sunxi_efex_fel_payloads_writel</span><span class="hljs-params">(<span class="hljs-type">const</span> <span class="hljs-keyword">struct</span> <span class="hljs-type">sunxi_efex_ctx_t</span> *ctx,</span><br><span class="hljs-params">                                   <span class="hljs-type">uint32_t</span> value, <span class="hljs-type">uint32_t</span> addr)</span>;<br></code></pre></td></tr></tbody></table></figure>]]>
    </content>
    <id>https://gloomyghost.com/posts/libefex-architecture/</id>
    <link href="https://gloomyghost.com/posts/libefex-architecture/"/>
    <published>2026-04-29T16:00:00.000Z</published>
    <summary>
      <![CDATA[<h1 id="libefex-模块架构" data-id="libefex-模块架构" class="notion-h"><a href="#libefex-模块架构" class="headerlink" title="libefex 模块架构"></a>libefex 模块]]>
    </summary>
    <title>libefex 模块架构深度解析：分层设计与核心数据结构</title>
    <updated>2026-05-29T20:33:31.219Z</updated>
  </entry>
  <entry>
    <author>
      <name>柚木 鉉</name>
    </author>
    <category term="嵌入式" scheme="https://gloomyghost.com/tags/%E5%B5%8C%E5%85%A5%E5%BC%8F/"/>
    <category term="C" scheme="https://gloomyghost.com/tags/C/"/>
    <category term="libefex" scheme="https://gloomyghost.com/tags/libefex/"/>
    <category term="全志芯片" scheme="https://gloomyghost.com/tags/%E5%85%A8%E5%BF%97%E8%8A%AF%E7%89%87/"/>
    <category term="FEL模式" scheme="https://gloomyghost.com/tags/FEL%E6%A8%A1%E5%BC%8F/"/>
    <content>
      <![CDATA[<h1 id="libefex-FEL-模式操作" data-id="libefex-FEL-模式操作" class="notion-h"><a href="#libefex-FEL-模式操作" class="headerlink" title="libefex FEL 模式操作"></a>libefex FEL 模式操作</h1><h2 id="FEL-模式简介" data-id="FEL-模式简介" class="notion-h"><a href="#FEL-模式简介" class="headerlink" title="FEL 模式简介"></a>FEL 模式简介</h2><p>FEL (Firmware Extraction Level) 是全志芯片 BootROM 中内置的低级子程序模式。在 FEL 模式下，主机可以通过 USB 直接访问设备内存，实现：</p><ul><li>内存读取 (<code>sunxi_efex_fel_read</code>)</li><li>内存写入 (<code>sunxi_efex_fel_write</code>)</li><li>代码执行 (<code>sunxi_efex_fel_exec</code>)</li></ul><h2 id="FEL-代码执行实现" data-id="FEL-代码执行实现" class="notion-h"><a href="#FEL-代码执行实现" class="headerlink" title="FEL 代码执行实现"></a>FEL 代码执行实现</h2><p>源码位置: <code>src/efex-fel.c:12-32</code></p><figure class="highlight c"><table><tbody><tr><td class="code"><pre><code class="hljs c"><span class="hljs-type">int</span> <span class="hljs-title function_">sunxi_efex_fel_exec</span><span class="hljs-params">(<span class="hljs-type">const</span> <span class="hljs-keyword">struct</span> <span class="hljs-type">sunxi_efex_ctx_t</span> *ctx, </span><br><span class="hljs-params">                        <span class="hljs-type">const</span> <span class="hljs-type">uint32_t</span> addr)</span> {<br>    <span class="hljs-keyword">if</span> (!ctx) {<br>        <span class="hljs-keyword">return</span> EFEX_ERR_NULL_PTR;<br>    }<br><br>    <span class="hljs-comment">// 检查设备必须在 FEL 模式</span><br>    <span class="hljs-keyword">if</span> (ctx-&gt;resp.mode != DEVICE_MODE_FEL) {<br>        <span class="hljs-keyword">return</span> EFEX_ERR_INVALID_DEVICE_MODE;<br>    }<br><br>    <span class="hljs-comment">// 发送执行命令</span><br>    <span class="hljs-type">int</span> ret = sunxi_send_efex_request(ctx, EFEX_CMD_FEL_EXEC, addr, <span class="hljs-number">0</span>);<br>    <span class="hljs-keyword">if</span> (ret != EFEX_ERR_SUCCESS) {<br>        <span class="hljs-keyword">return</span> ret;<br>    }<br><br>    <span class="hljs-comment">// 读取执行状态</span><br>    ret = sunxi_read_efex_status(ctx);<br>    <span class="hljs-keyword">if</span> (ret &lt; <span class="hljs-number">0</span>) {<br>        <span class="hljs-keyword">return</span> ret;<br>    }<br><br>    <span class="hljs-keyword">return</span> EFEX_ERR_SUCCESS;<br>}<br></code></pre></td></tr></tbody></table></figure><p>该函数让设备 CPU 跳转到指定地址执行已写入内存的代码。</p><h3 id="FEL-exec-执行流程" data-id="FEL-exec-执行流程" class="notion-h"><a href="#FEL-exec-执行流程" class="headerlink" title="FEL exec 执行流程"></a>FEL exec 执行流程</h3><pre class="mermaid">flowchart TD    A[sunxi_efex_fel_exec] --&gt; B{ctx 有效?}        B --&gt;|否| C[返回 NULL_PTR]        B --&gt;|是| D{mode == FEL?}        D --&gt;|否| E[返回 INVALID_DEVICE_MODE]        D --&gt;|是| F[发送 FEL_EXEC 命令]        F --&gt; G[sunxi_send_efex_request<br>cmd=0x0102 addr=目标地址]        G --&gt; H{请求成功?}        H --&gt;|否| I[返回 USB_TRANSFER 错误]        H --&gt;|是| J[读取执行状态]        J --&gt; K[sunxi_read_efex_status]        K --&gt; L{状态正常?}        L --&gt;|否| M[返回错误状态]        L --&gt;|是| N[返回 SUCCESS]</pre><h2 id="FEL-内存读取实现" data-id="FEL-内存读取实现" class="notion-h"><a href="#FEL-内存读取实现" class="headerlink" title="FEL 内存读取实现"></a>FEL 内存读取实现</h2><h3 id="单次读取函数" data-id="单次读取函数" class="notion-h"><a href="#单次读取函数" class="headerlink" title="单次读取函数"></a>单次读取函数</h3><p>源码位置: <code>src/efex-fel.c:34-52</code></p><figure class="highlight c"><table><tbody><tr><td class="code"><pre><code class="hljs c"><span class="hljs-type">static</span> <span class="hljs-keyword">inline</span> <span class="hljs-type">int</span> <span class="hljs-title function_">sunxi_efex_fel_read_data</span><span class="hljs-params">(</span><br><span class="hljs-params">        <span class="hljs-type">const</span> <span class="hljs-keyword">struct</span> <span class="hljs-type">sunxi_efex_ctx_t</span> *ctx, </span><br><span class="hljs-params">        <span class="hljs-type">const</span> <span class="hljs-type">uint32_t</span> addr, </span><br><span class="hljs-params">        <span class="hljs-type">const</span> <span class="hljs-type">char</span> *buf, </span><br><span class="hljs-params">        <span class="hljs-type">const</span> <span class="hljs-type">ssize_t</span> size)</span> {<br>    <span class="hljs-type">int</span> ret = <span class="hljs-number">0</span>;<br>    <br>    <span class="hljs-comment">// 1. 发送读取请求</span><br>    ret = sunxi_send_efex_request(ctx, EFEX_CMD_FEL_READ, addr, size);<br>    <span class="hljs-keyword">if</span> (ret != EFEX_ERR_SUCCESS) {<br>        <span class="hljs-keyword">return</span> ret;<br>    }<br><br>    <span class="hljs-comment">// 2. 接收读取的数据</span><br>    ret = sunxi_usb_read(ctx, (<span class="hljs-type">void</span> *) buf, size);<br>    <span class="hljs-keyword">if</span> (ret != EFEX_ERR_SUCCESS) {<br>        <span class="hljs-keyword">return</span> ret;<br>    }<br><br>    <span class="hljs-comment">// 3. 读取状态确认</span><br>    ret = sunxi_read_efex_status(ctx);<br>    <span class="hljs-keyword">if</span> (ret &lt; <span class="hljs-number">0</span>) {<br>        <span class="hljs-keyword">return</span> ret;<br>    }<br>    <br>    <span class="hljs-keyword">return</span> EFEX_ERR_SUCCESS;<br>}<br></code></pre></td></tr></tbody></table></figure><h3 id="单次读取流程图" data-id="单次读取流程图" class="notion-h"><a href="#单次读取流程图" class="headerlink" title="单次读取流程图"></a>单次读取流程图</h3><pre class="mermaid">sequenceDiagram    participant API as sunxi_efex_fel_read_data    participant USB as USB 协议层    participant Device as Sunxi 设备    API-&gt;&gt;USB: sunxi_send_efex_request(FEL_READ, addr, size)    USB-&gt;&gt;Device: 发送读取命令包        Device--&gt;&gt;USB: 准备数据    USB-&gt;&gt;API: sunxi_usb_read(buf, size)    USB-&gt;&gt;Device: 批量 IN 传输    Device--&gt;&gt;USB: 返回数据    USB--&gt;&gt;API: 数据写入 buf        API-&gt;&gt;USB: sunxi_read_efex_status    USB-&gt;&gt;Device: 读取状态    Device--&gt;&gt;USB: 状态响应    USB--&gt;&gt;API: 返回状态值        API--&gt;&gt;API: 检查状态并返回结果</pre><h3 id="分块读取函数" data-id="分块读取函数" class="notion-h"><a href="#分块读取函数" class="headerlink" title="分块读取函数"></a>分块读取函数</h3><p>源码位置: <code>src/efex-fel.c:74-100</code></p><figure class="highlight c"><table><tbody><tr><td class="code"><pre><code class="hljs c"><span class="hljs-type">int</span> <span class="hljs-title function_">sunxi_efex_fel_read</span><span class="hljs-params">(<span class="hljs-type">const</span> <span class="hljs-keyword">struct</span> <span class="hljs-type">sunxi_efex_ctx_t</span> *ctx, </span><br><span class="hljs-params">                        <span class="hljs-type">uint32_t</span> addr, <span class="hljs-type">char</span> *buf, <span class="hljs-type">ssize_t</span> len)</span> {<br>    <span class="hljs-keyword">if</span> (!ctx || !buf) {<br>        <span class="hljs-keyword">return</span> EFEX_ERR_NULL_PTR;<br>    }<br><br>    <span class="hljs-comment">// 检查设备模式</span><br>    <span class="hljs-keyword">if</span> (ctx-&gt;resp.mode != DEVICE_MODE_FEL) {<br>        <span class="hljs-keyword">return</span> EFEX_ERR_INVALID_DEVICE_MODE;<br>    }<br><br>    <span class="hljs-keyword">if</span> (len &lt;= <span class="hljs-number">0</span>) {<br>        <span class="hljs-keyword">return</span> EFEX_ERR_INVALID_PARAM;<br>    }<br><br>    <span class="hljs-type">int</span> ret = EFEX_ERR_SUCCESS;<br>    <br>    <span class="hljs-comment">// 分块读取循环</span><br>    <span class="hljs-keyword">while</span> (len &gt; <span class="hljs-number">0</span>) {<br>        <span class="hljs-comment">// 计算本次传输大小 (最大 64KB)</span><br>        <span class="hljs-type">const</span> <span class="hljs-type">uint32_t</span> n = len &gt; EFEX_CODE_MAX_SIZE <br>                          ? EFEX_CODE_MAX_SIZE <br>                          : (<span class="hljs-type">uint32_t</span>) len;<br><br>        <span class="hljs-comment">// 执行单次读取</span><br>        ret = sunxi_efex_fel_read_data(ctx, addr, buf, n);<br>        <span class="hljs-keyword">if</span> (ret &lt; <span class="hljs-number">0</span>)<br>            <span class="hljs-keyword">return</span> ret;<br><br>        <span class="hljs-comment">// 更新地址和缓冲区指针</span><br>        addr += n;<br>        buf += n;<br>        len -= n;<br>    }<br>    <br>    <span class="hljs-keyword">return</span> ret;<br>}<br></code></pre></td></tr></tbody></table></figure><h2 id="FEL-内存写入实现" data-id="FEL-内存写入实现" class="notion-h"><a href="#FEL-内存写入实现" class="headerlink" title="FEL 内存写入实现"></a>FEL 内存写入实现</h2><h3 id="单次写入函数" data-id="单次写入函数" class="notion-h"><a href="#单次写入函数" class="headerlink" title="单次写入函数"></a>单次写入函数</h3><p>源码位置: <code>src/efex-fel.c:54-72</code></p><figure class="highlight c"><table><tbody><tr><td class="code"><pre><code class="hljs c"><span class="hljs-type">static</span> <span class="hljs-keyword">inline</span> <span class="hljs-type">int</span> <span class="hljs-title function_">sunxi_efex_fel_write_data</span><span class="hljs-params">(</span><br><span class="hljs-params">        <span class="hljs-type">const</span> <span class="hljs-keyword">struct</span> <span class="hljs-type">sunxi_efex_ctx_t</span> *ctx, </span><br><span class="hljs-params">        <span class="hljs-type">const</span> <span class="hljs-type">uint32_t</span> addr, </span><br><span class="hljs-params">        <span class="hljs-type">const</span> <span class="hljs-type">char</span> *buf, </span><br><span class="hljs-params">        <span class="hljs-type">const</span> <span class="hljs-type">ssize_t</span> size)</span> {<br>    <span class="hljs-type">int</span> ret = <span class="hljs-number">0</span>;<br>    <br>    <span class="hljs-comment">// 1. 发送写入请求</span><br>    ret = sunxi_send_efex_request(ctx, EFEX_CMD_FEL_WRITE, addr, size);<br>    <span class="hljs-keyword">if</span> (ret != EFEX_ERR_SUCCESS) {<br>        <span class="hljs-keyword">return</span> ret;<br>    }<br><br>    <span class="hljs-comment">// 2. 发送要写入的数据</span><br>    ret = sunxi_usb_write(ctx, buf, size);<br>    <span class="hljs-keyword">if</span> (ret != EFEX_ERR_SUCCESS) {<br>        <span class="hljs-keyword">return</span> ret;<br>    }<br><br>    <span class="hljs-comment">// 3. 读取状态确认</span><br>    ret = sunxi_read_efex_status(ctx);<br>    <span class="hljs-keyword">if</span> (ret &lt; <span class="hljs-number">0</span>) {<br>        <span class="hljs-keyword">return</span> ret;<br>    }<br>    <br>    <span class="hljs-keyword">return</span> EFEX_ERR_SUCCESS;<br>}<br></code></pre></td></tr></tbody></table></figure><h3 id="分块写入函数" data-id="分块写入函数" class="notion-h"><a href="#分块写入函数" class="headerlink" title="分块写入函数"></a>分块写入函数</h3><p>源码位置: <code>src/efex-fel.c:102-128</code></p><figure class="highlight c"><table><tbody><tr><td class="code"><pre><code class="hljs c"><span class="hljs-type">int</span> <span class="hljs-title function_">sunxi_efex_fel_write</span><span class="hljs-params">(<span class="hljs-type">const</span> <span class="hljs-keyword">struct</span> <span class="hljs-type">sunxi_efex_ctx_t</span> *ctx, </span><br><span class="hljs-params">                         <span class="hljs-type">uint32_t</span> addr, <span class="hljs-type">const</span> <span class="hljs-type">char</span> *buf, <span class="hljs-type">ssize_t</span> len)</span> {<br>    <span class="hljs-keyword">if</span> (!ctx || !buf) {<br>        <span class="hljs-keyword">return</span> EFEX_ERR_NULL_PTR;<br>    }<br><br>    <span class="hljs-comment">// 检查设备模式</span><br>    <span class="hljs-keyword">if</span> (ctx-&gt;resp.mode != DEVICE_MODE_FEL) {<br>        <span class="hljs-keyword">return</span> EFEX_ERR_INVALID_DEVICE_MODE;<br>    }<br><br>    <span class="hljs-keyword">if</span> (len &lt;= <span class="hljs-number">0</span>) {<br>        <span class="hljs-keyword">return</span> EFEX_ERR_INVALID_PARAM;<br>    }<br><br>    <span class="hljs-type">int</span> ret = EFEX_ERR_SUCCESS;<br>    <br>    <span class="hljs-comment">// 分块写入循环</span><br>    <span class="hljs-keyword">while</span> (len &gt; <span class="hljs-number">0</span>) {<br>        <span class="hljs-comment">// 计算本次传输大小 (最大 64KB)</span><br>        <span class="hljs-type">const</span> <span class="hljs-type">uint32_t</span> n = len &gt; EFEX_CODE_MAX_SIZE <br>                          ? EFEX_CODE_MAX_SIZE <br>                          : (<span class="hljs-type">uint32_t</span>) len;<br><br>        <span class="hljs-comment">// 执行单次写入</span><br>        ret = sunxi_efex_fel_write_data(ctx, addr, buf, n);<br>        <span class="hljs-keyword">if</span> (ret &lt; <span class="hljs-number">0</span>)<br>            <span class="hljs-keyword">return</span> ret;<br><br>        <span class="hljs-comment">// 更新地址和缓冲区指针</span><br>        addr += n;<br>        buf += n;<br>        len -= n;<br>    }<br>    <br>    <span class="hljs-keyword">return</span> ret;<br>}<br></code></pre></td></tr></tbody></table></figure><h3 id="分块写入流程图" data-id="分块写入流程图" class="notion-h"><a href="#分块写入流程图" class="headerlink" title="分块写入流程图"></a>分块写入流程图</h3><pre class="mermaid">flowchart TD    A[sunxi_efex_fel_write] --&gt; B{参数检查}        B --&gt;|ctx/buf 为空| C[返回 NULL_PTR]    B --&gt;|len &lt;= 0| D[返回 INVALID_PARAM]    B --&gt;|mode != FEL| E[返回 INVALID_DEVICE_MODE]        B --&gt;|检查通过| F[进入分块循环]        F --&gt; G{len &gt; 0?}        G --&gt;|否| H[返回 SUCCESS]        G --&gt;|是| I[计算本次大小 n<br>min len, 64KB]        I --&gt; J[sunxi_efex_fel_write_data]        J --&gt; K[发送 FEL_WRITE 命令<br>addr + size]        K --&gt; L[发送数据块到设备]        L --&gt; M[读取状态确认]        M --&gt; N{状态正常?}        N --&gt;|否| O[返回错误]        N --&gt;|是| P[更新指针<br>addr += n<br>buf += n<br>len -= n]        P --&gt; G</pre><h2 id="带回调的读写函数" data-id="带回调的读写函数" class="notion-h"><a href="#带回调的读写函数" class="headerlink" title="带回调的读写函数"></a>带回调的读写函数</h2><p>源码位置: <code>src/efex-fel.c:130-192</code></p><figure class="highlight c"><table><tbody><tr><td class="code"><pre><code class="hljs c"><span class="hljs-comment">// 带进度回调的读取</span><br><span class="hljs-type">int</span> <span class="hljs-title function_">sunxi_efex_fel_read_cb</span><span class="hljs-params">(<span class="hljs-type">const</span> <span class="hljs-keyword">struct</span> <span class="hljs-type">sunxi_efex_ctx_t</span> *ctx, </span><br><span class="hljs-params">                           <span class="hljs-type">uint32_t</span> addr, <span class="hljs-type">const</span> <span class="hljs-type">char</span> *buf, <span class="hljs-type">ssize_t</span> len,</span><br><span class="hljs-params">                           <span class="hljs-type">void</span> (*callback)(<span class="hljs-type">ssize_t</span> done))</span> {<br>    <span class="hljs-comment">// ... 初始化检查 ...</span><br><br>    <span class="hljs-type">int</span> ret = EFEX_ERR_SUCCESS;<br>    <span class="hljs-keyword">while</span> (len &gt; <span class="hljs-number">0</span>) {<br>        <span class="hljs-type">const</span> <span class="hljs-type">uint32_t</span> n = len &gt; EFEX_CODE_MAX_SIZE <br>                          ? EFEX_CODE_MAX_SIZE <br>                          : (<span class="hljs-type">uint32_t</span>) len;<br><br>        ret = sunxi_efex_fel_read_data(ctx, addr, buf, n);<br>        <span class="hljs-keyword">if</span> (ret &lt; <span class="hljs-number">0</span>)<br>            <span class="hljs-keyword">return</span> ret;<br><br>        <span class="hljs-comment">// 调用进度回调</span><br>        <span class="hljs-keyword">if</span> (callback)<br>            callback((<span class="hljs-type">ssize_t</span>) n);<br><br>        addr += n;<br>        buf += n;<br>        len -= n;<br>    }<br>    <span class="hljs-keyword">return</span> ret;<br>}<br><br><span class="hljs-comment">// 带进度回调的写入</span><br><span class="hljs-type">int</span> <span class="hljs-title function_">sunxi_efex_fel_write_cb</span><span class="hljs-params">(<span class="hljs-type">const</span> <span class="hljs-keyword">struct</span> <span class="hljs-type">sunxi_efex_ctx_t</span> *ctx, </span><br><span class="hljs-params">                            <span class="hljs-type">uint32_t</span> addr, <span class="hljs-type">const</span> <span class="hljs-type">char</span> *buf, <span class="hljs-type">ssize_t</span> len,</span><br><span class="hljs-params">                            <span class="hljs-type">void</span> (*callback)(<span class="hljs-type">ssize_t</span> done))</span> {<br>    <span class="hljs-comment">// ... 初始化检查 ...</span><br><br>    <span class="hljs-type">int</span> ret = EFEX_ERR_SUCCESS;<br>    <span class="hljs-keyword">while</span> (len &gt; <span class="hljs-number">0</span>) {<br>        <span class="hljs-type">const</span> <span class="hljs-type">uint32_t</span> n = len &gt; EFEX_CODE_MAX_SIZE <br>                          ? EFEX_CODE_MAX_SIZE <br>                          : (<span class="hljs-type">uint32_t</span>) len;<br><br>        ret = sunxi_efex_fel_write_data(ctx, addr, buf, n);<br>        <span class="hljs-keyword">if</span> (ret &lt; <span class="hljs-number">0</span>)<br>            <span class="hljs-keyword">return</span> ret;<br><br>        <span class="hljs-comment">// 调用进度回调</span><br>        <span class="hljs-keyword">if</span> (callback)<br>            callback((<span class="hljs-type">ssize_t</span>) n);<br><br>        addr += n;<br>        buf += n;<br>        len -= n;<br>    }<br>    <span class="hljs-keyword">return</span> ret;<br>}<br></code></pre></td></tr></tbody></table></figure><h2 id="分块传输流程图" data-id="分块传输流程图" class="notion-h"><a href="#分块传输流程图" class="headerlink" title="分块传输流程图"></a>分块传输流程图</h2><pre class="mermaid">flowchart TD    A[调用 sunxi_efex_fel_read] --&gt; B[检查参数有效性]    B --&gt; C[检查设备模式]    C --&gt; D{mode == DEVICE_MODE_FEL?}    D --&gt;|否| E[返回 EFEX_ERR_INVALID_DEVICE_MODE]    D --&gt;|是| F[进入分块循环]    F --&gt; G{len &gt; 0?}    G --&gt;|否| H[返回成功]    G --&gt;|是| I[计算本次大小 n<br>min（len, 64KB）]        I --&gt; J[发送 FEL_READ 命令]        J --&gt; K[接收数据块]        K --&gt; L[读取状态]        L --&gt; M{状态正常?}        M --&gt;|否| N[返回错误]        M --&gt;|是| O[更新 addr += n<br>buf += n<br>len -= n]        O --&gt; G</pre><h2 id="Payload-注入流程" data-id="Payload-注入流程" class="notion-h"><a href="#Payload-注入流程" class="headerlink" title="Payload 注入流程"></a>Payload 注入流程</h2><pre class="mermaid">sequenceDiagram    participant Host as 主机    participant Memory as 设备内存    participant CPU as 设备 CPU    Note over Host,CPU: readl 操作示例        Host-&gt;&gt;Memory: sunxi_efex_fel_write(addr, payload_code)    Host-&gt;&gt;Memory: sunxi_efex_fel_write(addr+offset, target_addr)        Host-&gt;&gt;CPU: sunxi_efex_fel_exec(addr)    CPU-&gt;&gt;Memory: Payload 执行: 从 target_addr 读取值    Memory-&gt;&gt;CPU: 返回读取的值    CPU-&gt;&gt;Memory: Payload 将值写入 result_addr        Host-&gt;&gt;Memory: sunxi_efex_fel_read(result_addr, &amp;value)    Memory--&gt;&gt;Host: 返回寄存器值</pre><h2 id="EFEX-请求发送实现" data-id="EFEX-请求发送实现" class="notion-h"><a href="#EFEX-请求发送实现" class="headerlink" title="EFEX 请求发送实现"></a>EFEX 请求发送实现</h2><p>源码位置: <code>src/efex-common.c:13-32</code></p><figure class="highlight c"><table><tbody><tr><td class="code"><pre><code class="hljs c"><span class="hljs-type">int</span> <span class="hljs-title function_">sunxi_send_efex_request</span><span class="hljs-params">(<span class="hljs-type">const</span> <span class="hljs-keyword">struct</span> <span class="hljs-type">sunxi_efex_ctx_t</span> *ctx, </span><br><span class="hljs-params">                            <span class="hljs-type">const</span> <span class="hljs-keyword">enum</span> <span class="hljs-type">sunxi_efex_cmd_t</span> type, </span><br><span class="hljs-params">                            <span class="hljs-type">const</span> <span class="hljs-type">uint32_t</span> addr,</span><br><span class="hljs-params">                            <span class="hljs-type">const</span> <span class="hljs-type">uint32_t</span> length)</span> {<br>    <span class="hljs-keyword">if</span> (!ctx) {<br>        <span class="hljs-keyword">return</span> EFEX_ERR_NULL_PTR;<br>    }<br><br>    <span class="hljs-comment">// 构建 EFEX 请求结构</span><br>    <span class="hljs-type">const</span> <span class="hljs-class"><span class="hljs-keyword">struct</span> <span class="hljs-title">sunxi_efex_request_t</span> <span class="hljs-title">req</span> =</span> {<br>        .cmd = cpu_to_le16(type),      <span class="hljs-comment">// 命令类型</span><br>        .tag = <span class="hljs-number">0x0</span>,                    <span class="hljs-comment">// 标签</span><br>        .address = cpu_to_le32(addr),  <span class="hljs-comment">// 目标地址</span><br>        .len = cpu_to_le32(length),    <span class="hljs-comment">// 数据长度</span><br>    };<br><br>    <span class="hljs-comment">// 发送请求</span><br>    <span class="hljs-type">const</span> <span class="hljs-type">int</span> ret = sunxi_usb_write(ctx, &amp;req, <br>                                    <span class="hljs-keyword">sizeof</span>(<span class="hljs-keyword">struct</span> <span class="hljs-type">sunxi_efex_request_t</span>));<br>    <span class="hljs-keyword">if</span> (ret != <span class="hljs-number">0</span>) {<br>        <span class="hljs-keyword">return</span> EFEX_ERR_USB_TRANSFER;<br>    }<br><br>    <span class="hljs-keyword">return</span> EFEX_ERR_SUCCESS;<br>}<br></code></pre></td></tr></tbody></table></figure><h2 id="EFEX-状态读取实现" data-id="EFEX-状态读取实现" class="notion-h"><a href="#EFEX-状态读取实现" class="headerlink" title="EFEX 状态读取实现"></a>EFEX 状态读取实现</h2><p>源码位置: <code>src/efex-common.c:34-45</code></p><figure class="highlight c"><table><tbody><tr><td class="code"><pre><code class="hljs c"><span class="hljs-type">int</span> <span class="hljs-title function_">sunxi_read_efex_status</span><span class="hljs-params">(<span class="hljs-type">const</span> <span class="hljs-keyword">struct</span> <span class="hljs-type">sunxi_efex_ctx_t</span> *ctx)</span> {<br>    <span class="hljs-keyword">if</span> (!ctx) {<br>        <span class="hljs-keyword">return</span> EFEX_ERR_NULL_PTR;<br>    }<br><br>    <span class="hljs-comment">// 读取响应状态</span><br>    <span class="hljs-type">const</span> <span class="hljs-class"><span class="hljs-keyword">struct</span> <span class="hljs-title">sunxi_efex_response_t</span> <span class="hljs-title">resp</span> =</span> {<span class="hljs-number">0</span>};<br>    <span class="hljs-type">const</span> <span class="hljs-type">int</span> ret = sunxi_usb_read(ctx, &amp;resp, <span class="hljs-keyword">sizeof</span>(resp));<br>    <span class="hljs-keyword">if</span> (ret != <span class="hljs-number">0</span>) {<br>        <span class="hljs-keyword">return</span> EFEX_ERR_USB_TRANSFER;<br>    }<br>    <br>    <span class="hljs-keyword">return</span> resp.status;<br>}<br></code></pre></td></tr></tbody></table></figure><h2 id="FEL-API-使用示例" data-id="FEL-API-使用示例" class="notion-h"><a href="#FEL-API-使用示例" class="headerlink" title="FEL API 使用示例"></a>FEL API 使用示例</h2><h3 id="基础内存操作" data-id="基础内存操作" class="notion-h"><a href="#基础内存操作" class="headerlink" title="基础内存操作"></a>基础内存操作</h3><figure class="highlight c"><table><tbody><tr><td class="code"><pre><code class="hljs c"><span class="hljs-meta">#<span class="hljs-keyword">include</span> <span class="hljs-string">&lt;libefex.h&gt;</span></span><br><br><span class="hljs-type">int</span> <span class="hljs-title function_">main</span><span class="hljs-params">()</span> {<br>    <span class="hljs-class"><span class="hljs-keyword">struct</span> <span class="hljs-title">sunxi_efex_ctx_t</span> <span class="hljs-title">ctx</span> =</span> {<span class="hljs-number">0</span>};<br>    <br>    <span class="hljs-comment">// 1. 扫描并连接设备</span><br>    <span class="hljs-type">int</span> ret = sunxi_scan_usb_device(&amp;ctx);<br>    <span class="hljs-keyword">if</span> (ret != EFEX_ERR_SUCCESS) {<br>        <span class="hljs-built_in">printf</span>(<span class="hljs-string">"Device not found: %s\n"</span>, sunxi_efex_strerror(ret));<br>        <span class="hljs-keyword">return</span> <span class="hljs-number">-1</span>;<br>    }<br>    <br>    <span class="hljs-comment">// 2. 初始化 USB</span><br>    ret = sunxi_usb_init(&amp;ctx);<br>    <span class="hljs-keyword">if</span> (ret != EFEX_ERR_SUCCESS) {<br>        <span class="hljs-built_in">printf</span>(<span class="hljs-string">"USB init failed: %s\n"</span>, sunxi_efex_strerror(ret));<br>        <span class="hljs-keyword">return</span> <span class="hljs-number">-1</span>;<br>    }<br>    <br>    <span class="hljs-comment">// 3. 初始化 EFEX 协议</span><br>    ret = sunxi_efex_init(&amp;ctx);<br>    <span class="hljs-keyword">if</span> (ret != EFEX_ERR_SUCCESS) {<br>        <span class="hljs-built_in">printf</span>(<span class="hljs-string">"EFEX init failed: %s\n"</span>, sunxi_efex_strerror(ret));<br>        <span class="hljs-keyword">return</span> <span class="hljs-number">-1</span>;<br>    }<br>    <br>    <span class="hljs-comment">// 4. 检查设备模式</span><br>    <span class="hljs-built_in">printf</span>(<span class="hljs-string">"Device mode: %s\n"</span>, sunxi_efex_get_device_mode_str(&amp;ctx));<br>    <span class="hljs-built_in">printf</span>(<span class="hljs-string">"Chip ID: 0x%08x\n"</span>, ctx.resp.id);<br>    <br>    <span class="hljs-comment">// 5. 读取内存 (示例: 读取 SRAM)</span><br>    <span class="hljs-type">char</span> buf[<span class="hljs-number">1024</span>];<br>    ret = sunxi_efex_fel_read(&amp;ctx, <span class="hljs-number">0x40000000</span>, buf, <span class="hljs-keyword">sizeof</span>(buf));<br>    <span class="hljs-keyword">if</span> (ret == EFEX_ERR_SUCCESS) {<br>        <span class="hljs-built_in">printf</span>(<span class="hljs-string">"Read 1024 bytes from 0x40000000\n"</span>);<br>    }<br>    <br>    <span class="hljs-comment">// 6. 写入内存</span><br>    <span class="hljs-type">char</span> data[] = {<span class="hljs-number">0x01</span>, <span class="hljs-number">0x02</span>, <span class="hljs-number">0x03</span>, <span class="hljs-number">0x04</span>};<br>    ret = sunxi_efex_fel_write(&amp;ctx, <span class="hljs-number">0x40010000</span>, data, <span class="hljs-keyword">sizeof</span>(data));<br>    <br>    <span class="hljs-comment">// 7. 执行代码</span><br>    ret = sunxi_efex_fel_exec(&amp;ctx, <span class="hljs-number">0x40010000</span>);<br>    <br>    <span class="hljs-comment">// 8. 清理</span><br>    sunxi_usb_exit(&amp;ctx);<br>    <br>    <span class="hljs-keyword">return</span> <span class="hljs-number">0</span>;<br>}<br></code></pre></td></tr></tbody></table></figure><h3 id="带进度回调的写入" data-id="带进度回调的写入" class="notion-h"><a href="#带进度回调的写入" class="headerlink" title="带进度回调的写入"></a>带进度回调的写入</h3><figure class="highlight c"><table><tbody><tr><td class="code"><pre><code class="hljs c"><span class="hljs-type">void</span> <span class="hljs-title function_">progress_callback</span><span class="hljs-params">(<span class="hljs-type">ssize_t</span> done)</span> {<br>    <span class="hljs-built_in">printf</span>(<span class="hljs-string">"Transferred %zd bytes\n"</span>, done);<br>}<br><br><span class="hljs-type">int</span> <span class="hljs-title function_">write_with_progress</span><span class="hljs-params">(<span class="hljs-keyword">struct</span> <span class="hljs-type">sunxi_efex_ctx_t</span> *ctx)</span> {<br>    <span class="hljs-type">char</span> large_data[<span class="hljs-number">1024</span> * <span class="hljs-number">1024</span>];  <span class="hljs-comment">// 1MB 数据</span><br>    <br>    <span class="hljs-type">int</span> ret = sunxi_efex_fel_write_cb(&amp;ctx, <span class="hljs-number">0x40000000</span>, large_data,<br>                                       <span class="hljs-keyword">sizeof</span>(large_data), progress_callback);<br>    <br>    <span class="hljs-keyword">return</span> ret;<br>}<br></code></pre></td></tr></tbody></table></figure>]]>
    </content>
    <id>https://gloomyghost.com/posts/libefex-fel-mode/</id>
    <link href="https://gloomyghost.com/posts/libefex-fel-mode/"/>
    <published>2026-04-29T16:00:00.000Z</published>
    <summary>
      <![CDATA[<h1 id="libefex-FEL-模式操作" data-id="libefex-FEL-模式操作" class="notion-h"><a href="#libefex-FEL-模式操作" class="headerlink" title="libefex FEL 模式操作]]>
    </summary>
    <title>libefex FEL 模式操作详解：内存读写与代码执行</title>
    <updated>2026-05-29T20:33:31.219Z</updated>
  </entry>
  <entry>
    <author>
      <name>柚木 鉉</name>
    </author>
    <category term="嵌入式" scheme="https://gloomyghost.com/tags/%E5%B5%8C%E5%85%A5%E5%BC%8F/"/>
    <category term="C" scheme="https://gloomyghost.com/tags/C/"/>
    <category term="libefex" scheme="https://gloomyghost.com/tags/libefex/"/>
    <category term="全志芯片" scheme="https://gloomyghost.com/tags/%E5%85%A8%E5%BF%97%E8%8A%AF%E7%89%87/"/>
    <category term="FES模式" scheme="https://gloomyghost.com/tags/FES%E6%A8%A1%E5%BC%8F/"/>
    <content>
      <![CDATA[<h1 id="libefex-FES-模式操作" data-id="libefex-FES-模式操作" class="notion-h"><a href="#libefex-FES-模式操作" class="headerlink" title="libefex FES 模式操作"></a>libefex FES 模式操作</h1><h2 id="FES-模式简介" data-id="FES-模式简介" class="notion-h"><a href="#FES-模式简介" class="headerlink" title="FES 模式简介"></a>FES 模式简介</h2><p>FES (Firmware Extraction Stage) 是 FEL 模式的升级版本，提供更高级的闪存操作能力。FES 模式需要先在 FEL 模式下加载特定固件才能进入。</p><h3 id="FES-vs-FEL-对比" data-id="FES-vs-FEL-对比" class="notion-h"><a href="#FES-vs-FEL-对比" class="headerlink" title="FES vs FEL 对比"></a>FES vs FEL 对比</h3><table><thead><tr><th>特性</th><th>FEL 模式</th><th>FES 模式</th></tr></thead><tbody><tr><td>内存访问</td><td>直接读写</td><td>通过固件代理</td></tr><tr><td>闪存操作</td><td>不支持</td><td>完整支持</td></tr><tr><td>代码执行</td><td>直接执行</td><td>通过固件</td></tr><tr><td>设备状态</td><td>BootROM</td><td>固件运行</td></tr></tbody></table><h2 id="FES-模式检查" data-id="FES-模式检查" class="notion-h"><a href="#FES-模式检查" class="headerlink" title="FES 模式检查"></a>FES 模式检查</h2><p>FES 操作前需要确认设备处于 <code>DEVICE_MODE_SRV</code> 模式：</p><p>源码位置: <code>src/efex-usb.c:106-108</code></p><figure class="highlight c"><table><tbody><tr><td class="code"><pre><code class="hljs c"><span class="hljs-comment">// FES 传输函数中的模式检查</span><br><span class="hljs-keyword">if</span> (ctx-&gt;resp.mode != DEVICE_MODE_SRV) {<br>    <span class="hljs-keyword">return</span> EFEX_ERR_INVALID_PARAM;<br>}<br></code></pre></td></tr></tbody></table></figure><h2 id="存储查询实现" data-id="存储查询实现" class="notion-h"><a href="#存储查询实现" class="headerlink" title="存储查询实现"></a>存储查询实现</h2><p>源码位置: <code>src/efex-fes.c:13-16</code></p><figure class="highlight c"><table><tbody><tr><td class="code"><pre><code class="hljs c"><span class="hljs-type">int</span> <span class="hljs-title function_">sunxi_efex_fes_query_storage</span><span class="hljs-params">(<span class="hljs-type">const</span> <span class="hljs-keyword">struct</span> <span class="hljs-type">sunxi_efex_ctx_t</span> *ctx, </span><br><span class="hljs-params">                                  <span class="hljs-type">uint32_t</span> *storage_type)</span> {<br>    <span class="hljs-keyword">return</span> sunxi_usb_fes_xfer(ctx, FES_XFER_RECV, <br>                              EFEX_CMD_FES_QUERY_STORAGE, <br>                              <span class="hljs-literal">NULL</span>, <span class="hljs-number">0</span>, <br>                              (<span class="hljs-type">char</span> *) storage_type,<br>                              <span class="hljs-keyword">sizeof</span>(<span class="hljs-type">uint32_t</span>));<br>}<br></code></pre></td></tr></tbody></table></figure><p>该函数发送 <code>EFEX_CMD_FES_QUERY_STORAGE</code> 命令并接收存储类型响应。</p><h3 id="存储查询流程图" data-id="存储查询流程图" class="notion-h"><a href="#存储查询流程图" class="headerlink" title="存储查询流程图"></a>存储查询流程图</h3><pre class="mermaid">sequenceDiagram    participant API as sunxi_efex_fes_query_storage    participant USB as sunxi_usb_fes_xfer    participant Device as FES 固件    API-&gt;&gt;USB: 调用 fes_xfer(FES_XFER_RECV)    USB-&gt;&gt;Device: 发送 QUERY_STORAGE 命令    USB-&gt;&gt;Device: cmd=0x0209, magic=AWUC        Device-&gt;&gt;Device: 检测存储类型        Device--&gt;&gt;USB: 返回存储类型值    USB--&gt;&gt;API: 接收 4 字节响应        API--&gt;&gt;API: 返回 storage_type</pre><h3 id="其他查询函数" data-id="其他查询函数" class="notion-h"><a href="#其他查询函数" class="headerlink" title="其他查询函数"></a>其他查询函数</h3><p>源码位置: <code>src/efex-fes.c:18-39</code></p><figure class="highlight c"><table><tbody><tr><td class="code"><pre><code class="hljs c"><span class="hljs-comment">// 查询安全状态</span><br><span class="hljs-type">int</span> <span class="hljs-title function_">sunxi_efex_fes_query_secure</span><span class="hljs-params">(<span class="hljs-type">const</span> <span class="hljs-keyword">struct</span> <span class="hljs-type">sunxi_efex_ctx_t</span> *ctx, </span><br><span class="hljs-params">                                 <span class="hljs-type">uint32_t</span> *secure_type)</span> {<br>    <span class="hljs-keyword">return</span> sunxi_usb_fes_xfer(ctx, FES_XFER_RECV, <br>                              EFEX_CMD_FES_QUERY_SECURE, <br>                              <span class="hljs-literal">NULL</span>, <span class="hljs-number">0</span>, <br>                              (<span class="hljs-type">char</span> *) secure_type,<br>                              <span class="hljs-keyword">sizeof</span>(<span class="hljs-type">uint32_t</span>));<br>}<br><br><span class="hljs-comment">// 探测闪存大小</span><br><span class="hljs-type">int</span> <span class="hljs-title function_">sunxi_efex_fes_probe_flash_size</span><span class="hljs-params">(<span class="hljs-type">const</span> <span class="hljs-keyword">struct</span> <span class="hljs-type">sunxi_efex_ctx_t</span> *ctx, </span><br><span class="hljs-params">                                     <span class="hljs-type">uint32_t</span> *flash_size)</span> {<br>    <span class="hljs-keyword">return</span> sunxi_usb_fes_xfer(ctx, FES_XFER_RECV, <br>                              EFEX_CMD_FES_FLASH_SIZE_PROBE, <br>                              <span class="hljs-literal">NULL</span>, <span class="hljs-number">0</span>, <br>                              (<span class="hljs-type">char</span> *) flash_size,<br>                              <span class="hljs-keyword">sizeof</span>(<span class="hljs-type">uint32_t</span>));<br>}<br><br><span class="hljs-comment">// 设置闪存开关</span><br><span class="hljs-type">int</span> <span class="hljs-title function_">sunxi_efex_fes_flash_set_onoff</span><span class="hljs-params">(<span class="hljs-type">const</span> <span class="hljs-keyword">struct</span> <span class="hljs-type">sunxi_efex_ctx_t</span> *ctx, </span><br><span class="hljs-params">                                   <span class="hljs-type">const</span> <span class="hljs-type">uint32_t</span> *storage_type,</span><br><span class="hljs-params">                                   <span class="hljs-type">const</span> <span class="hljs-type">uint32_t</span> on_off)</span> {<br>    <span class="hljs-type">const</span> <span class="hljs-class"><span class="hljs-keyword">struct</span> <span class="hljs-title">sunxi_fes_flash_t</span> <span class="hljs-title">fes_flash</span> =</span> {<br>        .flash_type = cpu_to_le32(*storage_type),<br>    };<br>    <br>    <span class="hljs-type">const</span> <span class="hljs-class"><span class="hljs-keyword">enum</span> <span class="hljs-title">sunxi_efex_cmd_t</span> <span class="hljs-title">cmd</span> =</span> on_off <br>        ? EFEX_CMD_FES_FLASH_SET_ON <br>        : EFEX_CMD_FES_FLASH_SET_OFF;<br>        <br>    <span class="hljs-keyword">return</span> sunxi_usb_fes_xfer(ctx, FES_XFER_NONE, cmd,<br>                              (<span class="hljs-type">const</span> <span class="hljs-type">char</span> *) &amp;fes_flash, <br>                              <span class="hljs-keyword">sizeof</span>(fes_flash), <br>                              <span class="hljs-literal">NULL</span>, <span class="hljs-number">0</span>);<br>}<br><br><span class="hljs-comment">// 获取芯片 ID</span><br><span class="hljs-type">int</span> <span class="hljs-title function_">sunxi_efex_fes_get_chipid</span><span class="hljs-params">(<span class="hljs-type">const</span> <span class="hljs-keyword">struct</span> <span class="hljs-type">sunxi_efex_ctx_t</span> *ctx, </span><br><span class="hljs-params">                               <span class="hljs-type">const</span> <span class="hljs-type">char</span> *chip_id)</span> {<br>    <span class="hljs-keyword">return</span> sunxi_usb_fes_xfer(ctx, FES_XFER_RECV, <br>                              EFEX_CMD_FES_GET_CHIPID, <br>                              <span class="hljs-literal">NULL</span>, <span class="hljs-number">0</span>, <br>                              chip_id, <span class="hljs-number">129</span>);<br>}<br></code></pre></td></tr></tbody></table></figure><h2 id="FES-数据传输结构" data-id="FES-数据传输结构" class="notion-h"><a href="#FES-数据传输结构" class="headerlink" title="FES 数据传输结构"></a>FES 数据传输结构</h2><p>源码位置: <code>includes/efex-protocol.h:175-181</code></p><figure class="highlight c"><table><tbody><tr><td class="code"><pre><code class="hljs c">EFEX_PACKED_BEGIN<br><span class="hljs-class"><span class="hljs-keyword">struct</span> <span class="hljs-title">sunxi_fes_trans_t</span> {</span><br>    <span class="hljs-type">uint32_t</span> addr;     <span class="hljs-comment">// 目标地址</span><br>    <span class="hljs-type">uint32_t</span> len;      <span class="hljs-comment">// 数据长度</span><br>    <span class="hljs-type">uint32_t</span> flags;    <span class="hljs-comment">// 传输标志 (数据类型 + 完成标记)</span><br>} EFEX_PACKED;<br>EFEX_PACKED_END<br></code></pre></td></tr></tbody></table></figure><h2 id="FES-数据上传下载实现" data-id="FES-数据上传下载实现" class="notion-h"><a href="#FES-数据上传下载实现" class="headerlink" title="FES 数据上传下载实现"></a>FES 数据上传下载实现</h2><h3 id="核心-up-down-函数" data-id="核心-up-down-函数" class="notion-h"><a href="#核心-up-down-函数" class="headerlink" title="核心 up_down 函数"></a>核心 up_down 函数</h3><p>源码位置: <code>src/efex-fes.c:41-91</code></p><figure class="highlight c"><table><tbody><tr><td class="code"><pre><code class="hljs c"><span class="hljs-type">static</span> <span class="hljs-type">int</span> <span class="hljs-title function_">sunxi_efex_fes_up_down</span><span class="hljs-params">(<span class="hljs-type">const</span> <span class="hljs-keyword">struct</span> <span class="hljs-type">sunxi_efex_ctx_t</span> *ctx, </span><br><span class="hljs-params">                                  <span class="hljs-type">const</span> <span class="hljs-type">char</span> *buf, <span class="hljs-type">const</span> <span class="hljs-type">ssize_t</span> len,</span><br><span class="hljs-params">                                  <span class="hljs-type">const</span> <span class="hljs-type">uint32_t</span> addr, </span><br><span class="hljs-params">                                  <span class="hljs-type">const</span> <span class="hljs-keyword">enum</span> <span class="hljs-type">sunxi_fes_data_type_t</span> type,</span><br><span class="hljs-params">                                  <span class="hljs-type">const</span> <span class="hljs-keyword">enum</span> <span class="hljs-type">sunxi_efex_cmd_t</span> cmd)</span> {<br>    <span class="hljs-keyword">if</span> (!ctx || !buf) {<br>        <span class="hljs-keyword">return</span> EFEX_ERR_NULL_PTR;<br>    }<br><br>    <span class="hljs-type">int</span> ret = EFEX_ERR_SUCCESS;<br>    <span class="hljs-keyword">if</span> (len &lt;= <span class="hljs-number">0</span>) {<br>        <span class="hljs-keyword">return</span> EFEX_ERR_INVALID_PARAM;<br>    }<br><br>    <span class="hljs-type">uint32_t</span> remain_data = (<span class="hljs-type">uint32_t</span>) len;<br>    <span class="hljs-type">const</span> <span class="hljs-type">char</span> *buff_ptr = (<span class="hljs-type">char</span> *) buf;<br>    <span class="hljs-type">uint32_t</span> addr_cur = addr;<br>    <span class="hljs-class"><span class="hljs-keyword">enum</span> <span class="hljs-title">sunxi_fes_data_type_t</span> <span class="hljs-title">current_type</span> =</span> type;<br>    <span class="hljs-type">const</span> <span class="hljs-type">bool</span> is_data_type = (type &amp; SUNXI_EFEX_DATA_TYPE_MASK) != <span class="hljs-number">0</span>;<br><br>    <span class="hljs-keyword">while</span> (remain_data &gt; <span class="hljs-number">0</span>) {<br>        <span class="hljs-comment">// 计算本次传输长度 (最大 64KB)</span><br>        <span class="hljs-type">const</span> <span class="hljs-type">uint32_t</span> length = (remain_data &gt; EFEX_CODE_MAX_SIZE) <br>                               ? EFEX_CODE_MAX_SIZE <br>                               : remain_data;<br>        remain_data -= length;<br><br>        <span class="hljs-comment">// 如果是最后一块数据，添加完成标记</span><br>        <span class="hljs-keyword">if</span> (remain_data == <span class="hljs-number">0</span>) {<br>            current_type |= SUNXI_EFEX_TRANS_FINISH_TAG;<br>        }<br><br>        <span class="hljs-comment">// 构建传输结构</span><br>        <span class="hljs-type">const</span> <span class="hljs-class"><span class="hljs-keyword">struct</span> <span class="hljs-title">sunxi_fes_trans_t</span> <span class="hljs-title">trans</span> =</span> {<br>            .addr = addr_cur,<br>            .len = length,<br>            .flags = current_type,<br>        };<br><br>        <span class="hljs-comment">// 根据命令类型确定传输方向</span><br>        <span class="hljs-type">const</span> <span class="hljs-class"><span class="hljs-keyword">enum</span> <span class="hljs-title">sunxi_usb_fes_xfer_type_t</span> <span class="hljs-title">xfer_type</span> =</span> <br>            (cmd == EFEX_CMD_FES_DOWN) ? FES_XFER_SEND : FES_XFER_RECV;<br><br>        <span class="hljs-comment">// 执行 USB 传输</span><br>        ret = sunxi_usb_fes_xfer(ctx, xfer_type, cmd,<br>                                 (<span class="hljs-type">const</span> <span class="hljs-type">char</span> *) &amp;trans, <span class="hljs-keyword">sizeof</span>(trans),<br>                                 buff_ptr, length);<br><br>        <span class="hljs-comment">// 根据数据类型更新地址</span><br>        addr_cur += is_data_type ? length : (length / <span class="hljs-number">512</span>);<br>        buff_ptr += length;<br><br>        <span class="hljs-comment">// 检查错误</span><br>        <span class="hljs-keyword">if</span> (ret != EFEX_ERR_SUCCESS) {<br>            <span class="hljs-keyword">return</span> ret;<br>        }<br>    }<br><br>    <span class="hljs-keyword">return</span> EFEX_ERR_SUCCESS;<br>}<br></code></pre></td></tr></tbody></table></figure><h3 id="FES-up-down-详细流程图" data-id="FES-up-down-详细流程图" class="notion-h"><a href="#FES-up-down-详细流程图" class="headerlink" title="FES up_down 详细流程图"></a>FES up_down 详细流程图</h3><pre class="mermaid">sequenceDiagram    participant API as sunxi_efex_fes_up_down    participant Iter as 分块迭代    participant USB as sunxi_usb_fes_xfer    participant Device as FES 固件    API-&gt;&gt;API: 参数检查    API-&gt;&gt;API: 初始化 remain_data, addr_cur        API-&gt;&gt;Iter: 进入 while 循环        loop 每个数据块        Iter-&gt;&gt;Iter: 计算本次长度 max 64KB        Iter-&gt;&gt;Iter: remain_data -= length                Iter-&gt;&gt;Iter: 检查最后一块?        Iter-&gt;&gt;Iter: 添加 FINISH_TAG                Iter-&gt;&gt;Iter: 构建 trans 结构<br>addr, len, flags                Iter-&gt;&gt;USB: sunxi_usb_fes_xfer        USB-&gt;&gt;Device: 发送 trans 结构        USB-&gt;&gt;Device: 发送/接收数据块                Device--&gt;&gt;USB: 状态响应        USB--&gt;&gt;Iter: 返回结果                Iter-&gt;&gt;Iter: 更新 addr_cur, buff_ptr                Iter-&gt;&gt;Iter: 检查错误?    end        Iter--&gt;&gt;API: 循环结束    API--&gt;&gt;API: 返回 SUCCESS</pre><h3 id="数据下载函数" data-id="数据下载函数" class="notion-h"><a href="#数据下载函数" class="headerlink" title="数据下载函数"></a>数据下载函数</h3><p>源码位置: <code>src/efex-fes.c:93-96</code></p><figure class="highlight c"><table><tbody><tr><td class="code"><pre><code class="hljs c"><span class="hljs-type">int</span> <span class="hljs-title function_">sunxi_efex_fes_down</span><span class="hljs-params">(<span class="hljs-type">const</span> <span class="hljs-keyword">struct</span> <span class="hljs-type">sunxi_efex_ctx_t</span> *ctx, </span><br><span class="hljs-params">                        <span class="hljs-type">const</span> <span class="hljs-type">char</span> *buf, <span class="hljs-type">const</span> <span class="hljs-type">ssize_t</span> len, </span><br><span class="hljs-params">                        <span class="hljs-type">const</span> <span class="hljs-type">uint32_t</span> addr,</span><br><span class="hljs-params">                        <span class="hljs-type">const</span> <span class="hljs-keyword">enum</span> <span class="hljs-type">sunxi_fes_data_type_t</span> type)</span> {<br>    <span class="hljs-keyword">return</span> sunxi_efex_fes_up_down(ctx, buf, len, addr, type, <br>                                  EFEX_CMD_FES_DOWN);<br>}<br></code></pre></td></tr></tbody></table></figure><h3 id="数据上传函数" data-id="数据上传函数" class="notion-h"><a href="#数据上传函数" class="headerlink" title="数据上传函数"></a>数据上传函数</h3><p>源码位置: <code>src/efex-fes.c:98-101</code></p><figure class="highlight c"><table><tbody><tr><td class="code"><pre><code class="hljs c"><span class="hljs-type">int</span> <span class="hljs-title function_">sunxi_efex_fes_up</span><span class="hljs-params">(<span class="hljs-type">const</span> <span class="hljs-keyword">struct</span> <span class="hljs-type">sunxi_efex_ctx_t</span> *ctx, </span><br><span class="hljs-params">                      <span class="hljs-type">const</span> <span class="hljs-type">char</span> *buf, <span class="hljs-type">const</span> <span class="hljs-type">ssize_t</span> len, </span><br><span class="hljs-params">                      <span class="hljs-type">const</span> <span class="hljs-type">uint32_t</span> addr,</span><br><span class="hljs-params">                      <span class="hljs-type">const</span> <span class="hljs-keyword">enum</span> <span class="hljs-type">sunxi_fes_data_type_t</span> type)</span> {<br>    <span class="hljs-keyword">return</span> sunxi_efex_fes_up_down(ctx, buf, len, addr, type, <br>                                  EFEX_CMD_FES_UP);<br>}<br></code></pre></td></tr></tbody></table></figure><h2 id="验证相关结构体" data-id="验证相关结构体" class="notion-h"><a href="#验证相关结构体" class="headerlink" title="验证相关结构体"></a>验证相关结构体</h2><p>源码位置: <code>includes/efex-protocol.h:184-204</code></p><figure class="highlight c"><table><tbody><tr><td class="code"><pre><code class="hljs c"><span class="hljs-comment">// 验证值请求</span><br>EFEX_PACKED_BEGIN<br><span class="hljs-class"><span class="hljs-keyword">struct</span> <span class="hljs-title">sunxi_fes_verify_value_t</span> {</span><br>    <span class="hljs-type">uint32_t</span> addr;<br>    <span class="hljs-type">uint64_t</span> size;<br>} EFEX_PACKED;<br>EFEX_PACKED_END<br><br><span class="hljs-comment">// 验证状态请求</span><br>EFEX_PACKED_BEGIN<br><span class="hljs-class"><span class="hljs-keyword">struct</span> <span class="hljs-title">sunxi_fes_verify_status_t</span> {</span><br>    <span class="hljs-type">uint32_t</span> addr;<br>    <span class="hljs-type">uint32_t</span> size;<br>    <span class="hljs-type">uint32_t</span> tag;<br>} EFEX_PACKED;<br>EFEX_PACKED_END<br><br><span class="hljs-comment">// 验证响应</span><br>EFEX_PACKED_BEGIN<br><span class="hljs-class"><span class="hljs-keyword">struct</span> <span class="hljs-title">sunxi_fes_verify_resp_t</span> {</span><br>    <span class="hljs-type">uint32_t</span> flag;<br>    <span class="hljs-type">int32_t</span> fes_crc;<br>    <span class="hljs-type">int32_t</span> media_crc;<br>} EFEX_PACKED;<br>EFEX_PACKED_END<br></code></pre></td></tr></tbody></table></figure><h2 id="验证函数实现" data-id="验证函数实现" class="notion-h"><a href="#验证函数实现" class="headerlink" title="验证函数实现"></a>验证函数实现</h2><p>源码位置: <code>src/efex-fes.c:103-133</code></p><figure class="highlight c"><table><tbody><tr><td class="code"><pre><code class="hljs c"><span class="hljs-comment">// 验证数据值</span><br><span class="hljs-type">int</span> <span class="hljs-title function_">sunxi_efex_fes_verify_value</span><span class="hljs-params">(<span class="hljs-type">const</span> <span class="hljs-keyword">struct</span> <span class="hljs-type">sunxi_efex_ctx_t</span> *ctx, </span><br><span class="hljs-params">                                <span class="hljs-type">const</span> <span class="hljs-type">uint32_t</span> addr, <span class="hljs-type">const</span> <span class="hljs-type">uint64_t</span> size,</span><br><span class="hljs-params">                                <span class="hljs-type">const</span> <span class="hljs-keyword">struct</span> <span class="hljs-type">sunxi_fes_verify_resp_t</span> *buf)</span> {<br>    <span class="hljs-type">const</span> <span class="hljs-class"><span class="hljs-keyword">struct</span> <span class="hljs-title">sunxi_fes_verify_value_t</span> <span class="hljs-title">verify_value</span> =</span> {<br>        .addr = cpu_to_le32(addr),<br>        .size = cpu_to_le64(size),<br>    };<br>    <br>    <span class="hljs-keyword">return</span> sunxi_usb_fes_xfer(ctx, FES_XFER_RECV, <br>                              EFEX_CMD_FES_VERIFY_VALUE,<br>                              (<span class="hljs-type">const</span> <span class="hljs-type">char</span> *) &amp;verify_value, <br>                              <span class="hljs-keyword">sizeof</span>(verify_value),<br>                              (<span class="hljs-type">const</span> <span class="hljs-type">char</span> *) buf, <br>                              <span class="hljs-keyword">sizeof</span>(<span class="hljs-keyword">struct</span> <span class="hljs-type">sunxi_fes_verify_resp_t</span>));<br>}<br><br><span class="hljs-comment">// 验证状态</span><br><span class="hljs-type">int</span> <span class="hljs-title function_">sunxi_efex_fes_verify_status</span><span class="hljs-params">(<span class="hljs-type">const</span> <span class="hljs-keyword">struct</span> <span class="hljs-type">sunxi_efex_ctx_t</span> *ctx, </span><br><span class="hljs-params">                                 <span class="hljs-type">const</span> <span class="hljs-type">uint32_t</span> tag,</span><br><span class="hljs-params">                                 <span class="hljs-type">const</span> <span class="hljs-keyword">struct</span> <span class="hljs-type">sunxi_fes_verify_resp_t</span> *buf)</span> {<br>    <span class="hljs-type">const</span> <span class="hljs-class"><span class="hljs-keyword">struct</span> <span class="hljs-title">sunxi_fes_verify_status_t</span> <span class="hljs-title">verify_status</span> =</span> {<br>        .addr = <span class="hljs-number">0x0</span>,<br>        .size = <span class="hljs-number">0x0</span>,<br>        .tag = cpu_to_le32(tag),<br>    };<br>    <br>    <span class="hljs-keyword">return</span> sunxi_usb_fes_xfer(ctx, FES_XFER_RECV, <br>                              EFEX_CMD_FES_VERIFY_STATUS,<br>                              (<span class="hljs-type">const</span> <span class="hljs-type">char</span> *) &amp;verify_status, <br>                              <span class="hljs-keyword">sizeof</span>(verify_status),<br>                              (<span class="hljs-type">const</span> <span class="hljs-type">char</span> *) buf, <br>                              <span class="hljs-keyword">sizeof</span>(<span class="hljs-keyword">struct</span> <span class="hljs-type">sunxi_fes_verify_resp_t</span>));<br>}<br></code></pre></td></tr></tbody></table></figure><h2 id="工具模式切换" data-id="工具模式切换" class="notion-h"><a href="#工具模式切换" class="headerlink" title="工具模式切换"></a>工具模式切换</h2><p>源码位置: <code>src/efex-fes.c:135-144</code></p><figure class="highlight c"><table><tbody><tr><td class="code"><pre><code class="hljs c"><span class="hljs-type">int</span> <span class="hljs-title function_">sunxi_efex_fes_tool_mode</span><span class="hljs-params">(<span class="hljs-type">const</span> <span class="hljs-keyword">struct</span> <span class="hljs-type">sunxi_efex_ctx_t</span> *ctx, </span><br><span class="hljs-params">                             <span class="hljs-type">const</span> <span class="hljs-keyword">enum</span> <span class="hljs-type">sunxi_fes_tool_mode_t</span> tool_mode,</span><br><span class="hljs-params">                             <span class="hljs-type">const</span> <span class="hljs-keyword">enum</span> <span class="hljs-type">sunxi_fes_tool_mode_t</span> next_mode)</span> {<br>    <span class="hljs-type">const</span> <span class="hljs-class"><span class="hljs-keyword">struct</span> <span class="hljs-title">sunxi_fes_set_tool_mode_t</span> <span class="hljs-title">fes_tool_mode</span> =</span> {<br>        .tool_mode = cpu_to_le32(tool_mode),<br>        .next_mode = cpu_to_le32(next_mode),<br>        .reserved = <span class="hljs-number">0x0</span>,<br>    };<br>    <br>    <span class="hljs-keyword">return</span> sunxi_usb_fes_xfer(ctx, FES_XFER_NONE, <br>                              EFEX_CMD_FES_TOOL_MODE,<br>                              (<span class="hljs-type">const</span> <span class="hljs-type">char</span> *) &amp;fes_tool_mode, <br>                              <span class="hljs-keyword">sizeof</span>(fes_tool_mode),<br>                              <span class="hljs-literal">NULL</span>, <span class="hljs-number">0</span>);<br>}<br></code></pre></td></tr></tbody></table></figure><h3 id="工具模式定义" data-id="工具模式定义" class="notion-h"><a href="#工具模式定义" class="headerlink" title="工具模式定义"></a>工具模式定义</h3><p>源码位置: <code>includes/efex-protocol.h:105-111</code></p><figure class="highlight c"><table><tbody><tr><td class="code"><pre><code class="hljs c"><span class="hljs-class"><span class="hljs-keyword">enum</span> <span class="hljs-title">sunxi_fes_tool_mode_t</span> {</span><br>    TOOL_MODE_NORMAL = <span class="hljs-number">0x1</span>,    <span class="hljs-comment">// 正常模式</span><br>    TOOL_MODE_REBOOT = <span class="hljs-number">0x2</span>,    <span class="hljs-comment">// 重启</span><br>    TOOL_MODE_POWEROFF = <span class="hljs-number">0x3</span>,  <span class="hljs-comment">// 关机</span><br>    TOOL_MODE_REUPDATE = <span class="hljs-number">0x4</span>,  <span class="hljs-comment">// 重新更新</span><br>    TOOL_MODE_BOOT = <span class="hljs-number">0x5</span>,      <span class="hljs-comment">// 启动系统</span><br>};<br></code></pre></td></tr></tbody></table></figure><h2 id="FES-传输流程图" data-id="FES-传输流程图" class="notion-h"><a href="#FES-传输流程图" class="headerlink" title="FES 传输流程图"></a>FES 传输流程图</h2><pre class="mermaid">flowchart TD    A[sunxi_efex_fes_down/up] --&gt; B[检查参数]        B --&gt; C[初始化传输状态]        C --&gt; D{remain_data &gt; 0?}        D --&gt;|否| E[返回成功]        D --&gt;|是| F[计算本次长度<br>max 64KB]        F --&gt; G[remain_data -= length]        G --&gt; H{最后一块?}        H --&gt;|是| I[添加 FINISH_TAG]    H --&gt;|否| J[保持原类型]        I --&gt; K[构建 trans 结构]    J --&gt; K        K --&gt; L[调用 sunxi_usb_fes_xfer]        L --&gt; M[更新地址和指针]        M --&gt; N{传输成功?}        N --&gt;|否| O[返回错误]        N --&gt;|是| P[更新 addr_cur]        P --&gt; D</pre><h2 id="FES-API-使用示例" data-id="FES-API-使用示例" class="notion-h"><a href="#FES-API-使用示例" class="headerlink" title="FES API 使用示例"></a>FES API 使用示例</h2><h3 id="存储查询示例" data-id="存储查询示例" class="notion-h"><a href="#存储查询示例" class="headerlink" title="存储查询示例"></a>存储查询示例</h3><figure class="highlight c"><table><tbody><tr><td class="code"><pre><code class="hljs c"><span class="hljs-meta">#<span class="hljs-keyword">include</span> <span class="hljs-string">&lt;libefex.h&gt;</span></span><br><br><span class="hljs-type">void</span> <span class="hljs-title function_">query_storage_example</span><span class="hljs-params">(<span class="hljs-keyword">struct</span> <span class="hljs-type">sunxi_efex_ctx_t</span> *ctx)</span> {<br>    <span class="hljs-comment">// 查询存储类型</span><br>    <span class="hljs-type">uint32_t</span> storage_type;<br>    <span class="hljs-type">int</span> ret = sunxi_efex_fes_query_storage(ctx, &amp;storage_type);<br>    <br>    <span class="hljs-keyword">if</span> (ret == EFEX_ERR_SUCCESS) {<br>        <span class="hljs-keyword">switch</span> (storage_type) {<br>            <span class="hljs-keyword">case</span> <span class="hljs-number">1</span>:  <span class="hljs-comment">// NAND</span><br>                <span class="hljs-built_in">printf</span>(<span class="hljs-string">"Storage: NAND Flash\n"</span>);<br>                <span class="hljs-keyword">break</span>;<br>            <span class="hljs-keyword">case</span> <span class="hljs-number">2</span>:  <span class="hljs-comment">// SPI</span><br>                <span class="hljs-built_in">printf</span>(<span class="hljs-string">"Storage: SPI Flash\n"</span>);<br>                <span class="hljs-keyword">break</span>;<br>            <span class="hljs-keyword">case</span> <span class="hljs-number">3</span>:  <span class="hljs-comment">// SDC</span><br>                <span class="hljs-built_in">printf</span>(<span class="hljs-string">"Storage: SD/MMC Card\n"</span>);<br>                <span class="hljs-keyword">break</span>;<br>            <span class="hljs-keyword">default</span>:<br>                <span class="hljs-built_in">printf</span>(<span class="hljs-string">"Storage: Unknown (%u)\n"</span>, storage_type);<br>                <span class="hljs-keyword">break</span>;<br>        }<br>    }<br>    <br>    <span class="hljs-comment">// 探测闪存大小</span><br>    <span class="hljs-type">uint32_t</span> flash_size;<br>    ret = sunxi_efex_fes_probe_flash_size(ctx, &amp;flash_size);<br>    <span class="hljs-keyword">if</span> (ret == EFEX_ERR_SUCCESS) {<br>        <span class="hljs-built_in">printf</span>(<span class="hljs-string">"Flash size: %u MB\n"</span>, flash_size / (<span class="hljs-number">1024</span> * <span class="hljs-number">1024</span>));<br>    }<br>    <br>    <span class="hljs-comment">// 获取芯片 ID</span><br>    <span class="hljs-type">char</span> chip_id[<span class="hljs-number">129</span>];<br>    ret = sunxi_efex_fes_get_chipid(ctx, chip_id);<br>    <span class="hljs-keyword">if</span> (ret == EFEX_ERR_SUCCESS) {<br>        <span class="hljs-built_in">printf</span>(<span class="hljs-string">"Chip ID: %s\n"</span>, chip_id);<br>    }<br>}<br></code></pre></td></tr></tbody></table></figure><h3 id="数据下载示例" data-id="数据下载示例" class="notion-h"><a href="#数据下载示例" class="headerlink" title="数据下载示例"></a>数据下载示例</h3><figure class="highlight c"><table><tbody><tr><td class="code"><pre><code class="hljs c"><span class="hljs-type">void</span> <span class="hljs-title function_">download_firmware_example</span><span class="hljs-params">(<span class="hljs-keyword">struct</span> <span class="hljs-type">sunxi_efex_ctx_t</span> *ctx)</span> {<br>    <span class="hljs-comment">// 准备固件数据</span><br>    <span class="hljs-type">char</span> firmware_data[<span class="hljs-number">4096</span>];<br>    <span class="hljs-comment">// ... 加载固件数据 ...</span><br>    <br>    <span class="hljs-type">uint32_t</span> flash_addr = <span class="hljs-number">0x00000000</span>;  <span class="hljs-comment">// 闪存起始地址</span><br>    <br>    <span class="hljs-comment">// 下载数据到闪存 (数据类型模式)</span><br>    <span class="hljs-type">int</span> ret = sunxi_efex_fes_down(ctx, firmware_data, <br>                                  <span class="hljs-keyword">sizeof</span>(firmware_data),<br>                                  flash_addr, <br>                                  SUNXI_EFEX_DATA_TYPE_DATA);<br>    <br>    <span class="hljs-keyword">if</span> (ret != EFEX_ERR_SUCCESS) {<br>        <span class="hljs-built_in">printf</span>(<span class="hljs-string">"Download failed: %s\n"</span>, sunxi_efex_strerror(ret));<br>    } <span class="hljs-keyword">else</span> {<br>        <span class="hljs-built_in">printf</span>(<span class="hljs-string">"Download completed successfully\n"</span>);<br>    }<br>}<br></code></pre></td></tr></tbody></table></figure><h3 id="设备重启示例" data-id="设备重启示例" class="notion-h"><a href="#设备重启示例" class="headerlink" title="设备重启示例"></a>设备重启示例</h3><figure class="highlight c"><table><tbody><tr><td class="code"><pre><code class="hljs c"><span class="hljs-type">void</span> <span class="hljs-title function_">reboot_device_example</span><span class="hljs-params">(<span class="hljs-keyword">struct</span> <span class="hljs-type">sunxi_efex_ctx_t</span> *ctx)</span> {<br>    <span class="hljs-comment">// 设置工具模式为重启</span><br>    <span class="hljs-type">int</span> ret = sunxi_efex_fes_tool_mode(ctx, <br>                                        TOOL_MODE_REBOOT,<br>                                        TOOL_MODE_NORMAL);<br>    <br>    <span class="hljs-keyword">if</span> (ret == EFEX_ERR_SUCCESS) {<br>        <span class="hljs-built_in">printf</span>(<span class="hljs-string">"Device will reboot...\n"</span>);<br>    }<br>}<br></code></pre></td></tr></tbody></table></figure>]]>
    </content>
    <id>https://gloomyghost.com/posts/libefex-fes-mode/</id>
    <link href="https://gloomyghost.com/posts/libefex-fes-mode/"/>
    <published>2026-04-29T16:00:00.000Z</published>
    <summary>
      <![CDATA[<h1 id="libefex-FES-模式操作" data-id="libefex-FES-模式操作" class="notion-h"><a href="#libefex-FES-模式操作" class="headerlink" title="libefex FES 模式操作]]>
    </summary>
    <title>libefex FES 模式操作详解：闪存访问与数据传输</title>
    <updated>2026-05-29T20:33:31.219Z</updated>
  </entry>
  <entry>
    <author>
      <name>柚木 鉉</name>
    </author>
    <category term="RISC-V" scheme="https://gloomyghost.com/tags/RISC-V/"/>
    <category term="ARM" scheme="https://gloomyghost.com/tags/ARM/"/>
    <category term="嵌入式" scheme="https://gloomyghost.com/tags/%E5%B5%8C%E5%85%A5%E5%BC%8F/"/>
    <category term="C" scheme="https://gloomyghost.com/tags/C/"/>
    <category term="libefex" scheme="https://gloomyghost.com/tags/libefex/"/>
    <category term="Payload" scheme="https://gloomyghost.com/tags/Payload/"/>
    <content>
      <![CDATA[<h1 id="libefex-架构-Payload" data-id="libefex-架构-Payload" class="notion-h"><a href="#libefex-架构-Payload" class="headerlink" title="libefex 架构 Payload"></a>libefex 架构 Payload</h1><h2 id="Payload-技术概述" data-id="Payload-技术概述" class="notion-h"><a href="#Payload-技术概述" class="headerlink" title="Payload 技术概述"></a>Payload 技术概述</h2><p>Payload 是一种机器码注入技术，用于在 FEL 模式下执行复杂的硬件操作。由于 FEL 只提供基础的内存读写和代码执行能力，要实现寄存器读写等高级操作，需要将特定的机器码写入设备内存并执行。</p><pre class="mermaid">flowchart TD    A[需要硬件操作] --&gt; B[选择 CPU 架构]        B --&gt; C{架构类型?}        C --&gt;|ARM32| D[ARM Payload]    C --&gt;|RISC-V| E[RISC-V Payload]    C --&gt;|AARCH64| F[暂不支持]        D --&gt; G[写入机器码到内存]    E --&gt; G        G --&gt; H[写入参数地址/值]        H --&gt; I[执行 Payload]        I --&gt; J[读取执行结果]</pre><h2 id="Payload-调度器结构" data-id="Payload-调度器结构" class="notion-h"><a href="#Payload-调度器结构" class="headerlink" title="Payload 调度器结构"></a>Payload 调度器结构</h2><p>源码位置: <code>includes/efex-payloads.h</code></p><figure class="highlight c"><table><tbody><tr><td class="code"><pre><code class="hljs c"><span class="hljs-class"><span class="hljs-keyword">struct</span> <span class="hljs-title">payloads_ops</span> {</span><br>    <span class="hljs-type">const</span> <span class="hljs-type">char</span> *name;                          <span class="hljs-comment">// 架构名称</span><br>    <span class="hljs-class"><span class="hljs-keyword">enum</span> <span class="hljs-title">sunxi_efex_fel_payloads_arch</span> <span class="hljs-title">arch</span>;</span>    <span class="hljs-comment">// 架构类型</span><br>    <br>    <span class="hljs-comment">// 读取寄存器</span><br>    <span class="hljs-type">int</span> (*readl)(<span class="hljs-type">const</span> <span class="hljs-keyword">struct</span> <span class="hljs-type">sunxi_efex_ctx_t</span> *ctx, <br>                 <span class="hljs-type">uint32_t</span> addr, <span class="hljs-type">uint32_t</span> *val);<br>    <br>    <span class="hljs-comment">// 写入寄存器</span><br>    <span class="hljs-type">int</span> (*writel)(<span class="hljs-type">const</span> <span class="hljs-keyword">struct</span> <span class="hljs-type">sunxi_efex_ctx_t</span> *ctx, <br>                  <span class="hljs-type">uint32_t</span> value, <span class="hljs-type">uint32_t</span> addr);<br>};<br></code></pre></td></tr></tbody></table></figure><h2 id="Payload-调度器实现" data-id="Payload-调度器实现" class="notion-h"><a href="#Payload-调度器实现" class="headerlink" title="Payload 调度器实现"></a>Payload 调度器实现</h2><p>源码位置: <code>src/efex-payloads.c</code></p><figure class="highlight c"><table><tbody><tr><td class="code"><pre><code class="hljs c"><span class="hljs-meta">#<span class="hljs-keyword">include</span> <span class="hljs-string">"efex-common.h"</span></span><br><span class="hljs-meta">#<span class="hljs-keyword">include</span> <span class="hljs-string">"efex-payloads.h"</span></span><br><span class="hljs-meta">#<span class="hljs-keyword">include</span> <span class="hljs-string">"efex-protocol.h"</span></span><br><br><span class="hljs-comment">// 外部架构操作结构</span><br><span class="hljs-keyword">extern</span> <span class="hljs-class"><span class="hljs-keyword">struct</span> <span class="hljs-title">payloads_ops</span> <span class="hljs-title">riscv_ops</span>;</span><br><span class="hljs-keyword">extern</span> <span class="hljs-class"><span class="hljs-keyword">struct</span> <span class="hljs-title">payloads_ops</span> <span class="hljs-title">aarch64_ops</span>;</span><br><span class="hljs-keyword">extern</span> <span class="hljs-class"><span class="hljs-keyword">struct</span> <span class="hljs-title">payloads_ops</span> <span class="hljs-title">arm_ops</span>;</span><br><br><span class="hljs-comment">// Payload 数组</span><br><span class="hljs-type">static</span> <span class="hljs-class"><span class="hljs-keyword">struct</span> <span class="hljs-title">payloads_ops</span> *<span class="hljs-title">payloads</span>[] =</span> {<br>    &amp;aarch64_ops,<br>    &amp;arm_ops,<br>    &amp;riscv_ops,<br>};<br><br><span class="hljs-type">static</span> <span class="hljs-class"><span class="hljs-keyword">struct</span> <span class="hljs-title">payloads_ops</span> *<span class="hljs-title">current_payload</span>;</span><br><br><span class="hljs-comment">// 初始化 Payload 系统</span><br><span class="hljs-type">int</span> <span class="hljs-title function_">sunxi_efex_fel_payloads_init</span><span class="hljs-params">(<span class="hljs-type">const</span> <span class="hljs-keyword">enum</span> sunxi_efex_fel_payloads_arch arch)</span> {<br>    <span class="hljs-keyword">for</span> (<span class="hljs-type">size_t</span> i = <span class="hljs-number">0</span>; i &lt; <span class="hljs-keyword">sizeof</span>(payloads) / <span class="hljs-keyword">sizeof</span>(payloads[<span class="hljs-number">0</span>]); ++i) {<br>        <span class="hljs-class"><span class="hljs-keyword">struct</span> <span class="hljs-title">payloads_ops</span> *<span class="hljs-title">p</span> =</span> payloads[i];<br>        <span class="hljs-keyword">if</span> (p-&gt;arch == arch) {<br>            current_payload = p;<br>            <span class="hljs-keyword">return</span> EFEX_ERR_SUCCESS;<br>        }<br>    }<br>    <span class="hljs-keyword">return</span> EFEX_ERR_INVALID_PARAM;<br>}<br><br><span class="hljs-comment">// 获取当前 Payload</span><br><span class="hljs-keyword">struct</span> payloads_ops *<span class="hljs-title function_">sunxi_efex_fel_get_current_payload</span><span class="hljs-params">()</span> { <br>    <span class="hljs-keyword">return</span> current_payload; <br>}<br><br><span class="hljs-comment">// 通过 Payload 读取寄存器</span><br><span class="hljs-type">int</span> <span class="hljs-title function_">sunxi_efex_fel_payloads_readl</span><span class="hljs-params">(<span class="hljs-type">const</span> <span class="hljs-keyword">struct</span> <span class="hljs-type">sunxi_efex_ctx_t</span> *ctx, </span><br><span class="hljs-params">                                  <span class="hljs-type">const</span> <span class="hljs-type">uint32_t</span> addr, <span class="hljs-type">uint32_t</span> *val)</span> {<br>    <span class="hljs-keyword">if</span> (!ctx || !val) {<br>        <span class="hljs-keyword">return</span> EFEX_ERR_NULL_PTR;<br>    }<br>    <span class="hljs-keyword">if</span> (ctx-&gt;resp.mode != DEVICE_MODE_FEL) {<br>        <span class="hljs-keyword">return</span> EFEX_ERR_INVALID_DEVICE_MODE;<br>    }<br>    <span class="hljs-keyword">if</span> (!current_payload || !current_payload-&gt;readl) {<br>        <span class="hljs-keyword">return</span> EFEX_ERR_NOT_SUPPORT;<br>    }<br>    <span class="hljs-keyword">return</span> current_payload-&gt;readl(ctx, addr, val);<br>}<br><br><span class="hljs-comment">// 通过 Payload 写入寄存器</span><br><span class="hljs-type">int</span> <span class="hljs-title function_">sunxi_efex_fel_payloads_writel</span><span class="hljs-params">(<span class="hljs-type">const</span> <span class="hljs-keyword">struct</span> <span class="hljs-type">sunxi_efex_ctx_t</span> *ctx, </span><br><span class="hljs-params">                                   <span class="hljs-type">const</span> <span class="hljs-type">uint32_t</span> value, <span class="hljs-type">const</span> <span class="hljs-type">uint32_t</span> addr)</span> {<br>    <span class="hljs-keyword">if</span> (!ctx) {<br>        <span class="hljs-keyword">return</span> EFEX_ERR_NULL_PTR;<br>    }<br>    <span class="hljs-keyword">if</span> (ctx-&gt;resp.mode != DEVICE_MODE_FEL) {<br>        <span class="hljs-keyword">return</span> EFEX_ERR_INVALID_DEVICE_MODE;<br>    }<br>    <span class="hljs-keyword">if</span> (!current_payload || !current_payload-&gt;writel) {<br>        <span class="hljs-keyword">return</span> EFEX_ERR_NOT_SUPPORT;<br>    }<br>    <span class="hljs-keyword">return</span> current_payload-&gt;writel(ctx, value, addr);<br>}<br></code></pre></td></tr></tbody></table></figure><h3 id="Payload-调度流程图" data-id="Payload-调度流程图" class="notion-h"><a href="#Payload-调度流程图" class="headerlink" title="Payload 调度流程图"></a>Payload 调度流程图</h3><pre class="mermaid">flowchart TD    A[sunxi_efex_fel_payloads_init] --&gt; B[遍历 payloads 数组]        B --&gt; C{找到匹配架构?}        C --&gt;|否| D[返回 INVALID_PARAM]        C --&gt;|是| E[设置 current_payload]        E --&gt; F[返回 SUCCESS]        G[sunxi_efex_fel_payloads_readl] --&gt; H{参数检查}        H --&gt;|失败| I[返回对应错误]        H --&gt;|通过| J{current_payload 存在?}        J --&gt;|否| K[返回 NOT_SUPPORT]        J --&gt;|是| L[调用 current_payload-&gt;readl]        L --&gt; M[执行架构特定实现]</pre><h2 id="支持的架构" data-id="支持的架构" class="notion-h"><a href="#支持的架构" class="headerlink" title="支持的架构"></a>支持的架构</h2><table><thead><tr><th>架构</th><th>文件</th><th>状态</th><th>适用芯片</th></tr></thead><tbody><tr><td>ARM32</td><td><code>src/arch/arm.c</code></td><td>完整实现</td><td>A10/A13/A20/A23/A33 等</td></tr><tr><td>RISC-V</td><td><code>src/arch/riscv.c</code></td><td>完整实现</td><td>D1/D1s 等</td></tr><tr><td>AARCH64</td><td><code>src/arch/aarch64.c</code></td><td>待实现</td><td>A64/A80/H3/H5 等</td></tr></tbody></table><h2 id="ARM32-Payload-实现" data-id="ARM32-Payload-实现" class="notion-h"><a href="#ARM32-Payload-实现" class="headerlink" title="ARM32 Payload 实现"></a>ARM32 Payload 实现</h2><h3 id="readl-实现" data-id="readl-实现" class="notion-h"><a href="#readl-实现" class="headerlink" title="readl 实现"></a>readl 实现</h3><p>源码位置: <code>src/arch/arm.c:15-79</code></p><figure class="highlight c"><table><tbody><tr><td class="code"><pre><code class="hljs c"><span class="hljs-type">static</span> <span class="hljs-type">int</span> <span class="hljs-title function_">payloads_readl</span><span class="hljs-params">(<span class="hljs-type">const</span> <span class="hljs-keyword">struct</span> <span class="hljs-type">sunxi_efex_ctx_t</span> *ctx, </span><br><span class="hljs-params">                          <span class="hljs-type">const</span> <span class="hljs-type">uint32_t</span> addr, <span class="hljs-type">uint32_t</span> *val)</span> {<br>    <span class="hljs-comment">// ARMv7 机器码指令数组</span><br>    <span class="hljs-comment">/* 注意: 不要声明为 'static'，某些 Windows 驱动无法访问</span><br><span class="hljs-comment">     * static 标记的 Payload 符号 */</span><br>    <span class="hljs-type">const</span> <span class="hljs-type">uint32_t</span> payload[] = {<br>        WARP_INST(<span class="hljs-number">0b11100011101000000000000000000000</span>), <span class="hljs-comment">/* mov r0, #0 */</span><br>        WARP_INST(<span class="hljs-number">0b11101110000010000000111100010111</span>), <span class="hljs-comment">/* mcr 15, 0, r0, cr8, cr7, {0} */</span><br>        WARP_INST(<span class="hljs-number">0b11101110000001110000111100010101</span>), <span class="hljs-comment">/* mcr 15, 0, r0, cr7, cr5, {0} */</span><br>        WARP_INST(<span class="hljs-number">0b11101110000001110000111111010101</span>), <span class="hljs-comment">/* mcr 15, 0, r0, cr7, cr5, {6} */</span><br>        WARP_INST(<span class="hljs-number">0b11101110000001110000111110011010</span>), <span class="hljs-comment">/* mcr 15, 0, r0, cr7, cr10, {4} */</span><br>        WARP_INST(<span class="hljs-number">0b11101110000001110000111110010101</span>), <span class="hljs-comment">/* mcr 15, 0, r0, cr7, cr5, {4} */</span><br>        WARP_INST(<span class="hljs-number">0b11101010111111111111111111111111</span>), <span class="hljs-comment">/* b 0x4 */</span><br>        WARP_INST(<span class="hljs-number">0b11100101100111110000000000001100</span>), <span class="hljs-comment">/* ldr r0, [pc, #12] */</span><br>        WARP_INST(<span class="hljs-number">0b11100010100011110001000000001100</span>), <span class="hljs-comment">/* add r1, pc, #12 */</span><br>        WARP_INST(<span class="hljs-number">0b11100101100100000010000000000000</span>), <span class="hljs-comment">/* ldr r2, [r0] */</span><br>        WARP_INST(<span class="hljs-number">0b11100101100000010010000000000000</span>), <span class="hljs-comment">/* str r2, [r1] */</span><br>        WARP_INST(<span class="hljs-number">0b11100001001011111111111100011110</span>), <span class="hljs-comment">/* bx lr */</span><br>        <span class="hljs-comment">/* var_addr: 目标地址 (由 sunxi_fel_write 填充) */</span><br>        <span class="hljs-comment">/* var_value: 结果值 (由 Payload 写入) */</span><br>    };<br><br>    <span class="hljs-keyword">if</span> (!val) {<br>        <span class="hljs-keyword">return</span> EFEX_ERR_NULL_PTR;<br>    }<br><br>    <span class="hljs-comment">// 转换为小端格式</span><br>    <span class="hljs-type">uint32_t</span> addr_le32 = cpu_to_le32(addr);<br>    <span class="hljs-type">uint32_t</span> tmp_val = <span class="hljs-number">0</span>;<br>    <span class="hljs-type">int</span> ret = EFEX_ERR_SUCCESS;<br><br>    <span class="hljs-comment">// 1. 写入 Payload 到设备内存</span><br>    ret = sunxi_efex_fel_write(ctx, ctx-&gt;resp.data_start_address,<br>                               (<span class="hljs-type">void</span> *) payload, <span class="hljs-keyword">sizeof</span>(payload));<br>    <span class="hljs-keyword">if</span> (ret != EFEX_ERR_SUCCESS) {<br>        <span class="hljs-keyword">return</span> ret;<br>    }<br><br>    <span class="hljs-comment">// 2. 写入目标地址参数</span><br>    ret = sunxi_efex_fel_write(ctx, <br>                               ctx-&gt;resp.data_start_address + <span class="hljs-keyword">sizeof</span>(payload),<br>                               (<span class="hljs-type">void</span> *) &amp;addr_le32, <span class="hljs-keyword">sizeof</span>(addr_le32));<br>    <span class="hljs-keyword">if</span> (ret != EFEX_ERR_SUCCESS) {<br>        <span class="hljs-keyword">return</span> ret;<br>    }<br><br>    <span class="hljs-comment">// 3. 执行 Payload</span><br>    ret = sunxi_efex_fel_exec(ctx, ctx-&gt;resp.data_start_address);<br>    <span class="hljs-keyword">if</span> (ret != EFEX_ERR_SUCCESS) {<br>        <span class="hljs-keyword">return</span> ret;<br>    }<br><br>    <span class="hljs-comment">// 4. 读取结果值</span><br>    ret = sunxi_efex_fel_read(ctx, <br>                              ctx-&gt;resp.data_start_address + <span class="hljs-keyword">sizeof</span>(payload) + <span class="hljs-keyword">sizeof</span>(addr_le32),<br>                              (<span class="hljs-type">void</span> *) &amp;tmp_val, <span class="hljs-keyword">sizeof</span>(tmp_val));<br>    <span class="hljs-keyword">if</span> (ret != EFEX_ERR_SUCCESS) {<br>        <span class="hljs-keyword">return</span> ret;<br>    }<br><br>    <span class="hljs-comment">// 转换为主机字节序</span><br>    *val = le32_to_cpu(tmp_val);<br><br>    <span class="hljs-keyword">return</span> EFEX_ERR_SUCCESS;<br>}<br></code></pre></td></tr></tbody></table></figure><h3 id="ARM32-readl-执行流程图" data-id="ARM32-readl-执行流程图" class="notion-h"><a href="#ARM32-readl-执行流程图" class="headerlink" title="ARM32 readl 执行流程图"></a>ARM32 readl 执行流程图</h3><pre class="mermaid">sequenceDiagram    participant API as payloads_readl    participant FEL as FEL API    participant Memory as 设备内存    participant CPU as ARM CPU    API-&gt;&gt;API: 准备 ARM32 机器码 payload    API-&gt;&gt;API: 转换地址为小端格式        API-&gt;&gt;FEL: sunxi_efex_fel_write<br>写入 payload 代码    FEL-&gt;&gt;Memory: 48 bytes 机器码        API-&gt;&gt;FEL: sunxi_efex_fel_write<br>写入目标地址参数    FEL-&gt;&gt;Memory: 4 bytes 目标地址        API-&gt;&gt;FEL: sunxi_efex_fel_exec<br>执行 payload    FEL-&gt;&gt;CPU: 跳转到 payload 地址        CPU-&gt;&gt;CPU: mov r0, #0 (缓存维护)    CPU-&gt;&gt;CPU: mcr p15 指令序列    CPU-&gt;&gt;CPU: ldr r0, [pc, #12] 加载地址    CPU-&gt;&gt;Memory: ldr r2, [r0] 读取寄存器值    CPU-&gt;&gt;Memory: str r2, [r1] 写入结果地址    CPU-&gt;&gt;CPU: bx lr 返回        API-&gt;&gt;FEL: sunxi_efex_fel_read<br>读取结果    FEL-&gt;&gt;Memory: 读取 4 bytes    Memory--&gt;&gt;API: 返回 tmp_val        API-&gt;&gt;API: 转换为主机字节序    API--&gt;&gt;API: 返回 val</pre><h3 id="writel-实现" data-id="writel-实现" class="notion-h"><a href="#writel-实现" class="headerlink" title="writel 实现"></a>writel 实现</h3><p>源码位置: <code>src/arch/arm.c:82-131</code></p><figure class="highlight c"><table><tbody><tr><td class="code"><pre><code class="hljs c"><span class="hljs-type">static</span> <span class="hljs-type">int</span> <span class="hljs-title function_">payloads_writel</span><span class="hljs-params">(<span class="hljs-type">const</span> <span class="hljs-keyword">struct</span> <span class="hljs-type">sunxi_efex_ctx_t</span> *ctx, </span><br><span class="hljs-params">                           <span class="hljs-type">const</span> <span class="hljs-type">uint32_t</span> value, <span class="hljs-type">const</span> <span class="hljs-type">uint32_t</span> addr)</span> {<br>    <span class="hljs-comment">// ARMv7 机器码指令数组</span><br>    <span class="hljs-type">const</span> <span class="hljs-type">uint32_t</span> payload[] = {<br>        WARP_INST(<span class="hljs-number">0b11100011101000000000000000000000</span>), <span class="hljs-comment">/* mov r0, #0 */</span><br>        WARP_INST(<span class="hljs-number">0b11101110000010000000111100010111</span>), <span class="hljs-comment">/* mcr 15, 0, r0, cr8, cr7, {0} */</span><br>        WARP_INST(<span class="hljs-number">0b11101110000001110000111100010101</span>), <span class="hljs-comment">/* mcr 15, 0, r0, cr7, cr5, {0} */</span><br>        WARP_INST(<span class="hljs-number">0b11101110000001110000111111010101</span>), <span class="hljs-comment">/* mcr 15, 0, r0, cr7, cr5, {6} */</span><br>        WARP_INST(<span class="hljs-number">0b11101110000001110000111110011010</span>), <span class="hljs-comment">/* mcr 15, 0, r0, cr7, cr10, {4} */</span><br>        WARP_INST(<span class="hljs-number">0b11101110000001110000111110010101</span>), <span class="hljs-comment">/* mcr 15, 0, r0, cr7, cr5, {4} */</span><br>        WARP_INST(<span class="hljs-number">0b11101010111111111111111111111111</span>), <span class="hljs-comment">/* b 0x4 */</span><br>        WARP_INST(<span class="hljs-number">0b11100101100111110000000000001000</span>), <span class="hljs-comment">/* ldr r0, [pc, #8] */</span><br>        WARP_INST(<span class="hljs-number">0b11100101100111110001000000001000</span>), <span class="hljs-comment">/* ldr r1, [pc, #8] */</span><br>        WARP_INST(<span class="hljs-number">0b11100101100000000001000000000000</span>), <span class="hljs-comment">/* str r1, [r0] */</span><br>        WARP_INST(<span class="hljs-number">0b11100001001011111111111100011110</span>), <span class="hljs-comment">/* bx lr */</span><br>        <span class="hljs-comment">/* var_addr: 目标地址 */</span><br>        <span class="hljs-comment">/* var_value: 要写入的值 */</span><br>    };<br><br>    <span class="hljs-comment">// 准备参数 (地址和值)</span><br>    <span class="hljs-type">const</span> <span class="hljs-type">uint32_t</span> params[<span class="hljs-number">2</span>] = {<br>        cpu_to_le32(addr),<br>        cpu_to_le32(value),<br>    };<br><br>    <span class="hljs-type">int</span> ret = EFEX_ERR_SUCCESS;<br><br>    <span class="hljs-comment">// 1. 写入 Payload</span><br>    ret = sunxi_efex_fel_write(ctx, ctx-&gt;resp.data_start_address,<br>                               (<span class="hljs-type">void</span> *) payload, <span class="hljs-keyword">sizeof</span>(payload));<br>    <span class="hljs-keyword">if</span> (ret != EFEX_ERR_SUCCESS) {<br>        <span class="hljs-keyword">return</span> ret;<br>    }<br><br>    <span class="hljs-comment">// 2. 写入参数</span><br>    ret = sunxi_efex_fel_write(ctx, <br>                               ctx-&gt;resp.data_start_address + <span class="hljs-keyword">sizeof</span>(payload),<br>                               (<span class="hljs-type">void</span> *) params, <span class="hljs-keyword">sizeof</span>(params));<br>    <span class="hljs-keyword">if</span> (ret != EFEX_ERR_SUCCESS) {<br>        <span class="hljs-keyword">return</span> ret;<br>    }<br><br>    <span class="hljs-comment">// 3. 执行 Payload</span><br>    ret = sunxi_efex_fel_exec(ctx, ctx-&gt;resp.data_start_address);<br>    <span class="hljs-keyword">if</span> (ret != EFEX_ERR_SUCCESS) {<br>        <span class="hljs-keyword">return</span> ret;<br>    }<br><br>    <span class="hljs-keyword">return</span> EFEX_ERR_SUCCESS;<br>}<br></code></pre></td></tr></tbody></table></figure><h3 id="ARM32-writel-执行流程图" data-id="ARM32-writel-执行流程图" class="notion-h"><a href="#ARM32-writel-执行流程图" class="headerlink" title="ARM32 writel 执行流程图"></a>ARM32 writel 执行流程图</h3><pre class="mermaid">sequenceDiagram    participant API as payloads_writel    participant FEL as FEL API    participant Memory as 设备内存    participant CPU as ARM CPU    API-&gt;&gt;API: 准备 ARM32 机器码 payload    API-&gt;&gt;API: 准备参数 [addr, value]        API-&gt;&gt;FEL: sunxi_efex_fel_write<br>写入 payload 代码    FEL-&gt;&gt;Memory: 44 bytes 机器码        API-&gt;&gt;FEL: sunxi_efex_fel_write<br>写入参数    FEL-&gt;&gt;Memory: 8 bytes addr + value        API-&gt;&gt;FEL: sunxi_efex_fel_exec<br>执行 payload    FEL-&gt;&gt;CPU: 跳转到 payload 地址        CPU-&gt;&gt;CPU: 缓存维护指令序列    CPU-&gt;&gt;CPU: ldr r0, [pc, #8] 加载目标地址    CPU-&gt;&gt;CPU: ldr r1, [pc, #8] 加载写入值    CPU-&gt;&gt;Memory: str r1, [r0] 写入寄存器    CPU-&gt;&gt;CPU: bx lr 返回        API--&gt;&gt;API: 返回 SUCCESS</pre><h3 id="ARM-Ops-结构" data-id="ARM-Ops-结构" class="notion-h"><a href="#ARM-Ops-结构" class="headerlink" title="ARM Ops 结构"></a>ARM Ops 结构</h3><p>源码位置: <code>src/arch/arm.c:134-139</code></p><figure class="highlight c"><table><tbody><tr><td class="code"><pre><code class="hljs c"><span class="hljs-class"><span class="hljs-keyword">struct</span> <span class="hljs-title">payloads_ops</span> <span class="hljs-title">arm_ops</span> =</span> {<br>    .name = <span class="hljs-string">"arm32"</span>,<br>    .arch = ARCH_ARM32,<br>    .readl = payloads_readl,<br>    .writel = payloads_writel,<br>};<br></code></pre></td></tr></tbody></table></figure><h2 id="ARM32-指令详解" data-id="ARM32-指令详解" class="notion-h"><a href="#ARM32-指令详解" class="headerlink" title="ARM32 指令详解"></a>ARM32 指令详解</h2><table><thead><tr><th>指令</th><th>机器码</th><th>说明</th></tr></thead><tbody><tr><td><code>mov r0, #0</code></td><td><code>0xe3a00000</code></td><td>清除 r0 寄存器</td></tr><tr><td><code>mcr p15, 0, r0, c8, c7, {0}</code></td><td><code>0xee080f17</code></td><td>TLB 无效化 (缓存维护)</td></tr><tr><td><code>mcr p15, 0, r0, c7, c5, {0}</code></td><td><code>0xee071f15</code></td><td>指令缓存无效化</td></tr><tr><td><code>mcr p15, 0, r0, c7, c5, {6}</code></td><td><code>0xee071f55</code></td><td>分支预测器无效化</td></tr><tr><td><code>mcr p15, 0, r0, c7, c10, {4}</code></td><td><code>0xee071f9a</code></td><td>数据同步屏障</td></tr><tr><td><code>mcr p15, 0, r0, c7, c5, {4}</code></td><td><code>0xee071f15</code></td><td>数据缓存无效化</td></tr><tr><td><code>b 0x4</code></td><td><code>0xeafffffe</code></td><td>等待循环</td></tr><tr><td><code>ldr r0, [pc, #offset]</code></td><td><code>0xe59f0xxx</code></td><td>加载目标地址</td></tr><tr><td><code>ldr r2, [r0]</code></td><td><code>0xe5912000</code></td><td>从地址读取值</td></tr><tr><td><code>str r2, [r1]</code></td><td><code>0xe5812000</code></td><td>将值写入结果地址</td></tr><tr><td><code>bx lr</code></td><td><code>0xe12fff1e</code></td><td>返回调用者</td></tr></tbody></table><h2 id="RISC-V-Payload-实现" data-id="RISC-V-Payload-实现" class="notion-h"><a href="#RISC-V-Payload-实现" class="headerlink" title="RISC-V Payload 实现"></a>RISC-V Payload 实现</h2><h3 id="readl-实现-1" data-id="readl-实现-1" class="notion-h"><a href="#readl-实现-1" class="headerlink" title="readl 实现"></a>readl 实现</h3><p>源码位置: <code>src/arch/riscv.c:15-79</code></p><figure class="highlight c"><table><tbody><tr><td class="code"><pre><code class="hljs c"><span class="hljs-type">static</span> <span class="hljs-type">int</span> <span class="hljs-title function_">payloads_readl</span><span class="hljs-params">(<span class="hljs-type">const</span> <span class="hljs-keyword">struct</span> <span class="hljs-type">sunxi_efex_ctx_t</span> *ctx, </span><br><span class="hljs-params">                          <span class="hljs-type">const</span> <span class="hljs-type">uint32_t</span> addr, <span class="hljs-type">uint32_t</span> *val)</span> {<br>    <span class="hljs-comment">// RISC-V 机器码指令数组</span><br>    <span class="hljs-type">const</span> <span class="hljs-type">uint32_t</span> payload[] = {<br>        WARP_INST(<span class="hljs-number">0b00000000010000000000001100110111</span>), <span class="hljs-comment">/* lui t1, 0x400 */</span><br>        WARP_INST(<span class="hljs-number">0b01111100000000110010000001110011</span>), <span class="hljs-comment">/* csrs mxstatus, t1 */</span><br>        WARP_INST(<span class="hljs-number">0b00000000000000000001000000001111</span>), <span class="hljs-comment">/* fence.i */</span><br>        WARP_INST(<span class="hljs-number">0b00000000010000000000000001101111</span>), <span class="hljs-comment">/* j +4 */</span><br>        WARP_INST(<span class="hljs-number">0b00000000000000000000001010010111</span>), <span class="hljs-comment">/* auipc t0, 0x0 */</span><br>        WARP_INST(<span class="hljs-number">0b00000010000000101000001010010011</span>), <span class="hljs-comment">/* addi t0, t0, 32 */</span><br>        WARP_INST(<span class="hljs-number">0b00000000000000101010001010000011</span>), <span class="hljs-comment">/* lw t0, 0(t0) */</span><br>        WARP_INST(<span class="hljs-number">0b00000000000000101010001010000011</span>), <span class="hljs-comment">/* lw t0, 0(t0) */</span><br>        WARP_INST(<span class="hljs-number">0b00000000000000000000001100010111</span>), <span class="hljs-comment">/* auipc t1, 0x0 */</span><br>        WARP_INST(<span class="hljs-number">0b00000001010000110000001100010011</span>), <span class="hljs-comment">/* addi t1, t1, 20 */</span><br>        WARP_INST(<span class="hljs-number">0b00000000010100110010000000100011</span>), <span class="hljs-comment">/* sw t0, 0(t1) */</span><br>        WARP_INST(<span class="hljs-number">0b00000000000000001000000001100111</span>), <span class="hljs-comment">/* ret */</span><br>        <span class="hljs-comment">/* var_addr: 目标地址 */</span><br>        <span class="hljs-comment">/* var_value: 结果值 */</span><br>    };<br><br>    <span class="hljs-keyword">if</span> (!val) {<br>        <span class="hljs-keyword">return</span> EFEX_ERR_NULL_PTR;<br>    }<br><br>    <span class="hljs-type">uint32_t</span> addr_le32 = cpu_to_le32(addr);<br>    <span class="hljs-type">uint32_t</span> tmp_val = <span class="hljs-number">0</span>;<br>    <span class="hljs-type">int</span> ret = EFEX_ERR_SUCCESS;<br><br>    <span class="hljs-comment">// 1. 写入 Payload</span><br>    ret = sunxi_efex_fel_write(ctx, ctx-&gt;resp.data_start_address,<br>                               (<span class="hljs-type">void</span> *) payload, <span class="hljs-keyword">sizeof</span>(payload));<br>    <span class="hljs-keyword">if</span> (ret != EFEX_ERR_SUCCESS) {<br>        <span class="hljs-keyword">return</span> ret;<br>    }<br><br>    <span class="hljs-comment">// 2. 写入目标地址</span><br>    ret = sunxi_efex_fel_write(ctx, <br>                               ctx-&gt;resp.data_start_address + <span class="hljs-keyword">sizeof</span>(payload),<br>                               (<span class="hljs-type">void</span> *) &amp;addr_le32, <span class="hljs-keyword">sizeof</span>(addr_le32));<br>    <span class="hljs-keyword">if</span> (ret != EFEX_ERR_SUCCESS) {<br>        <span class="hljs-keyword">return</span> ret;<br>    }<br><br>    <span class="hljs-comment">// 3. 执行 Payload</span><br>    ret = sunxi_efex_fel_exec(ctx, ctx-&gt;resp.data_start_address);<br>    <span class="hljs-keyword">if</span> (ret != EFEX_ERR_SUCCESS) {<br>        <span class="hljs-keyword">return</span> ret;<br>    }<br><br>    <span class="hljs-comment">// 4. 读取结果</span><br>    ret = sunxi_efex_fel_read(ctx, <br>                              ctx-&gt;resp.data_start_address + <span class="hljs-keyword">sizeof</span>(payload) + <span class="hljs-keyword">sizeof</span>(addr_le32),<br>                              (<span class="hljs-type">void</span> *) &amp;tmp_val, <span class="hljs-keyword">sizeof</span>(tmp_val));<br>    <span class="hljs-keyword">if</span> (ret != EFEX_ERR_SUCCESS) {<br>        <span class="hljs-keyword">return</span> ret;<br>    }<br><br>    *val = le32_to_cpu(tmp_val);<br><br>    <span class="hljs-keyword">return</span> EFEX_ERR_SUCCESS;<br>}<br></code></pre></td></tr></tbody></table></figure><h3 id="RISC-V-readl-执行流程图" data-id="RISC-V-readl-执行流程图" class="notion-h"><a href="#RISC-V-readl-执行流程图" class="headerlink" title="RISC-V readl 执行流程图"></a>RISC-V readl 执行流程图</h3><pre class="mermaid">sequenceDiagram    participant API as payloads_readl    participant FEL as FEL API    participant Memory as 设备内存    participant CPU as RISC-V CPU    API-&gt;&gt;API: 准备 RISC-V 机器码 payload    API-&gt;&gt;API: 转换地址为小端格式        API-&gt;&gt;FEL: sunxi_efex_fel_write<br>写入 payload 代码    FEL-&gt;&gt;Memory: 48 bytes 机器码        API-&gt;&gt;FEL: sunxi_efex_fel_write<br>写入目标地址参数    FEL-&gt;&gt;Memory: 4 bytes 目标地址        API-&gt;&gt;FEL: sunxi_efex_fel_exec<br>执行 payload    FEL-&gt;&gt;CPU: 跳转到 payload 地址        CPU-&gt;&gt;CPU: lui t1, 0x400    CPU-&gt;&gt;CPU: csrs mxstatus, t1    CPU-&gt;&gt;CPU: fence.i (缓存屏障)    CPU-&gt;&gt;CPU: auipc/addi 计算地址    CPU-&gt;&gt;Memory: lw t0, 0(t0) 加载目标地址    CPU-&gt;&gt;Memory: lw t0, 0(t0) 读取寄存器值    CPU-&gt;&gt;Memory: sw t0, 0(t1) 写入结果地址    CPU-&gt;&gt;CPU: ret 返回        API-&gt;&gt;FEL: sunxi_efex_fel_read<br>读取结果    FEL-&gt;&gt;Memory: 读取 4 bytes    Memory--&gt;&gt;API: 返回 tmp_val        API-&gt;&gt;API: 转换为主机字节序    API--&gt;&gt;API: 返回 val</pre><h3 id="writel-实现-1" data-id="writel-实现-1" class="notion-h"><a href="#writel-实现-1" class="headerlink" title="writel 实现"></a>writel 实现</h3><p>源码位置: <code>src/arch/riscv.c:82-132</code></p><figure class="highlight c"><table><tbody><tr><td class="code"><pre><code class="hljs c"><span class="hljs-type">static</span> <span class="hljs-type">int</span> <span class="hljs-title function_">payloads_writel</span><span class="hljs-params">(<span class="hljs-type">const</span> <span class="hljs-keyword">struct</span> <span class="hljs-type">sunxi_efex_ctx_t</span> *ctx, </span><br><span class="hljs-params">                           <span class="hljs-type">const</span> <span class="hljs-type">uint32_t</span> value, <span class="hljs-type">const</span> <span class="hljs-type">uint32_t</span> addr)</span> {<br>    <span class="hljs-comment">// RISC-V 机器码指令数组</span><br>    <span class="hljs-type">const</span> <span class="hljs-type">uint32_t</span> payload[] = {<br>        WARP_INST(<span class="hljs-number">0b00000000010000000000001100110111</span>), <span class="hljs-comment">/* lui t1, 0x400 */</span><br>        WARP_INST(<span class="hljs-number">0b01111100000000110010000001110011</span>), <span class="hljs-comment">/* csrs mxstatus, t1 */</span><br>        WARP_INST(<span class="hljs-number">0b00000000000000000001000000001111</span>), <span class="hljs-comment">/* fence.i */</span><br>        WARP_INST(<span class="hljs-number">0b00000000010000000000000001101111</span>), <span class="hljs-comment">/* j +4 */</span><br>        WARP_INST(<span class="hljs-number">0b00000000000000000000001010010111</span>), <span class="hljs-comment">/* auipc t0, 0x0 */</span><br>        WARP_INST(<span class="hljs-number">0b00000010000000101000001010010011</span>), <span class="hljs-comment">/* addi t0, t0, 32 */</span><br>        WARP_INST(<span class="hljs-number">0b00000000000000101010001010000011</span>), <span class="hljs-comment">/* lw t0, 0(t0) */</span><br>        WARP_INST(<span class="hljs-number">0b00000000000000000000001100010111</span>), <span class="hljs-comment">/* auipc t1, 0x0 */</span><br>        WARP_INST(<span class="hljs-number">0b00000001100000110000001100010011</span>), <span class="hljs-comment">/* addi t1, t1, 24 */</span><br>        WARP_INST(<span class="hljs-number">0b00000000000000110010001100000011</span>), <span class="hljs-comment">/* lw t1, 0(t1) */</span><br>        WARP_INST(<span class="hljs-number">0b00000000011000101010000000100011</span>), <span class="hljs-comment">/* sw t1, 0(t0) */</span><br>        WARP_INST(<span class="hljs-number">0b00000000000000001000000001100111</span>), <span class="hljs-comment">/* ret */</span><br>        <span class="hljs-comment">/* var_addr: 目标地址 */</span><br>        <span class="hljs-comment">/* var_value: 要写入的值 */</span><br>    };<br><br>    <span class="hljs-type">const</span> <span class="hljs-type">uint32_t</span> params[<span class="hljs-number">2</span>] = {<br>        cpu_to_le32(addr),<br>        cpu_to_le32(value),<br>    };<br><br>    <span class="hljs-type">int</span> ret = EFEX_ERR_SUCCESS;<br><br>    <span class="hljs-comment">// 1. 写入 Payload</span><br>    ret = sunxi_efex_fel_write(ctx, ctx-&gt;resp.data_start_address,<br>                               (<span class="hljs-type">void</span> *) payload, <span class="hljs-keyword">sizeof</span>(payload));<br>    <span class="hljs-keyword">if</span> (ret != EFEX_ERR_SUCCESS) {<br>        <span class="hljs-keyword">return</span> ret;<br>    }<br><br>    <span class="hljs-comment">// 2. 写入参数</span><br>    ret = sunxi_efex_fel_write(ctx, <br>                               ctx-&gt;resp.data_start_address + <span class="hljs-keyword">sizeof</span>(payload),<br>                               (<span class="hljs-type">void</span> *) params, <span class="hljs-keyword">sizeof</span>(params));<br>    <span class="hljs-keyword">if</span> (ret != EFEX_ERR_SUCCESS) {<br>        <span class="hljs-keyword">return</span> ret;<br>    }<br><br>    <span class="hljs-comment">// 3. 执行 Payload</span><br>    ret = sunxi_efex_fel_exec(ctx, ctx-&gt;resp.data_start_address);<br>    <span class="hljs-keyword">if</span> (ret != EFEX_ERR_SUCCESS) {<br>        <span class="hljs-keyword">return</span> ret;<br>    }<br><br>    <span class="hljs-keyword">return</span> EFEX_ERR_SUCCESS;<br>}<br></code></pre></td></tr></tbody></table></figure><h3 id="RISC-V-Ops-结构" data-id="RISC-V-Ops-结构" class="notion-h"><a href="#RISC-V-Ops-结构" class="headerlink" title="RISC-V Ops 结构"></a>RISC-V Ops 结构</h3><p>源码位置: <code>src/arch/riscv.c:135-140</code></p><figure class="highlight c"><table><tbody><tr><td class="code"><pre><code class="hljs c"><span class="hljs-class"><span class="hljs-keyword">struct</span> <span class="hljs-title">payloads_ops</span> <span class="hljs-title">riscv_ops</span> =</span> {<br>    .name = <span class="hljs-string">"riscv"</span>,<br>    .arch = ARCH_RISCV,<br>    .readl = payloads_readl,<br>    .writel = payloads_writel,<br>};<br></code></pre></td></tr></tbody></table></figure><h2 id="RISC-V-指令详解" data-id="RISC-V-指令详解" class="notion-h"><a href="#RISC-V-指令详解" class="headerlink" title="RISC-V 指令详解"></a>RISC-V 指令详解</h2><table><thead><tr><th>指令</th><th>机器码</th><th>说明</th></tr></thead><tbody><tr><td><code>lui t1, 0x400</code></td><td><code>0x40000337</code></td><td>加载立即数高位到 t1</td></tr><tr><td><code>csrs mxstatus, t1</code></td><td><code>0xfc003110</code></td><td>设置扩展状态寄存器</td></tr><tr><td><code>fence.i</code></td><td><code>0x0000100f</code></td><td>指令缓存屏障</td></tr><tr><td><code>j +4</code></td><td><code>0x0040006f</code></td><td>跳转等待</td></tr><tr><td><code>auipc t0, 0x0</code></td><td><code>0x00001297</code></td><td>PC 相对地址加载</td></tr><tr><td><code>addi t0, t0, offset</code></td><td><code>0x02028293</code></td><td>计算实际地址</td></tr><tr><td><code>lw t0, 0(t0)</code></td><td><code>0x0002a283</code></td><td>加载目标地址</td></tr><tr><td><code>lw t0, 0(t0)</code></td><td><code>0x0002a283</code></td><td>从地址读取值</td></tr><tr><td><code>sw t0, 0(t1)</code></td><td><code>0x00531023</code></td><td>将值写入结果地址</td></tr><tr><td><code>ret</code></td><td><code>0x00008067</code></td><td>返回调用者</td></tr></tbody></table><h2 id="Payload-API-使用示例" data-id="Payload-API-使用示例" class="notion-h"><a href="#Payload-API-使用示例" class="headerlink" title="Payload API 使用示例"></a>Payload API 使用示例</h2><h3 id="初始化-Payload" data-id="初始化-Payload" class="notion-h"><a href="#初始化-Payload" class="headerlink" title="初始化 Payload"></a>初始化 Payload</h3><figure class="highlight c"><table><tbody><tr><td class="code"><pre><code class="hljs c"><span class="hljs-meta">#<span class="hljs-keyword">include</span> <span class="hljs-string">&lt;libefex.h&gt;</span></span><br><br><span class="hljs-type">int</span> <span class="hljs-title function_">main</span><span class="hljs-params">()</span> {<br>    <span class="hljs-class"><span class="hljs-keyword">struct</span> <span class="hljs-title">sunxi_efex_ctx_t</span> <span class="hljs-title">ctx</span> =</span> {<span class="hljs-number">0</span>};<br>    <br>    <span class="hljs-comment">// 连接设备</span><br>    sunxi_scan_usb_device(&amp;ctx);<br>    sunxi_usb_init(&amp;ctx);<br>    sunxi_efex_init(&amp;ctx);<br>    <br>    <span class="hljs-comment">// 根据芯片 ID 判断架构并初始化 Payload</span><br>    <span class="hljs-class"><span class="hljs-keyword">enum</span> <span class="hljs-title">sunxi_efex_fel_payloads_arch</span> <span class="hljs-title">arch</span>;</span><br>    <br>    <span class="hljs-keyword">if</span> ((ctx.resp.id &amp; <span class="hljs-number">0xffff</span>) == <span class="hljs-number">0x1623</span> ||   <span class="hljs-comment">// A10</span><br>        (ctx.resp.id &amp; <span class="hljs-number">0xffff</span>) == <span class="hljs-number">0x1651</span>) {   <span class="hljs-comment">// A20</span><br>        arch = ARCH_ARM32;<br>    } <span class="hljs-keyword">else</span> <span class="hljs-keyword">if</span> ((ctx.resp.id &amp; <span class="hljs-number">0xffff</span>) == <span class="hljs-number">0x1700</span>) {  <span class="hljs-comment">// D1/D1s</span><br>        arch = ARCH_RISCV;<br>    } <span class="hljs-keyword">else</span> {<br>        <span class="hljs-built_in">printf</span>(<span class="hljs-string">"Unknown chip architecture\n"</span>);<br>        <span class="hljs-keyword">return</span> <span class="hljs-number">-1</span>;<br>    }<br>    <br>    <span class="hljs-type">int</span> ret = sunxi_efex_fel_payloads_init(arch);<br>    <span class="hljs-keyword">if</span> (ret != EFEX_ERR_SUCCESS) {<br>        <span class="hljs-built_in">printf</span>(<span class="hljs-string">"Payload init failed: %s\n"</span>, sunxi_efex_strerror(ret));<br>        <span class="hljs-keyword">return</span> <span class="hljs-number">-1</span>;<br>    }<br>    <br>    <span class="hljs-built_in">printf</span>(<span class="hljs-string">"Payload initialized: %s\n"</span>, <br>           sunxi_efex_fel_get_current_payload()-&gt;name);<br>    <br>    <span class="hljs-comment">// ... 使用 Payload API ...</span><br>    <br>    sunxi_usb_exit(&amp;ctx);<br>    <span class="hljs-keyword">return</span> <span class="hljs-number">0</span>;<br>}<br></code></pre></td></tr></tbody></table></figure><h3 id="寄存器读写示例" data-id="寄存器读写示例" class="notion-h"><a href="#寄存器读写示例" class="headerlink" title="寄存器读写示例"></a>寄存器读写示例</h3><figure class="highlight c"><table><tbody><tr><td class="code"><pre><code class="hljs c"><span class="hljs-type">void</span> <span class="hljs-title function_">read_write_registers</span><span class="hljs-params">(<span class="hljs-keyword">struct</span> <span class="hljs-type">sunxi_efex_ctx_t</span> *ctx)</span> {<br>    <span class="hljs-comment">// 读取时钟控制寄存器 (CCU)</span><br>    <span class="hljs-type">uint32_t</span> clk_value;<br>    <span class="hljs-type">int</span> ret = sunxi_efex_fel_payloads_readl(ctx, <span class="hljs-number">0x01c20000</span>, &amp;clk_value);<br>    <br>    <span class="hljs-keyword">if</span> (ret == EFEX_ERR_SUCCESS) {<br>        <span class="hljs-built_in">printf</span>(<span class="hljs-string">"Clock register at 0x01c20000: 0x%08x\n"</span>, clk_value);<br>    } <span class="hljs-keyword">else</span> {<br>        <span class="hljs-built_in">printf</span>(<span class="hljs-string">"Read failed: %s\n"</span>, sunxi_efex_strerror(ret));<br>    }<br>    <br>    <span class="hljs-comment">// 写入 GPIO 配置寄存器</span><br>    ret = sunxi_efex_fel_payloads_writel(ctx, <span class="hljs-number">0x12345678</span>, <span class="hljs-number">0x01c20800</span>);<br>    <br>    <span class="hljs-keyword">if</span> (ret == EFEX_ERR_SUCCESS) {<br>        <span class="hljs-built_in">printf</span>(<span class="hljs-string">"Write to 0x01c20800 successful\n"</span>);<br>    } <span class="hljs-keyword">else</span> {<br>        <span class="hljs-built_in">printf</span>(<span class="hljs-string">"Write failed: %s\n"</span>, sunxi_efex_strerror(ret));<br>    }<br>}<br></code></pre></td></tr></tbody></table></figure><h2 id="内存布局" data-id="内存布局" class="notion-h"><a href="#内存布局" class="headerlink" title="内存布局"></a>内存布局</h2><pre class="mermaid">block-beta    columns 8    block:Memory        columns 4        M1["Payload 代码<br>48 bytes"]        M2["参数区<br>8 bytes"]        M3["结果区<br>4 bytes"]        M4["空闲区域"]    end</pre><h3 id="Payload-内存地址分配" data-id="Payload-内存地址分配" class="notion-h"><a href="#Payload-内存地址分配" class="headerlink" title="Payload 内存地址分配"></a>Payload 内存地址分配</h3><table><thead><tr><th>区域</th><th>地址偏移</th><th>大小</th><th>说明</th></tr></thead><tbody><tr><td>Payload 代码区</td><td><code>data_start_address</code></td><td>~48 bytes</td><td>机器码存放</td></tr><tr><td>参数区</td><td><code>+ sizeof(payload)</code></td><td>4-8 bytes</td><td>目标地址/值</td></tr><tr><td>结果区</td><td><code>+ sizeof(payload) + sizeof(params)</code></td><td>4 bytes</td><td>执行结果存放</td></tr></tbody></table>]]>
    </content>
    <id>https://gloomyghost.com/posts/libefex-payloads/</id>
    <link href="https://gloomyghost.com/posts/libefex-payloads/"/>
    <published>2026-04-29T16:00:00.000Z</published>
    <summary>
      <![CDATA[<h1 id="libefex-架构-Payload" data-id="libefex-架构-Payload" class="notion-h"><a href="#libefex-架构-Payload" class="headerlink" title="libefex 架构]]>
    </summary>
    <title>libefex Payload 技术：ARM 与 RISC-V 机器码注入</title>
    <updated>2026-05-29T20:33:31.219Z</updated>
  </entry>
  <entry>
    <author>
      <name>柚木 鉉</name>
    </author>
    <category term="嵌入式" scheme="https://gloomyghost.com/tags/%E5%B5%8C%E5%85%A5%E5%BC%8F/"/>
    <category term="C" scheme="https://gloomyghost.com/tags/C/"/>
    <category term="libefex" scheme="https://gloomyghost.com/tags/libefex/"/>
    <category term="libusb" scheme="https://gloomyghost.com/tags/libusb/"/>
    <category term="WinUSB" scheme="https://gloomyghost.com/tags/WinUSB/"/>
    <category term="跨平台" scheme="https://gloomyghost.com/tags/%E8%B7%A8%E5%B9%B3%E5%8F%B0/"/>
    <content>
      <![CDATA[<h1 id="libefex-跨平台-USB-后端" data-id="libefex-跨平台-USB-后端" class="notion-h"><a href="#libefex-跨平台-USB-后端" class="headerlink" title="libefex 跨平台 USB 后端"></a>libefex 跨平台 USB 后端</h1><h2 id="USB-后端概述" data-id="USB-后端概述" class="notion-h"><a href="#USB-后端概述" class="headerlink" title="USB 后端概述"></a>USB 后端概述</h2><p>libefex 需要跨平台支持 Linux、macOS 和 Windows。由于不同平台的 USB 驱动接口不同，项目采用后端抽象层设计：</p><ul><li><strong>libusb</strong>: 开源跨平台 USB 库（Linux、macOS、可选 Windows）</li><li><strong>WinUSB</strong>: Windows 专用 USB 驱动框架</li></ul><pre class="mermaid">graph TB    subgraph Application["应用层"]        A1[libefex API]    end    subgraph Abstraction["抽象层 - usb_layer.c"]        B1[后端接口定义]        B2[平台检测]        B3[后端调度]    end    subgraph Libusb["libusb 后端"]        C1[Linux 实现]        C2[macOS 实现]        C3[Windows 实现（可选）]    end    subgraph WinUSB["WinUSB 后端"]        D1[SetupAPI 设备枚举]        D2[WinUSB 驱动接口]    end    A1 --&gt; B1    B1 --&gt; B2    B2 --&gt; B3        B3 --&gt;|Linux/macOS| C1    B3 --&gt;|Linux/macOS| C2    B3 --&gt;|Windows + libusb| C3    B3 --&gt;|Windows + WinUSB| D1    B3 --&gt;|Windows + WinUSB| D2</pre><h2 id="USB-后端接口定义" data-id="USB-后端接口定义" class="notion-h"><a href="#USB-后端接口定义" class="headerlink" title="USB 后端接口定义"></a>USB 后端接口定义</h2><p>源码位置: <code>includes/usb_layer.h</code></p><figure class="highlight c"><table><tbody><tr><td class="code"><pre><code class="hljs c"><span class="hljs-comment">// USB 后端类型枚举</span><br><span class="hljs-class"><span class="hljs-keyword">enum</span> <span class="hljs-title">usb_backend_type</span> {</span><br>    USB_BACKEND_AUTO = <span class="hljs-number">0</span>,    <span class="hljs-comment">// 自动选择</span><br>    USB_BACKEND_LIBUSB = <span class="hljs-number">1</span>,  <span class="hljs-comment">// 强制使用 libusb</span><br>    USB_BACKEND_WINUSB = <span class="hljs-number">2</span>,  <span class="hljs-comment">// 强制使用 WinUSB (仅 Windows)</span><br>};<br><br><span class="hljs-comment">// USB 后端操作结构</span><br><span class="hljs-class"><span class="hljs-keyword">struct</span> <span class="hljs-title">usb_backend_ops</span> {</span><br>    <span class="hljs-comment">// 批量传输</span><br>    <span class="hljs-type">int</span> (*bulk_send)(<span class="hljs-type">void</span> *handle, <span class="hljs-type">int</span> ep, <span class="hljs-type">const</span> <span class="hljs-type">char</span> *buf, <span class="hljs-type">ssize_t</span> len);<br>    <span class="hljs-type">int</span> (*bulk_recv)(<span class="hljs-type">void</span> *handle, <span class="hljs-type">int</span> ep, <span class="hljs-type">char</span> *buf, <span class="hljs-type">ssize_t</span> len);<br>    <br>    <span class="hljs-comment">// 设备扫描</span><br>    <span class="hljs-type">int</span> (*scan_device)(<span class="hljs-keyword">struct</span> <span class="hljs-type">sunxi_efex_ctx_t</span> *ctx);<br>    <span class="hljs-type">int</span> (*scan_device_at)(<span class="hljs-keyword">struct</span> <span class="hljs-type">sunxi_efex_ctx_t</span> *ctx, <span class="hljs-type">uint8_t</span> bus, <span class="hljs-type">uint8_t</span> port);<br>    <span class="hljs-type">int</span> (*scan_devices)(<span class="hljs-keyword">struct</span> <span class="hljs-type">sunxi_scanned_device_t</span> **devices, <span class="hljs-type">size_t</span> *count);<br>    <br>    <span class="hljs-comment">// 热插拔支持</span><br>    <span class="hljs-type">int</span> (*hotplug_snapshot)(<span class="hljs-keyword">struct</span> <span class="hljs-type">sunxi_hotplug_device_t</span> **devices, <span class="hljs-type">size_t</span> *count);<br>    <br>    <span class="hljs-comment">// 后端生命周期</span><br>    <span class="hljs-type">int</span> (*init)(<span class="hljs-keyword">struct</span> <span class="hljs-type">sunxi_efex_ctx_t</span> *ctx);<br>    <span class="hljs-type">int</span> (*<span class="hljs-built_in">exit</span>)(<span class="hljs-keyword">struct</span> <span class="hljs-type">sunxi_efex_ctx_t</span> *ctx);<br>};<br></code></pre></td></tr></tbody></table></figure><h2 id="USB-后端调度器实现" data-id="USB-后端调度器实现" class="notion-h"><a href="#USB-后端调度器实现" class="headerlink" title="USB 后端调度器实现"></a>USB 后端调度器实现</h2><p>源码位置: <code>src/usb/usb_layer.c</code></p><figure class="highlight c"><table><tbody><tr><td class="code"><pre><code class="hljs c"><span class="hljs-meta">#<span class="hljs-keyword">include</span> <span class="hljs-string">&lt;stdlib.h&gt;</span></span><br><span class="hljs-meta">#<span class="hljs-keyword">include</span> <span class="hljs-string">&lt;string.h&gt;</span></span><br><br><span class="hljs-meta">#<span class="hljs-keyword">include</span> <span class="hljs-string">"usb_layer.h"</span></span><br><span class="hljs-meta">#<span class="hljs-keyword">include</span> <span class="hljs-string">"efex-common.h"</span></span><br><br><span class="hljs-type">static</span> <span class="hljs-class"><span class="hljs-keyword">enum</span> <span class="hljs-title">usb_backend_type</span> <span class="hljs-title">current_backend</span> =</span> USB_BACKEND_AUTO;<br><br><span class="hljs-comment">// 外部后端操作结构</span><br><span class="hljs-keyword">extern</span> <span class="hljs-type">const</span> <span class="hljs-class"><span class="hljs-keyword">struct</span> <span class="hljs-title">usb_backend_ops</span> <span class="hljs-title">usb_libusb_ops</span>;</span><br><span class="hljs-keyword">extern</span> <span class="hljs-type">const</span> <span class="hljs-class"><span class="hljs-keyword">struct</span> <span class="hljs-title">usb_backend_ops</span> <span class="hljs-title">usb_winusb_ops</span>;</span><br><br><span class="hljs-comment">// 根据平台和配置获取后端操作</span><br><span class="hljs-type">static</span> <span class="hljs-type">const</span> <span class="hljs-keyword">struct</span> usb_backend_ops *<span class="hljs-title function_">get_backend_ops</span><span class="hljs-params">(<span class="hljs-type">void</span>)</span> {<br><span class="hljs-meta">#<span class="hljs-keyword">ifdef</span> _WIN32</span><br>    <span class="hljs-keyword">if</span> (current_backend == USB_BACKEND_LIBUSB) {<br>        <span class="hljs-keyword">return</span> &amp;usb_libusb_ops;<br>    } <span class="hljs-keyword">else</span> {<br>        <span class="hljs-keyword">return</span> &amp;usb_winusb_ops;  <span class="hljs-comment">// Windows 默认 WinUSB</span><br>    }<br><span class="hljs-meta">#<span class="hljs-keyword">else</span></span><br>    <span class="hljs-keyword">return</span> &amp;usb_libusb_ops;  <span class="hljs-comment">// Linux/macOS 使用 libusb</span><br><span class="hljs-meta">#<span class="hljs-keyword">endif</span></span><br>}<br><br><span class="hljs-comment">// 批量发送</span><br><span class="hljs-type">int</span> <span class="hljs-title function_">sunxi_usb_bulk_send</span><span class="hljs-params">(<span class="hljs-type">void</span> *handle, <span class="hljs-type">int</span> ep, <span class="hljs-type">const</span> <span class="hljs-type">char</span> *buf, <span class="hljs-type">ssize_t</span> len)</span> {<br>    <span class="hljs-type">const</span> <span class="hljs-class"><span class="hljs-keyword">struct</span> <span class="hljs-title">usb_backend_ops</span> *<span class="hljs-title">ops</span> =</span> get_backend_ops();<br>    <span class="hljs-keyword">if</span> (!ops || !ops-&gt;bulk_send) {<br>        <span class="hljs-keyword">return</span> EFEX_ERR_NOT_SUPPORT;<br>    }<br>    <span class="hljs-keyword">return</span> ops-&gt;bulk_send(handle, ep, buf, len);<br>}<br><br><span class="hljs-comment">// 批量接收</span><br><span class="hljs-type">int</span> <span class="hljs-title function_">sunxi_usb_bulk_recv</span><span class="hljs-params">(<span class="hljs-type">void</span> *handle, <span class="hljs-type">int</span> ep, <span class="hljs-type">char</span> *buf, <span class="hljs-type">ssize_t</span> len)</span> {<br>    <span class="hljs-type">const</span> <span class="hljs-class"><span class="hljs-keyword">struct</span> <span class="hljs-title">usb_backend_ops</span> *<span class="hljs-title">ops</span> =</span> get_backend_ops();<br>    <span class="hljs-keyword">if</span> (!ops || !ops-&gt;bulk_recv) {<br>        <span class="hljs-keyword">return</span> EFEX_ERR_NOT_SUPPORT;<br>    }<br>    <span class="hljs-keyword">return</span> ops-&gt;bulk_recv(handle, ep, buf, len);<br>}<br><br><span class="hljs-comment">// 扫描设备</span><br><span class="hljs-type">int</span> <span class="hljs-title function_">sunxi_scan_usb_device</span><span class="hljs-params">(<span class="hljs-keyword">struct</span> <span class="hljs-type">sunxi_efex_ctx_t</span> *ctx)</span> {<br>    <span class="hljs-type">const</span> <span class="hljs-class"><span class="hljs-keyword">struct</span> <span class="hljs-title">usb_backend_ops</span> *<span class="hljs-title">ops</span> =</span> get_backend_ops();<br>    <span class="hljs-keyword">if</span> (!ops || !ops-&gt;scan_device) {<br>        <span class="hljs-keyword">return</span> EFEX_ERR_NOT_SUPPORT;<br>    }<br>    <span class="hljs-keyword">return</span> ops-&gt;scan_device(ctx);<br>}<br><br><span class="hljs-comment">// 指定位置扫描设备</span><br><span class="hljs-type">int</span> <span class="hljs-title function_">sunxi_scan_usb_device_at</span><span class="hljs-params">(<span class="hljs-keyword">struct</span> <span class="hljs-type">sunxi_efex_ctx_t</span> *ctx, </span><br><span class="hljs-params">                              <span class="hljs-type">uint8_t</span> bus, <span class="hljs-type">uint8_t</span> port)</span> {<br>    <span class="hljs-type">const</span> <span class="hljs-class"><span class="hljs-keyword">struct</span> <span class="hljs-title">usb_backend_ops</span> *<span class="hljs-title">ops</span> =</span> get_backend_ops();<br>    <span class="hljs-keyword">if</span> (!ops || !ops-&gt;scan_device_at) {<br>        <span class="hljs-keyword">return</span> EFEX_ERR_NOT_SUPPORT;<br>    }<br>    <span class="hljs-keyword">return</span> ops-&gt;scan_device_at(ctx, bus, port);<br>}<br><br><span class="hljs-comment">// 扫描所有设备</span><br><span class="hljs-type">int</span> <span class="hljs-title function_">sunxi_scan_usb_devices</span><span class="hljs-params">(<span class="hljs-keyword">struct</span> <span class="hljs-type">sunxi_scanned_device_t</span> **devices, </span><br><span class="hljs-params">                            <span class="hljs-type">size_t</span> *count)</span> {<br>    <span class="hljs-type">const</span> <span class="hljs-class"><span class="hljs-keyword">struct</span> <span class="hljs-title">usb_backend_ops</span> *<span class="hljs-title">ops</span> =</span> get_backend_ops();<br>    <span class="hljs-keyword">if</span> (!ops || !ops-&gt;scan_devices) {<br>        <span class="hljs-keyword">return</span> EFEX_ERR_NOT_SUPPORT;<br>    }<br>    <span class="hljs-keyword">return</span> ops-&gt;scan_devices(devices, count);<br>}<br><br><span class="hljs-comment">// 热插拔快照</span><br><span class="hljs-type">int</span> <span class="hljs-title function_">sunxi_hotplug_snapshot</span><span class="hljs-params">(<span class="hljs-keyword">struct</span> <span class="hljs-type">sunxi_hotplug_device_t</span> **devices, </span><br><span class="hljs-params">                            <span class="hljs-type">size_t</span> *count)</span> {<br>    <span class="hljs-type">const</span> <span class="hljs-class"><span class="hljs-keyword">struct</span> <span class="hljs-title">usb_backend_ops</span> *<span class="hljs-title">ops</span> =</span> get_backend_ops();<br>    <span class="hljs-keyword">if</span> (!ops || !ops-&gt;hotplug_snapshot) {<br>        <span class="hljs-keyword">return</span> EFEX_ERR_NOT_SUPPORT;<br>    }<br>    <span class="hljs-keyword">return</span> ops-&gt;hotplug_snapshot(devices, count);<br>}<br><br><span class="hljs-comment">// 初始化 USB</span><br><span class="hljs-type">int</span> <span class="hljs-title function_">sunxi_usb_init</span><span class="hljs-params">(<span class="hljs-keyword">struct</span> <span class="hljs-type">sunxi_efex_ctx_t</span> *ctx)</span> {<br>    <span class="hljs-type">const</span> <span class="hljs-class"><span class="hljs-keyword">struct</span> <span class="hljs-title">usb_backend_ops</span> *<span class="hljs-title">ops</span> =</span> get_backend_ops();<br>    <span class="hljs-keyword">if</span> (!ops || !ops-&gt;init) {<br>        <span class="hljs-keyword">return</span> EFEX_ERR_NOT_SUPPORT;<br>    }<br>    <span class="hljs-keyword">return</span> ops-&gt;init(ctx);<br>}<br><br><span class="hljs-comment">// 退出 USB</span><br><span class="hljs-type">int</span> <span class="hljs-title function_">sunxi_usb_exit</span><span class="hljs-params">(<span class="hljs-keyword">struct</span> <span class="hljs-type">sunxi_efex_ctx_t</span> *ctx)</span> {<br>    <span class="hljs-type">const</span> <span class="hljs-class"><span class="hljs-keyword">struct</span> <span class="hljs-title">usb_backend_ops</span> *<span class="hljs-title">ops</span> =</span> get_backend_ops();<br>    <span class="hljs-keyword">if</span> (!ops || !ops-&gt;<span class="hljs-built_in">exit</span>) {<br>        <span class="hljs-keyword">return</span> EFEX_ERR_NOT_SUPPORT;<br>    }<br>    <span class="hljs-keyword">return</span> ops-&gt;<span class="hljs-built_in">exit</span>(ctx);<br>}<br><br><span class="hljs-comment">// 设置后端类型</span><br><span class="hljs-type">int</span> <span class="hljs-title function_">sunxi_efex_set_usb_backend</span><span class="hljs-params">(<span class="hljs-keyword">enum</span> usb_backend_type backend)</span> {<br><span class="hljs-meta">#<span class="hljs-keyword">ifdef</span> _WIN32</span><br>    <span class="hljs-keyword">if</span> (backend == USB_BACKEND_LIBUSB || <br>        backend == USB_BACKEND_WINUSB || <br>        backend == USB_BACKEND_AUTO) {<br>        current_backend = backend;<br>        <span class="hljs-keyword">return</span> EFEX_ERR_SUCCESS;<br>    }<br><span class="hljs-meta">#<span class="hljs-keyword">else</span></span><br>    <span class="hljs-keyword">if</span> (backend == USB_BACKEND_LIBUSB || backend == USB_BACKEND_AUTO) {<br>        current_backend = backend;<br>        <span class="hljs-keyword">return</span> EFEX_ERR_SUCCESS;<br>    }<br><span class="hljs-meta">#<span class="hljs-keyword">endif</span></span><br>    <span class="hljs-keyword">return</span> EFEX_ERR_INVALID_PARAM;<br>}<br><br><span class="hljs-comment">// 获取当前后端类型</span><br><span class="hljs-keyword">enum</span> usb_backend_type <span class="hljs-title function_">sunxi_efex_get_usb_backend</span><span class="hljs-params">(<span class="hljs-type">void</span>)</span> {<br>    <span class="hljs-keyword">return</span> current_backend;<br>}<br></code></pre></td></tr></tbody></table></figure><h3 id="后端调度流程图" data-id="后端调度流程图" class="notion-h"><a href="#后端调度流程图" class="headerlink" title="后端调度流程图"></a>后端调度流程图</h3><pre class="mermaid">flowchart TD    A[sunxi_usb_bulk_send/recv] --&gt; B[get_backend_ops]        B --&gt; C{平台检测}        C --&gt;|Linux/macOS| D[返回 libusb_ops]        C --&gt;|Windows| E{current_backend}        E --&gt;|LIBUSB| F[返回 libusb_ops]    E --&gt;|WINUSB/AUTO| G[返回 winusb_ops]        D --&gt; H[调用 ops-&gt;bulk_send/recv]    F --&gt; H    G --&gt; H        H --&gt; I[执行后端具体实现]</pre><h2 id="libusb-后端实现" data-id="libusb-后端实现" class="notion-h"><a href="#libusb-后端实现" class="headerlink" title="libusb 后端实现"></a>libusb 后端实现</h2><p>源码位置: <code>src/usb/usb_layer_libusb.c</code></p><h3 id="批量发送实现" data-id="批量发送实现" class="notion-h"><a href="#批量发送实现" class="headerlink" title="批量发送实现"></a>批量发送实现</h3><p>源码位置: <code>src/usb/usb_layer_libusb.c:16-38</code></p><figure class="highlight c"><table><tbody><tr><td class="code"><pre><code class="hljs c"><span class="hljs-meta">#<span class="hljs-keyword">include</span> <span class="hljs-string">&lt;libusb.h&gt;</span></span><br><br><span class="hljs-type">static</span> <span class="hljs-type">int</span> <span class="hljs-title function_">libusb_bulk_send</span><span class="hljs-params">(<span class="hljs-type">void</span> *handle, <span class="hljs-type">int</span> ep, </span><br><span class="hljs-params">                            <span class="hljs-type">const</span> <span class="hljs-type">char</span> *buf, <span class="hljs-type">ssize_t</span> len)</span> {<br>    <span class="hljs-keyword">if</span> (!handle || !buf || len &lt;= <span class="hljs-number">0</span>) {<br>        <span class="hljs-keyword">return</span> EFEX_ERR_NULL_PTR;<br>    }<br><br>    libusb_device_handle *hdl = (libusb_device_handle *) handle;<br>    <span class="hljs-type">const</span> <span class="hljs-type">size_t</span> max_chunk = <span class="hljs-number">128</span> * <span class="hljs-number">1024</span>;  <span class="hljs-comment">// 最大 128KB 分块</span><br>    <span class="hljs-type">int</span> bytes;<br><br>    <span class="hljs-keyword">while</span> (len &gt; <span class="hljs-number">0</span>) {<br>        <span class="hljs-type">const</span> <span class="hljs-type">size_t</span> chunk = (<span class="hljs-type">size_t</span>)len &lt; max_chunk ? (<span class="hljs-type">size_t</span>)len : max_chunk;<br><br>        sunxi_usb_hex_dump(buf, chunk, <span class="hljs-string">"SEND"</span>);<br><br>        <span class="hljs-type">const</span> <span class="hljs-type">int</span> r = libusb_bulk_transfer(hdl, ep, <br>                                           (<span class="hljs-type">void</span> *) buf, <br>                                           (<span class="hljs-type">int</span>) chunk, <br>                                           &amp;bytes, <br>                                           DEFAULT_USB_TIMEOUT);<br>        <span class="hljs-keyword">if</span> (r != <span class="hljs-number">0</span>) {<br>            <span class="hljs-keyword">return</span> EFEX_ERR_USB_TRANSFER;<br>        }<br>        len -= bytes;<br>        buf += bytes;<br>    }<br>    <span class="hljs-keyword">return</span> EFEX_ERR_SUCCESS;<br>}<br></code></pre></td></tr></tbody></table></figure><h3 id="libusb-批量发送流程图" data-id="libusb-批量发送流程图" class="notion-h"><a href="#libusb-批量发送流程图" class="headerlink" title="libusb 批量发送流程图"></a>libusb 批量发送流程图</h3><pre class="mermaid">flowchart TD    A[libusb_bulk_send] --&gt; B{参数检查}        B --&gt;|失败| C[返回 NULL_PTR]        B --&gt;|通过| D[设置 max_chunk = 128KB]        D --&gt; E{len &gt; 0?}        E --&gt;|否| F[返回 SUCCESS]        E --&gt;|是| G[计算本次 chunk<br>min len, max_chunk]        G --&gt; H[sunxi_usb_hex_dump<br>调试输出]        H --&gt; I[libusb_bulk_transfer<br>OUT 端点]        I --&gt; J{传输成功?}        J --&gt;|否| K[返回 USB_TRANSFER]        J --&gt;|是| L[更新 len -= bytes<br>buf += bytes]        L --&gt; E</pre><h3 id="批量接收实现" data-id="批量接收实现" class="notion-h"><a href="#批量接收实现" class="headerlink" title="批量接收实现"></a>批量接收实现</h3><p>源码位置: <code>src/usb/usb_layer_libusb.c:40-60</code></p><figure class="highlight c"><table><tbody><tr><td class="code"><pre><code class="hljs c"><span class="hljs-type">static</span> <span class="hljs-type">int</span> <span class="hljs-title function_">libusb_bulk_recv</span><span class="hljs-params">(<span class="hljs-type">void</span> *handle, <span class="hljs-type">int</span> ep, </span><br><span class="hljs-params">                            <span class="hljs-type">char</span> *buf, <span class="hljs-type">ssize_t</span> len)</span> {<br>    <span class="hljs-keyword">if</span> (!handle || !buf || len &lt;= <span class="hljs-number">0</span>) {<br>        <span class="hljs-keyword">return</span> EFEX_ERR_NULL_PTR;<br>    }<br><br>    libusb_device_handle *hdl = (libusb_device_handle *) handle;<br>    <span class="hljs-type">int</span> bytes;<br><br>    <span class="hljs-keyword">while</span> (len &gt; <span class="hljs-number">0</span>) {<br>        <span class="hljs-type">const</span> <span class="hljs-type">int</span> r = libusb_bulk_transfer(hdl, ep, <br>                                           (<span class="hljs-type">uint8_t</span> *) buf, <br>                                           (<span class="hljs-type">int</span>) len, <br>                                           &amp;bytes, <br>                                           DEFAULT_USB_TIMEOUT);<br>        <span class="hljs-keyword">if</span> (r != <span class="hljs-number">0</span>) {<br>            <span class="hljs-keyword">return</span> EFEX_ERR_USB_TRANSFER;<br>        }<br><br>        sunxi_usb_hex_dump(buf, (<span class="hljs-type">size_t</span>) bytes, <span class="hljs-string">"RECV"</span>);<br><br>        len -= bytes;<br>        buf += bytes;<br>    }<br>    <span class="hljs-keyword">return</span> EFEX_ERR_SUCCESS;<br>}<br></code></pre></td></tr></tbody></table></figure><h3 id="libusb-批量接收流程图" data-id="libusb-批量接收流程图" class="notion-h"><a href="#libusb-批量接收流程图" class="headerlink" title="libusb 批量接收流程图"></a>libusb 批量接收流程图</h3><pre class="mermaid">flowchart TD    A[libusb_bulk_recv] --&gt; B{参数检查}        B --&gt;|失败| C[返回 NULL_PTR]        B --&gt;|通过| D{len &gt; 0?}        D --&gt;|否| E[返回 SUCCESS]        D --&gt;|是| F[libusb_bulk_transfer<br>IN 端点]        F --&gt; G{传输成功?}        G --&gt;|否| H[返回 USB_TRANSFER]        G --&gt;|是| I[sunxi_usb_hex_dump<br>调试输出]        I --&gt; J[更新 len -= bytes<br>buf += bytes]        J --&gt; D</pre><h3 id="设备扫描实现" data-id="设备扫描实现" class="notion-h"><a href="#设备扫描实现" class="headerlink" title="设备扫描实现"></a>设备扫描实现</h3><p>源码位置: <code>src/usb/usb_layer_libusb.c:72-105</code></p><figure class="highlight c"><table><tbody><tr><td class="code"><pre><code class="hljs c"><span class="hljs-type">static</span> <span class="hljs-type">int</span> <span class="hljs-title function_">libusb_scan_device</span><span class="hljs-params">(<span class="hljs-keyword">struct</span> <span class="hljs-type">sunxi_efex_ctx_t</span> *ctx)</span> {<br>    <span class="hljs-keyword">if</span> (!ctx) {<br>        <span class="hljs-keyword">return</span> EFEX_ERR_NULL_PTR;<br>    }<br><br>    libusb_device **<span class="hljs-built_in">list</span> = <span class="hljs-literal">NULL</span>;<br>    libusb_context *context = <span class="hljs-literal">NULL</span>;<br><br>    <span class="hljs-comment">// 初始化 libusb</span><br>    libusb_init(&amp;context);<br>    ctx-&gt;usb_context = context;<br>    <br>    <span class="hljs-comment">// 获取设备列表</span><br>    <span class="hljs-type">const</span> <span class="hljs-type">size_t</span> count = libusb_get_device_list(context, &amp;<span class="hljs-built_in">list</span>);<br>    <br>    <span class="hljs-keyword">for</span> (<span class="hljs-type">size_t</span> i = <span class="hljs-number">0</span>; i &lt; count; i++) {<br>        libusb_device *device = <span class="hljs-built_in">list</span>[i];<br>        <span class="hljs-class"><span class="hljs-keyword">struct</span> <span class="hljs-title">libusb_device_descriptor</span> <span class="hljs-title">desc</span>;</span><br>        <br>        <span class="hljs-keyword">if</span> (libusb_get_device_descriptor(device, &amp;desc) != <span class="hljs-number">0</span>) {<br>            <span class="hljs-keyword">return</span> EFEX_ERR_USB_DEVICE_NOT_FOUND;<br>        }<br>        <br>        <span class="hljs-comment">// 匹配 VID/PID</span><br>        <span class="hljs-keyword">if</span> (desc.idVendor == SUNXI_USB_VENDOR &amp;&amp; <br>            desc.idProduct == SUNXI_USB_PRODUCT) {<br>            libusb_device_handle *libusb_hdl = <span class="hljs-literal">NULL</span>;<br>            <span class="hljs-type">int</span> rc = libusb_open(device, &amp;libusb_hdl);<br>            <br>            <span class="hljs-keyword">if</span> (rc != <span class="hljs-number">0</span>) {<br>                <span class="hljs-built_in">fprintf</span>(<span class="hljs-built_in">stderr</span>, <span class="hljs-string">"ERROR: Can't connect to device: %s (rc=%d)\r\n"</span>,<br>                        libusb_strerror(rc), rc);<br>                libusb_free_device_list(<span class="hljs-built_in">list</span>, <span class="hljs-number">1</span>);<br>                <span class="hljs-keyword">return</span> libusb_open_error_to_efex(rc);<br>            }<br>            <br>            ctx-&gt;hdl = libusb_hdl;<br>            libusb_free_device_list(<span class="hljs-built_in">list</span>, <span class="hljs-number">1</span>);<br>            <span class="hljs-keyword">return</span> EFEX_ERR_SUCCESS;<br>        }<br>    }<br>    <br>    libusb_free_device_list(<span class="hljs-built_in">list</span>, <span class="hljs-number">1</span>);<br>    <span class="hljs-keyword">return</span> EFEX_ERR_USB_DEVICE_NOT_FOUND;<br>}<br></code></pre></td></tr></tbody></table></figure><h3 id="libusb-设备扫描流程图" data-id="libusb-设备扫描流程图" class="notion-h"><a href="#libusb-设备扫描流程图" class="headerlink" title="libusb 设备扫描流程图"></a>libusb 设备扫描流程图</h3><pre class="mermaid">flowchart TD    A[libusb_scan_device] --&gt; B[libusb_init<br>初始化上下文]        B --&gt; C[libusb_get_device_list<br>获取设备列表]        C --&gt; D[遍历设备列表]        D --&gt; E[libusb_get_device_descriptor]        E --&gt; F{VID == 0x1f3a<br>PID == 0xefe8?}        F --&gt;|否| G[继续下一个设备]    G --&gt; D        F --&gt;|是| H[libusb_open<br>打开设备]        H --&gt; I{打开成功?}        I --&gt;|否| J[返回错误码]        I --&gt;|是| K[保存设备句柄到 ctx]        K --&gt; L[释放设备列表]        L --&gt; M[返回 SUCCESS]</pre><h3 id="后端初始化实现" data-id="后端初始化实现" class="notion-h"><a href="#后端初始化实现" class="headerlink" title="后端初始化实现"></a>后端初始化实现</h3><p>源码位置: <code>src/usb/usb_layer_libusb.c:299-330</code></p><figure class="highlight c"><table><tbody><tr><td class="code"><pre><code class="hljs c"><span class="hljs-type">static</span> <span class="hljs-type">int</span> <span class="hljs-title function_">libusb_backend_init</span><span class="hljs-params">(<span class="hljs-keyword">struct</span> <span class="hljs-type">sunxi_efex_ctx_t</span> *ctx)</span> {<br>    <span class="hljs-keyword">if</span> (ctx &amp;&amp; ctx-&gt;hdl) {<br>        libusb_device_handle *libusb_hdl = <br>            (libusb_device_handle *) ctx-&gt;hdl;<br>        <span class="hljs-class"><span class="hljs-keyword">struct</span> <span class="hljs-title">libusb_config_descriptor</span> *<span class="hljs-title">config</span>;</span><br><br>        <span class="hljs-comment">// 分离内核驱动 (Linux)</span><br>        <span class="hljs-keyword">if</span> (libusb_kernel_driver_active(libusb_hdl, <span class="hljs-number">0</span>))<br>            libusb_detach_kernel_driver(libusb_hdl, <span class="hljs-number">0</span>);<br><br>        <span class="hljs-comment">// 声称接口</span><br>        <span class="hljs-keyword">if</span> (libusb_claim_interface(libusb_hdl, <span class="hljs-number">0</span>) == <span class="hljs-number">0</span>) {<br>            <span class="hljs-keyword">if</span> (libusb_get_active_config_descriptor(<br>                    libusb_get_device(libusb_hdl), &amp;config) == <span class="hljs-number">0</span>) {<br>                <br>                <span class="hljs-comment">// 查找端点</span><br>                <span class="hljs-keyword">for</span> (<span class="hljs-type">int</span> if_idx = <span class="hljs-number">0</span>; if_idx &lt; config-&gt;bNumInterfaces; if_idx++) {<br>                    <span class="hljs-type">const</span> <span class="hljs-class"><span class="hljs-keyword">struct</span> <span class="hljs-title">libusb_interface</span> *<span class="hljs-title">iface</span> =</span> <br>                        config-&gt;interface + if_idx;<br>                    <br>                    <span class="hljs-keyword">for</span> (<span class="hljs-type">int</span> set_idx = <span class="hljs-number">0</span>; <br>                         set_idx &lt; iface-&gt;num_altsetting; <br>                         set_idx++) {<br>                        <span class="hljs-type">const</span> <span class="hljs-class"><span class="hljs-keyword">struct</span> <span class="hljs-title">libusb_interface_descriptor</span> *<span class="hljs-title">setting</span> =</span> <br>                            iface-&gt;altsetting + set_idx;<br>                        <br>                        <span class="hljs-keyword">for</span> (<span class="hljs-type">int</span> ep_idx = <span class="hljs-number">0</span>; <br>                             ep_idx &lt; setting-&gt;bNumEndpoints; <br>                             ep_idx++) {<br>                            <span class="hljs-type">const</span> <span class="hljs-class"><span class="hljs-keyword">struct</span> <span class="hljs-title">libusb_endpoint_descriptor</span> *<span class="hljs-title">ep</span> =</span> <br>                                setting-&gt;endpoint + ep_idx;<br>                            <br>                            <span class="hljs-comment">// 只处理批量端点</span><br>                            <span class="hljs-keyword">if</span> ((ep-&gt;bmAttributes &amp; LIBUSB_TRANSFER_TYPE_MASK) <br>                                != LIBUSB_TRANSFER_TYPE_BULK)<br>                                <span class="hljs-keyword">continue</span>;<br>                            <br>                            <span class="hljs-comment">// IN 还是 OUT 端点</span><br>                            <span class="hljs-keyword">if</span> ((ep-&gt;bEndpointAddress &amp; LIBUSB_ENDPOINT_DIR_MASK) <br>                                == LIBUSB_ENDPOINT_IN)<br>                                ctx-&gt;epin = ep-&gt;bEndpointAddress;<br>                            <span class="hljs-keyword">else</span><br>                                ctx-&gt;epout = ep-&gt;bEndpointAddress;<br>                        }<br>                    }<br>                }<br>                libusb_free_config_descriptor(config);<br>                <span class="hljs-keyword">return</span> EFEX_ERR_SUCCESS;<br>            }<br>        }<br>    }<br>    <span class="hljs-keyword">return</span> EFEX_ERR_USB_INIT;<br>}<br></code></pre></td></tr></tbody></table></figure><h3 id="libusb-后端初始化流程图" data-id="libusb-后端初始化流程图" class="notion-h"><a href="#libusb-后端初始化流程图" class="headerlink" title="libusb 后端初始化流程图"></a>libusb 后端初始化流程图</h3><pre class="mermaid">flowchart TD    A[libusb_backend_init] --&gt; B{ctx-&gt;hdl 有效?}        B --&gt;|否| C[返回 USB_INIT 错误]        B --&gt;|是| D[libusb_kernel_driver_active]        D --&gt; E{内核驱动活跃?}        E --&gt;|是| F[libusb_detach_kernel_driver]    E --&gt;|否| G[继续]        F --&gt; G        G --&gt; H[libusb_claim_interface<br>声称接口 0]        H --&gt; I{声称成功?}        I --&gt;|否| C        I --&gt;|是| J[libusb_get_active_config_descriptor]        J --&gt; K[遍历接口和端点]        K --&gt; L{端点类型?}        L --&gt;|非批量| M[跳过]    L --&gt;|批量| N{方向?}        N --&gt;|IN| O[保存 epin]    N --&gt;|OUT| P[保存 epout]        M --&gt; K    O --&gt; K    P --&gt; K        K --&gt; Q[释放配置描述符]        Q --&gt; R[返回 SUCCESS]</pre><h3 id="后端退出实现" data-id="后端退出实现" class="notion-h"><a href="#后端退出实现" class="headerlink" title="后端退出实现"></a>后端退出实现</h3><p>源码位置: <code>src/usb/usb_layer_libusb.c:332-350</code></p><figure class="highlight c"><table><tbody><tr><td class="code"><pre><code class="hljs c"><span class="hljs-type">static</span> <span class="hljs-type">int</span> <span class="hljs-title function_">libusb_backend_exit</span><span class="hljs-params">(<span class="hljs-keyword">struct</span> <span class="hljs-type">sunxi_efex_ctx_t</span> *ctx)</span> {<br>    <span class="hljs-keyword">if</span> (!ctx) {<br>        <span class="hljs-keyword">return</span> EFEX_ERR_NULL_PTR;<br>    }<br><br>    <span class="hljs-keyword">if</span> (ctx-&gt;hdl) {<br>        libusb_device_handle *libusb_hdl = <br>            (libusb_device_handle *) ctx-&gt;hdl;<br>        libusb_release_interface(libusb_hdl, <span class="hljs-number">0</span>);<br>        libusb_close(libusb_hdl);<br>        ctx-&gt;hdl = <span class="hljs-literal">NULL</span>;<br>    }<br><br>    <span class="hljs-keyword">if</span> (ctx-&gt;usb_context) {<br>        libusb_exit((libusb_context *) ctx-&gt;usb_context);<br>        ctx-&gt;usb_context = <span class="hljs-literal">NULL</span>;<br>    }<br><br>    <span class="hljs-keyword">return</span> EFEX_ERR_SUCCESS;<br>}<br></code></pre></td></tr></tbody></table></figure><h3 id="libusb-Ops-结构" data-id="libusb-Ops-结构" class="notion-h"><a href="#libusb-Ops-结构" class="headerlink" title="libusb Ops 结构"></a>libusb Ops 结构</h3><p>源码位置: <code>src/usb/usb_layer_libusb.c:352-361</code></p><figure class="highlight c"><table><tbody><tr><td class="code"><pre><code class="hljs c"><span class="hljs-type">const</span> <span class="hljs-class"><span class="hljs-keyword">struct</span> <span class="hljs-title">usb_backend_ops</span> <span class="hljs-title">usb_libusb_ops</span> =</span> {<br>    .bulk_send = libusb_bulk_send,<br>    .bulk_recv = libusb_bulk_recv,<br>    .scan_device = libusb_scan_device,<br>    .scan_device_at = libusb_scan_device_at,<br>    .scan_devices = libusb_scan_devices,<br>    .hotplug_snapshot = libusb_hotplug_snapshot,<br>    .init = libusb_backend_init,<br>    .<span class="hljs-built_in">exit</span> = libusb_backend_exit,<br>};<br></code></pre></td></tr></tbody></table></figure><h2 id="后端选择流程图" data-id="后端选择流程图" class="notion-h"><a href="#后端选择流程图" class="headerlink" title="后端选择流程图"></a>后端选择流程图</h2><pre class="mermaid">flowchart TD    A[sunxi_efex_set_usb_backend] --&gt; B{平台检查}        B --&gt;|Windows| C{backend 类型?}    B --&gt;|Linux/macOS| D{backend == LIBUSB 或 AUTO?}        C --&gt;|LIBUSB| E[设置 current_backend = LIBUSB]    C --&gt;|WINUSB| F[设置 current_backend = WINUSB]    C --&gt;|AUTO| G[设置 current_backend = AUTO]        D --&gt;|是| H[设置 current_backend]    D --&gt;|否| I[返回 INVALID_PARAM]        E --&gt; J[返回 SUCCESS]    F --&gt; J    G --&gt; J    H --&gt; J        J --&gt; K[后续调用 get_backend_ops]        K --&gt; L{平台 + backend}        L --&gt;|Linux/macOS| M[返回 libusb_ops]    L --&gt;|Windows + LIBUSB| N[返回 libusb_ops]    L --&gt;|Windows + WINUSB/AUTO| O[返回 winusb_ops]</pre><h2 id="平台差异处理" data-id="平台差异处理" class="notion-h"><a href="#平台差异处理" class="headerlink" title="平台差异处理"></a>平台差异处理</h2><h3 id="Linux" data-id="Linux" class="notion-h"><a href="#Linux" class="headerlink" title="Linux"></a>Linux</h3><ul><li>使用 udev 规则配置设备权限</li><li>libusb 直接访问 USB 设备</li><li>可能需要 <code>libusb_detach_kernel_driver</code> 分离内核驱动</li></ul><figure class="highlight bash"><table><tbody><tr><td class="code"><pre><code class="hljs bash"><span class="hljs-comment"># udev 规则示例 (/etc/udev/rules.d/99-sunxi.rules)</span><br>SUBSYSTEM==<span class="hljs-string">"usb"</span>, ATTR{idVendor}==<span class="hljs-string">"1f3a"</span>, ATTR{idProduct}==<span class="hljs-string">"efe8"</span>, MODE=<span class="hljs-string">"0666"</span><br></code></pre></td></tr></tbody></table></figure><h3 id="macOS" data-id="macOS" class="notion-h"><a href="#macOS" class="headerlink" title="macOS"></a>macOS</h3><ul><li>使用 IOKit 框架辅助设备枚举</li><li>libusb 需要处理权限问题</li><li>可能需要签名配置</li></ul><h3 id="Windows" data-id="Windows" class="notion-h"><a href="#Windows" class="headerlink" title="Windows"></a>Windows</h3><ul><li>使用 SetupAPI 枚举设备</li><li>WinUSB 需要安装驱动</li><li>处理驱动兼容性问题</li></ul><pre class="mermaid">graph TB    subgraph Linux["Linux 平台"]        L1[udev 规则]        L2[libusb 后端]        L3[设备权限]    end        subgraph MacOS["macOS 平台"]        M1[IOKit]        M2[libusb 后端]        M3[签名配置]    end        subgraph Windows["Windows 平台"]        W1[SetupAPI]        W2[WinUSB/libusb]        W3[驱动安装]    end        L1 --&gt; L2 --&gt; L3    M1 --&gt; M2 --&gt; M3    W1 --&gt; W2 --&gt; W3</pre><h2 id="USB-设备路径格式" data-id="USB-设备路径格式" class="notion-h"><a href="#USB-设备路径格式" class="headerlink" title="USB 设备路径格式"></a>USB 设备路径格式</h2><p>不同平台的设备路径格式不同：</p><table><thead><tr><th>平台</th><th>格式</th><th>示例</th></tr></thead><tbody><tr><td>Linux</td><td>bus:port</td><td><code>1:2</code></td></tr><tr><td>macOS</td><td>bus:port</td><td><code>1:2</code></td></tr><tr><td>Windows</td><td>设备路径</td><td><code>\\?\USB#VID_1f3a&amp;PID_efe8#...</code></td></tr></tbody></table><p>libusb 后端生成的路径格式:</p><p>源码位置: <code>src/usb/usb_layer_libusb.c:243-247</code></p><figure class="highlight c"><table><tbody><tr><td class="code"><pre><code class="hljs c"><span class="hljs-comment">// 生成设备路径字符串</span><br><span class="hljs-type">char</span> buf[<span class="hljs-number">32</span>];<br><span class="hljs-built_in">snprintf</span>(buf, <span class="hljs-keyword">sizeof</span>(buf), <span class="hljs-string">"libusb:%u:%u"</span>,<br>         (<span class="hljs-type">unsigned</span>)result[idx].bus_id, <br>         (<span class="hljs-type">unsigned</span>)result[idx].port);<br>result[idx].device_path = strdup(buf);<br></code></pre></td></tr></tbody></table></figure><h2 id="热插拔快照实现" data-id="热插拔快照实现" class="notion-h"><a href="#热插拔快照实现" class="headerlink" title="热插拔快照实现"></a>热插拔快照实现</h2><p>源码位置: <code>src/usb/usb_layer_libusb.c:179-258</code></p><figure class="highlight c"><table><tbody><tr><td class="code"><pre><code class="hljs c"><span class="hljs-type">static</span> <span class="hljs-type">int</span> <span class="hljs-title function_">libusb_hotplug_snapshot</span><span class="hljs-params">(<span class="hljs-keyword">struct</span> <span class="hljs-type">sunxi_hotplug_device_t</span> **devices, </span><br><span class="hljs-params">                                   <span class="hljs-type">size_t</span> *count)</span> {<br>    <span class="hljs-keyword">if</span> (!devices || !count) {<br>        <span class="hljs-keyword">return</span> EFEX_ERR_NULL_PTR;<br>    }<br><br>    *devices = <span class="hljs-literal">NULL</span>;<br>    *count = <span class="hljs-number">0</span>;<br><br>    libusb_device **<span class="hljs-built_in">list</span> = <span class="hljs-literal">NULL</span>;<br>    libusb_context *context = <span class="hljs-literal">NULL</span>;<br><br>    <span class="hljs-type">int</span> r = libusb_init(&amp;context);<br>    <span class="hljs-keyword">if</span> (r &lt; <span class="hljs-number">0</span>) {<br>        <span class="hljs-keyword">return</span> EFEX_ERR_USB_INIT;<br>    }<br><br>    <span class="hljs-type">const</span> <span class="hljs-type">ssize_t</span> device_count = libusb_get_device_list(context, &amp;<span class="hljs-built_in">list</span>);<br>    <span class="hljs-comment">// ... 扫描并构建设备列表 ...</span><br><br>    <span class="hljs-comment">// 为每个设备创建结构</span><br>    <span class="hljs-keyword">for</span> (<span class="hljs-type">ssize_t</span> i = <span class="hljs-number">0</span>; i &lt; device_count &amp;&amp; idx &lt; found_count; i++) {<br>        libusb_device *device = <span class="hljs-built_in">list</span>[i];<br>        <span class="hljs-class"><span class="hljs-keyword">struct</span> <span class="hljs-title">libusb_device_descriptor</span> <span class="hljs-title">desc</span>;</span><br>        <br>        <span class="hljs-keyword">if</span> (desc.idVendor == SUNXI_USB_VENDOR &amp;&amp; <br>            desc.idProduct == SUNXI_USB_PRODUCT) {<br>            result[idx].vid = desc.idVendor;<br>            result[idx].pid = desc.idProduct;<br>            result[idx].bus_id = libusb_get_bus_number(device);<br>            result[idx].usb_device_id = libusb_get_device_address(device);<br>            result[idx].port = libusb_get_port_number(device);<br>            <br>            <span class="hljs-comment">// 生成路径</span><br>            <span class="hljs-built_in">snprintf</span>(buf, <span class="hljs-keyword">sizeof</span>(buf), <span class="hljs-string">"libusb:%u:%u"</span>,<br>                     (<span class="hljs-type">unsigned</span>)result[idx].bus_id, <br>                     (<span class="hljs-type">unsigned</span>)result[idx].port);<br>            result[idx].device_path = strdup(buf);<br>            idx++;<br>        }<br>    }<br><br>    libusb_free_device_list(<span class="hljs-built_in">list</span>, <span class="hljs-number">1</span>);<br>    libusb_exit(context);<br><br>    *devices = result;<br>    *count = found_count;<br>    <span class="hljs-keyword">return</span> EFEX_ERR_SUCCESS;<br>}<br></code></pre></td></tr></tbody></table></figure><h2 id="API-使用示例" data-id="API-使用示例" class="notion-h"><a href="#API-使用示例" class="headerlink" title="API 使用示例"></a>API 使用示例</h2><h3 id="选择后端" data-id="选择后端" class="notion-h"><a href="#选择后端" class="headerlink" title="选择后端"></a>选择后端</h3><figure class="highlight c"><table><tbody><tr><td class="code"><pre><code class="hljs c"><span class="hljs-meta">#<span class="hljs-keyword">include</span> <span class="hljs-string">&lt;libefex.h&gt;</span></span><br><br><span class="hljs-type">int</span> <span class="hljs-title function_">main</span><span class="hljs-params">()</span> {<br>    <span class="hljs-comment">// Windows 上可以选择 WinUSB 或 libusb</span><br><span class="hljs-meta">#<span class="hljs-keyword">ifdef</span> _WIN32</span><br>    <span class="hljs-comment">// 强制使用 WinUSB</span><br>    sunxi_efex_set_usb_backend(USB_BACKEND_WINUSB);<br>    <br>    <span class="hljs-comment">// 或者强制使用 libusb</span><br>    <span class="hljs-comment">// sunxi_efex_set_usb_backend(USB_BACKEND_LIBUSB);</span><br><span class="hljs-meta">#<span class="hljs-keyword">endif</span></span><br><br>    <span class="hljs-comment">// Linux/macOS 只支持 libusb</span><br>    <span class="hljs-comment">// 自动选择即可</span><br>    <br>    <span class="hljs-class"><span class="hljs-keyword">struct</span> <span class="hljs-title">sunxi_efex_ctx_t</span> <span class="hljs-title">ctx</span> =</span> {<span class="hljs-number">0</span>};<br>    sunxi_scan_usb_device(&amp;ctx);<br>    sunxi_usb_init(&amp;ctx);<br>    sunxi_efex_init(&amp;ctx);<br>    <br>    <span class="hljs-built_in">printf</span>(<span class="hljs-string">"Current backend: %d\n"</span>, sunxi_efex_get_usb_backend());<br>    <br>    <span class="hljs-comment">// ... 使用 API ...</span><br>    <br>    sunxi_usb_exit(&amp;ctx);<br>    <span class="hljs-keyword">return</span> <span class="hljs-number">0</span>;<br>}<br></code></pre></td></tr></tbody></table></figure><h3 id="热插拔检测" data-id="热插拔检测" class="notion-h"><a href="#热插拔检测" class="headerlink" title="热插拔检测"></a>热插拔检测</h3><figure class="highlight c"><table><tbody><tr><td class="code"><pre><code class="hljs c"><span class="hljs-type">void</span> <span class="hljs-title function_">monitor_devices</span><span class="hljs-params">()</span> {<br>    <span class="hljs-class"><span class="hljs-keyword">struct</span> <span class="hljs-title">sunxi_hotplug_device_t</span> *<span class="hljs-title">devices</span> =</span> <span class="hljs-literal">NULL</span>;<br>    <span class="hljs-type">size_t</span> count = <span class="hljs-number">0</span>;<br>    <br>    <span class="hljs-type">int</span> ret = sunxi_hotplug_snapshot(&amp;devices, &amp;count);<br>    <br>    <span class="hljs-keyword">if</span> (ret == EFEX_ERR_SUCCESS) {<br>        <span class="hljs-built_in">printf</span>(<span class="hljs-string">"Found %zu EFEX devices:\n"</span>, count);<br>        <br>        <span class="hljs-keyword">for</span> (<span class="hljs-type">size_t</span> i = <span class="hljs-number">0</span>; i &lt; count; i++) {<br>            <span class="hljs-built_in">printf</span>(<span class="hljs-string">"  Device %zu: VID=%04x PID=%04x path=%s\n"</span>,<br>                   i, devices[i].vid, devices[i].pid, <br>                   devices[i].device_path);<br>        }<br>        <br>        <span class="hljs-comment">// 清理</span><br>        sunxi_hotplug_free_snapshot(devices, count);<br>    }<br>}<br></code></pre></td></tr></tbody></table></figure>]]>
    </content>
    <id>https://gloomyghost.com/posts/libefex-usb-backend/</id>
    <link href="https://gloomyghost.com/posts/libefex-usb-backend/"/>
    <published>2026-04-29T16:00:00.000Z</published>
    <summary>
      <![CDATA[<h1 id="libefex-跨平台-USB-后端" data-id="libefex-跨平台-USB-后端" class="notion-h"><a href="#libefex-跨平台-USB-后端" class="headerlink" title="libefex 跨平]]>
    </summary>
    <title>libefex 跨平台 USB 后端：libusb 与 WinUSB 实现</title>
    <updated>2026-05-29T20:33:31.220Z</updated>
  </entry>
  <entry>
    <author>
      <name>柚木 鉉</name>
    </author>
    <category term="嵌入式" scheme="https://gloomyghost.com/tags/%E5%B5%8C%E5%85%A5%E5%BC%8F/"/>
    <category term="C" scheme="https://gloomyghost.com/tags/C/"/>
    <category term="libefex" scheme="https://gloomyghost.com/tags/libefex/"/>
    <category term="全志芯片" scheme="https://gloomyghost.com/tags/%E5%85%A8%E5%BF%97%E8%8A%AF%E7%89%87/"/>
    <category term="USB协议" scheme="https://gloomyghost.com/tags/USB%E5%8D%8F%E8%AE%AE/"/>
    <content>
      <![CDATA[<h1 id="libefex-USB-协议层" data-id="libefex-USB-协议层" class="notion-h"><a href="#libefex-USB-协议层" class="headerlink" title="libefex USB 协议层"></a>libefex USB 协议层</h1><h2 id="USB-数据包结构" data-id="USB-数据包结构" class="notion-h"><a href="#USB-数据包结构" class="headerlink" title="USB 数据包结构"></a>USB 数据包结构</h2><p>libefex 使用特定的数据包格式与 Sunxi 设备通信。所有通信基于 USB 批量传输。</p><h3 id="请求包结构-sunxi-usb-request-t" data-id="请求包结构-sunxi-usb-request-t" class="notion-h"><a href="#请求包结构-sunxi-usb-request-t" class="headerlink" title="请求包结构 (sunxi_usb_request_t)"></a>请求包结构 (<code>sunxi_usb_request_t</code>)</h3><p>源码位置: <code>includes/efex-protocol.h:115-127</code></p><figure class="highlight c"><table><tbody><tr><td class="code"><pre><code class="hljs c">EFEX_PACKED_BEGIN<br><span class="hljs-class"><span class="hljs-keyword">struct</span> <span class="hljs-title">sunxi_usb_request_t</span> {</span><br>    <span class="hljs-class"><span class="hljs-keyword">union</span> {</span><br>        <span class="hljs-type">char</span> magic[<span class="hljs-number">4</span>];           <span class="hljs-comment">// "AWUC" (Allwinner USB Command)</span><br>        <span class="hljs-type">uint32_t</span> magics;<br>    };<br>    <span class="hljs-type">uint32_t</span> tab;                <span class="hljs-comment">// 标签/序列号</span><br>    <span class="hljs-type">uint32_t</span> data_length;        <span class="hljs-comment">// 数据长度</span><br>    <span class="hljs-type">uint16_t</span> resvered1;          <span class="hljs-comment">// 保留字段</span><br>    <span class="hljs-type">uint8_t</span> resvered2;           <span class="hljs-comment">// 保留字段</span><br>    <span class="hljs-type">uint8_t</span> cmd_length;          <span class="hljs-comment">// 命令长度</span><br>    <span class="hljs-type">uint8_t</span> cmd_package[<span class="hljs-number">16</span>];     <span class="hljs-comment">// 命令包</span><br>} EFEX_PACKED;<br>EFEX_PACKED_END<br></code></pre></td></tr></tbody></table></figure><pre class="mermaid">block-beta    columns 8    block:Header:8        columns 4        M["Magic<br>4 bytes<br>AWUC"]        T["Tag<br>4 bytes"]        L["Length<br>4 bytes"]        P["Cmd Package<br>16 bytes"]    end    block:Data:8        columns 1        D["Data<br>Variable length"]    end</pre><h3 id="响应包结构-sunxi-usb-response-t" data-id="响应包结构-sunxi-usb-response-t" class="notion-h"><a href="#响应包结构-sunxi-usb-response-t" class="headerlink" title="响应包结构 (sunxi_usb_response_t)"></a>响应包结构 (<code>sunxi_usb_response_t</code>)</h3><p>源码位置: <code>includes/efex-protocol.h:130-141</code></p><figure class="highlight c"><table><tbody><tr><td class="code"><pre><code class="hljs c">EFEX_PACKED_BEGIN<br><span class="hljs-class"><span class="hljs-keyword">struct</span> <span class="hljs-title">sunxi_usb_response_t</span> {</span><br>    <span class="hljs-class"><span class="hljs-keyword">union</span> {</span><br>        <span class="hljs-type">char</span> magic[<span class="hljs-number">4</span>];           <span class="hljs-comment">// "AWUS" (Allwinner USB Status)</span><br>        <span class="hljs-type">uint32_t</span> magics;<br>    };<br>    <span class="hljs-type">uint32_t</span> tag;                <span class="hljs-comment">// 标签 (与请求匹配)</span><br>    <span class="hljs-type">uint32_t</span> residue;            <span class="hljs-comment">// 剩余数据</span><br>    <span class="hljs-type">uint8_t</span> status;              <span class="hljs-comment">// 操作状态码</span><br>} EFEX_PACKED;<br>EFEX_PACKED_END<br></code></pre></td></tr></tbody></table></figure><h3 id="EFEX-命令包结构-sunxi-efex-request-t" data-id="EFEX-命令包结构-sunxi-efex-request-t" class="notion-h"><a href="#EFEX-命令包结构-sunxi-efex-request-t" class="headerlink" title="EFEX 命令包结构 (sunxi_efex_request_t)"></a>EFEX 命令包结构 (<code>sunxi_efex_request_t</code>)</h3><p>源码位置: <code>includes/efex-protocol.h:143-151</code></p><figure class="highlight c"><table><tbody><tr><td class="code"><pre><code class="hljs c">EFEX_PACKED_BEGIN<br><span class="hljs-class"><span class="hljs-keyword">struct</span> <span class="hljs-title">sunxi_efex_request_t</span> {</span><br>    <span class="hljs-type">uint16_t</span> cmd;       <span class="hljs-comment">// 命令类型</span><br>    <span class="hljs-type">uint16_t</span> tag;       <span class="hljs-comment">// 标签</span><br>    <span class="hljs-type">uint32_t</span> address;   <span class="hljs-comment">// 内存地址</span><br>    <span class="hljs-type">uint32_t</span> len;       <span class="hljs-comment">// 数据长度</span><br>    <span class="hljs-type">uint32_t</span> flags;     <span class="hljs-comment">// 标志位</span><br>} EFEX_PACKED;<br>EFEX_PACKED_END<br></code></pre></td></tr></tbody></table></figure><pre class="mermaid">block-beta    columns 1    block:CmdPackage:1        columns 5        C["Command<br>2 bytes"]        G["Tag<br>2 bytes"]        A["Address<br>4 bytes"]        L["Length<br>4 bytes"]        F["Flags<br>4 bytes"]    end</pre><h3 id="FES-传输结构-sunxi-fes-xfer-t" data-id="FES-传输结构-sunxi-fes-xfer-t" class="notion-h"><a href="#FES-传输结构-sunxi-fes-xfer-t" class="headerlink" title="FES 传输结构 (sunxi_fes_xfer_t)"></a>FES 传输结构 (<code>sunxi_fes_xfer_t</code>)</h3><p>源码位置: <code>includes/efex-protocol.h:214-224</code></p><figure class="highlight c"><table><tbody><tr><td class="code"><pre><code class="hljs c">EFEX_PACKED_BEGIN<br><span class="hljs-class"><span class="hljs-keyword">struct</span> <span class="hljs-title">sunxi_fes_xfer_t</span> {</span><br>    <span class="hljs-type">uint16_t</span> cmd;       <span class="hljs-comment">// 命令类型</span><br>    <span class="hljs-type">uint16_t</span> tag;       <span class="hljs-comment">// 标签</span><br>    <span class="hljs-type">char</span> buf[<span class="hljs-number">12</span>];       <span class="hljs-comment">// 缓冲区 (用于传输参数)</span><br>    <span class="hljs-class"><span class="hljs-keyword">union</span> {</span><br>        <span class="hljs-type">char</span> magic[<span class="hljs-number">4</span>];  <span class="hljs-comment">// 魔数</span><br>        <span class="hljs-type">uint32_t</span> magics;<br>    };<br>} EFEX_PACKED;<br>EFEX_PACKED_END<br></code></pre></td></tr></tbody></table></figure><h2 id="USB-命令类型定义" data-id="USB-命令类型定义" class="notion-h"><a href="#USB-命令类型定义" class="headerlink" title="USB 命令类型定义"></a>USB 命令类型定义</h2><p>源码位置: <code>includes/efex-protocol.h:62-95</code></p><figure class="highlight c"><table><tbody><tr><td class="code"><pre><code class="hljs c"><span class="hljs-class"><span class="hljs-keyword">enum</span> <span class="hljs-title">sunxi_efex_cmd_t</span> {</span><br>    <span class="hljs-comment">/* Common Commands */</span><br>    EFEX_CMD_VERIFY_DEVICE = <span class="hljs-number">0x0001</span>,    <span class="hljs-comment">// 验证设备</span><br>    EFEX_CMD_SWITCH_ROLE = <span class="hljs-number">0x0002</span>,      <span class="hljs-comment">// 切换角色</span><br>    EFEX_CMD_IS_READY = <span class="hljs-number">0x0003</span>,         <span class="hljs-comment">// 检查就绪</span><br>    EFEX_CMD_DISCONNECT = <span class="hljs-number">0x0010</span>,       <span class="hljs-comment">// 断开连接</span><br><br>    <span class="hljs-comment">/* FEL Commands */</span><br>    EFEX_CMD_FEL_WRITE = <span class="hljs-number">0x0101</span>,        <span class="hljs-comment">// 写入内存</span><br>    EFEX_CMD_FEL_EXEC = <span class="hljs-number">0x0102</span>,         <span class="hljs-comment">// 执行代码</span><br>    EFEX_CMD_FEL_READ = <span class="hljs-number">0x0103</span>,         <span class="hljs-comment">// 读取内存</span><br><br>    <span class="hljs-comment">/* FES Commands */</span><br>    EFEX_CMD_FES_TRANS = <span class="hljs-number">0x0201</span>,        <span class="hljs-comment">// 传输</span><br>    EFEX_CMD_FES_RUN = <span class="hljs-number">0x0202</span>,          <span class="hljs-comment">// 运行</span><br>    EFEX_CMD_FES_INFO = <span class="hljs-number">0x0203</span>,         <span class="hljs-comment">// 信息查询</span><br>    EFEX_CMD_FES_DOWN = <span class="hljs-number">0x0206</span>,         <span class="hljs-comment">// 下载数据</span><br>    EFEX_CMD_FES_UP = <span class="hljs-number">0x0207</span>,           <span class="hljs-comment">// 上传数据</span><br>    EFEX_CMD_FES_VERIFY = <span class="hljs-number">0x0208</span>,       <span class="hljs-comment">// 验证</span><br>    EFEX_CMD_FES_QUERY_STORAGE = <span class="hljs-number">0x0209</span>, <span class="hljs-comment">// 查询存储</span><br>    EFEX_CMD_FES_FLASH_SET_ON = <span class="hljs-number">0x020A</span>,  <span class="hljs-comment">// 开启闪存</span><br>    EFEX_CMD_FES_FLASH_SET_OFF = <span class="hljs-number">0x020B</span>, <span class="hljs-comment">// 关闭闪存</span><br>    EFEX_CMD_FES_FLASH_SIZE_PROBE = <span class="hljs-number">0x020E</span>, <span class="hljs-comment">// 探测闪存大小</span><br>    EFEX_CMD_FES_TOOL_MODE = <span class="hljs-number">0x020F</span>,    <span class="hljs-comment">// 工具模式</span><br>    EFEX_CMD_FES_QUERY_SECURE = <span class="hljs-number">0x0230</span>, <span class="hljs-comment">// 查询安全状态</span><br>    EFEX_CMD_FES_GET_CHIPID = <span class="hljs-number">0x0232</span>    <span class="hljs-comment">// 获取芯片 ID</span><br>};<br></code></pre></td></tr></tbody></table></figure><table><thead><tr><th>命令</th><th>值</th><th>模式</th><th>说明</th></tr></thead><tbody><tr><td><code>EFEX_CMD_FEL_WRITE</code></td><td>0x0101</td><td>FEL</td><td>写入内存</td></tr><tr><td><code>EFEX_CMD_FEL_EXEC</code></td><td>0x0102</td><td>FEL</td><td>执行代码</td></tr><tr><td><code>EFEX_CMD_FEL_READ</code></td><td>0x0103</td><td>FEL</td><td>读取内存</td></tr><tr><td><code>EFEX_CMD_FES_DOWN</code></td><td>0x0206</td><td>FES</td><td>下载数据</td></tr><tr><td><code>EFEX_CMD_FES_UP</code></td><td>0x0207</td><td>FES</td><td>上传数据</td></tr><tr><td><code>EFEX_CMD_FES_QUERY_STORAGE</code></td><td>0x0209</td><td>FES</td><td>查询存储</td></tr></tbody></table><h2 id="USB-请求-响应流程" data-id="USB-请求-响应流程" class="notion-h"><a href="#USB-请求-响应流程" class="headerlink" title="USB 请求/响应流程"></a>USB 请求/响应流程</h2><pre class="mermaid">sequenceDiagram    participant Host as 主机    participant USB as USB 协议层    participant Device as Sunxi 设备    Note over Host,Device: 1. 构建请求包    Host-&gt;&gt;USB: 创建 sunxi_usb_request_t    USB-&gt;&gt;USB: 设置 magic = "AWUC"    USB-&gt;&gt;USB: 设置 cmd_package (EFEX 命令)    USB-&gt;&gt;USB: 设置 data_length    Note over Host,Device: 2. 发送请求    USB-&gt;&gt;Device: Bulk OUT Transfer (请求包)        Note over Host,Device: 3. 发送数据 (如果有)    USB-&gt;&gt;Device: Bulk OUT Transfer (数据)    Note over Host,Device: 4. 接收响应    Device--&gt;&gt;USB: Bulk IN Transfer (响应包)        Note over Host,Device: 5. 接收返回数据 (如果有)    Device--&gt;&gt;USB: Bulk IN Transfer (数据)    Note over Host,Device: 6. 解析响应    USB-&gt;&gt;USB: 验证 magic = "AWUS"    USB-&gt;&gt;USB: 检查 status    USB--&gt;&gt;Host: 返回结果</pre><h2 id="USB-写入实现" data-id="USB-写入实现" class="notion-h"><a href="#USB-写入实现" class="headerlink" title="USB 写入实现"></a>USB 写入实现</h2><p>源码位置: <code>src/efex-usb.c:55-75</code></p><figure class="highlight c"><table><tbody><tr><td class="code"><pre><code class="hljs c"><span class="hljs-type">int</span> <span class="hljs-title function_">sunxi_usb_write</span><span class="hljs-params">(<span class="hljs-type">const</span> <span class="hljs-keyword">struct</span> <span class="hljs-type">sunxi_efex_ctx_t</span> *ctx, </span><br><span class="hljs-params">                    <span class="hljs-type">const</span> <span class="hljs-type">void</span> *buf, <span class="hljs-type">const</span> <span class="hljs-type">size_t</span> len)</span> {<br>    <span class="hljs-keyword">if</span> (!ctx || !buf) {<br>        <span class="hljs-keyword">return</span> EFEX_ERR_NULL_PTR;<br>    }<br><br>    <span class="hljs-comment">// 1. 发送 USB 写入请求</span><br>    <span class="hljs-type">int</span> ret = sunxi_send_usb_request(ctx, AW_USB_WRITE, len);<br>    <span class="hljs-keyword">if</span> (ret != EFEX_ERR_SUCCESS) {<br>        <span class="hljs-keyword">return</span> ret;<br>    }<br><br>    <span class="hljs-comment">// 2. 发送实际数据</span><br>    ret = sunxi_usb_bulk_send(ctx-&gt;hdl, ctx-&gt;epout, buf, len);<br>    <span class="hljs-keyword">if</span> (ret != <span class="hljs-number">0</span>) {<br>        <span class="hljs-keyword">return</span> EFEX_ERR_USB_TRANSFER;<br>    }<br><br>    <span class="hljs-comment">// 3. 读取响应状态</span><br>    ret = sunxi_read_usb_response(ctx);<br>    <span class="hljs-keyword">if</span> (ret != <span class="hljs-number">0</span>) {<br>        <span class="hljs-keyword">return</span> EFEX_ERR_PROTOCOL;<br>    }<br>    <br>    <span class="hljs-keyword">return</span> EFEX_ERR_SUCCESS;<br>}<br></code></pre></td></tr></tbody></table></figure><h2 id="USB-读取实现" data-id="USB-读取实现" class="notion-h"><a href="#USB-读取实现" class="headerlink" title="USB 读取实现"></a>USB 读取实现</h2><p>源码位置: <code>src/efex-usb.c:77-97</code></p><figure class="highlight c"><table><tbody><tr><td class="code"><pre><code class="hljs c"><span class="hljs-type">int</span> <span class="hljs-title function_">sunxi_usb_read</span><span class="hljs-params">(<span class="hljs-type">const</span> <span class="hljs-keyword">struct</span> <span class="hljs-type">sunxi_efex_ctx_t</span> *ctx, </span><br><span class="hljs-params">                   <span class="hljs-type">const</span> <span class="hljs-type">void</span> *data, <span class="hljs-type">const</span> <span class="hljs-type">size_t</span> len)</span> {<br>    <span class="hljs-keyword">if</span> (!ctx || !data) {<br>        <span class="hljs-keyword">return</span> EFEX_ERR_NULL_PTR;<br>    }<br><br>    <span class="hljs-comment">// 1. 发送 USB 读取请求</span><br>    <span class="hljs-type">int</span> ret = sunxi_send_usb_request(ctx, AW_USB_READ, len);<br>    <span class="hljs-keyword">if</span> (ret != EFEX_ERR_SUCCESS) {<br>        <span class="hljs-keyword">return</span> ret;<br>    }<br><br>    <span class="hljs-comment">// 2. 接收数据</span><br>    ret = sunxi_usb_bulk_recv(ctx-&gt;hdl, ctx-&gt;epin, (<span class="hljs-type">char</span> *) data, len);<br>    <span class="hljs-keyword">if</span> (ret != <span class="hljs-number">0</span>) {<br>        <span class="hljs-keyword">return</span> EFEX_ERR_USB_TRANSFER;<br>    }<br><br>    <span class="hljs-comment">// 3. 读取响应状态</span><br>    ret = sunxi_read_usb_response(ctx);<br>    <span class="hljs-keyword">if</span> (ret != <span class="hljs-number">0</span>) {<br>        <span class="hljs-keyword">return</span> EFEX_ERR_PROTOCOL;<br>    }<br>    <br>    <span class="hljs-keyword">return</span> EFEX_ERR_SUCCESS;<br>}<br></code></pre></td></tr></tbody></table></figure><h2 id="发送-USB-请求实现" data-id="发送-USB-请求实现" class="notion-h"><a href="#发送-USB-请求实现" class="headerlink" title="发送 USB 请求实现"></a>发送 USB 请求实现</h2><p>源码位置: <code>src/efex-usb.c:14-34</code></p><figure class="highlight c"><table><tbody><tr><td class="code"><pre><code class="hljs c"><span class="hljs-type">int</span> <span class="hljs-title function_">sunxi_send_usb_request</span><span class="hljs-params">(<span class="hljs-type">const</span> <span class="hljs-keyword">struct</span> <span class="hljs-type">sunxi_efex_ctx_t</span> *ctx,</span><br><span class="hljs-params">                           <span class="hljs-type">const</span> <span class="hljs-keyword">enum</span> <span class="hljs-type">sunxi_efex_usb_request_t</span> type,</span><br><span class="hljs-params">                           <span class="hljs-type">const</span> <span class="hljs-type">size_t</span> length)</span> {<br>    <span class="hljs-keyword">if</span> (!ctx || !ctx-&gt;hdl) {<br>        <span class="hljs-keyword">return</span> EFEX_ERR_NULL_PTR;<br>    }<br><br>    <span class="hljs-comment">// 构建请求结构</span><br>    <span class="hljs-class"><span class="hljs-keyword">struct</span> <span class="hljs-title">sunxi_usb_request_t</span> <span class="hljs-title">req</span> =</span> {<br>        .magics = SUNXI_USB_REQ_MAGIC_INT,  <span class="hljs-comment">// "AWUC" 魔数</span><br>        .tab = <span class="hljs-number">0x0</span>,<br>        .data_length = cpu_to_le32(length),<br>        .cmd_length = SUNXI_EFEX_CMD_LEN,<br>        .cmd_package[<span class="hljs-number">0</span>] = type,<br>    };<br>    req.cmd_length = (<span class="hljs-type">uint8_t</span>) req.data_length;<br><br>    <span class="hljs-comment">// 发送请求包</span><br>    <span class="hljs-type">const</span> <span class="hljs-type">int</span> ret = sunxi_usb_bulk_send(ctx-&gt;hdl, ctx-&gt;epout,<br>                                        (<span class="hljs-type">const</span> <span class="hljs-type">char</span> *) &amp;req, <br>                                        <span class="hljs-keyword">sizeof</span>(<span class="hljs-keyword">struct</span> <span class="hljs-type">sunxi_usb_request_t</span>));<br>    <span class="hljs-keyword">if</span> (ret != <span class="hljs-number">0</span>) {<br>        <span class="hljs-keyword">return</span> EFEX_ERR_USB_TRANSFER;<br>    }<br>    <br>    <span class="hljs-keyword">return</span> EFEX_ERR_SUCCESS;<br>}<br></code></pre></td></tr></tbody></table></figure><h2 id="读取-USB-响应实现" data-id="读取-USB-响应实现" class="notion-h"><a href="#读取-USB-响应实现" class="headerlink" title="读取 USB 响应实现"></a>读取 USB 响应实现</h2><p>源码位置: <code>src/efex-usb.c:36-53</code></p><figure class="highlight c"><table><tbody><tr><td class="code"><pre><code class="hljs c"><span class="hljs-type">int</span> <span class="hljs-title function_">sunxi_read_usb_response</span><span class="hljs-params">(<span class="hljs-type">const</span> <span class="hljs-keyword">struct</span> <span class="hljs-type">sunxi_efex_ctx_t</span> *ctx)</span> {<br>    <span class="hljs-keyword">if</span> (!ctx || !ctx-&gt;hdl) {<br>        <span class="hljs-keyword">return</span> EFEX_ERR_NULL_PTR;<br>    }<br><br>    <span class="hljs-class"><span class="hljs-keyword">struct</span> <span class="hljs-title">sunxi_usb_response_t</span> <span class="hljs-title">resp</span> =</span> {<span class="hljs-number">0</span>};<br><br>    <span class="hljs-comment">// 接收响应包</span><br>    <span class="hljs-type">const</span> <span class="hljs-type">int</span> ret = sunxi_usb_bulk_recv(ctx-&gt;hdl, ctx-&gt;epin,<br>                                        (<span class="hljs-type">char</span> *) &amp;resp, <span class="hljs-keyword">sizeof</span>(resp));<br>    <span class="hljs-keyword">if</span> (ret != <span class="hljs-number">0</span>) {<br>        <span class="hljs-keyword">return</span> EFEX_ERR_USB_TRANSFER;<br>    }<br><br>    <span class="hljs-comment">// 验证响应魔数</span><br>    <span class="hljs-keyword">if</span> (<span class="hljs-built_in">strncmp</span>(resp.magic, SUNXI_USB_RSP_MAGIC, <span class="hljs-number">4</span>) != <span class="hljs-number">0</span>) {<br>        <span class="hljs-keyword">return</span> EFEX_ERR_INVALID_RESPONSE;<br>    }<br><br>    <span class="hljs-keyword">return</span> resp.status;<br>}<br></code></pre></td></tr></tbody></table></figure><h2 id="FES-传输函数实现" data-id="FES-传输函数实现" class="notion-h"><a href="#FES-传输函数实现" class="headerlink" title="FES 传输函数实现"></a>FES 传输函数实现</h2><p>源码位置: <code>src/efex-usb.c:99-149</code></p><figure class="highlight c"><table><tbody><tr><td class="code"><pre><code class="hljs c"><span class="hljs-type">int</span> <span class="hljs-title function_">sunxi_usb_fes_xfer</span><span class="hljs-params">(<span class="hljs-type">const</span> <span class="hljs-keyword">struct</span> <span class="hljs-type">sunxi_efex_ctx_t</span> *ctx,</span><br><span class="hljs-params">                       <span class="hljs-type">const</span> <span class="hljs-keyword">enum</span> <span class="hljs-type">sunxi_usb_fes_xfer_type_t</span> type,</span><br><span class="hljs-params">                       <span class="hljs-type">const</span> <span class="hljs-type">uint32_t</span> cmd, <span class="hljs-type">const</span> <span class="hljs-type">char</span> *request_buf,</span><br><span class="hljs-params">                       <span class="hljs-type">const</span> <span class="hljs-type">ssize_t</span> request_len, <span class="hljs-type">const</span> <span class="hljs-type">char</span> *buf,</span><br><span class="hljs-params">                       <span class="hljs-type">const</span> <span class="hljs-type">ssize_t</span> len)</span> {<br>    <span class="hljs-keyword">if</span> (!ctx) {<br>        <span class="hljs-keyword">return</span> EFEX_ERR_NULL_PTR;<br>    }<br><br>    <span class="hljs-comment">// 检查设备模式必须是 FES/SRV</span><br>    <span class="hljs-keyword">if</span> (ctx-&gt;resp.mode != DEVICE_MODE_SRV) {<br>        <span class="hljs-keyword">return</span> EFEX_ERR_INVALID_PARAM;<br>    }<br><br>    <span class="hljs-comment">// 构建 FES 传输结构</span><br>    <span class="hljs-class"><span class="hljs-keyword">struct</span> <span class="hljs-title">sunxi_fes_xfer_t</span> <span class="hljs-title">fes_xfer</span> =</span> {<br>        .cmd = cpu_to_le16((<span class="hljs-type">uint16_t</span>) cmd),<br>        .tag = <span class="hljs-number">0x0</span>,<br>        .magics = SUNXI_USB_REQ_MAGIC_INT,<br>    };<br><br>    <span class="hljs-comment">// 复制请求缓冲区数据</span><br>    <span class="hljs-keyword">if</span> (request_len &gt; <span class="hljs-number">0</span> &amp;&amp; (<span class="hljs-type">size_t</span>)request_len &lt;= <span class="hljs-keyword">sizeof</span>(fes_xfer.buf)) {<br>        <span class="hljs-built_in">memcpy</span>(fes_xfer.buf, request_buf, request_len);<br>    }<br><br>    <span class="hljs-comment">// 发送 FES 传输请求</span><br>    <span class="hljs-type">int</span> ret = sunxi_usb_bulk_send(ctx-&gt;hdl, ctx-&gt;epout,<br>                                  (<span class="hljs-type">const</span> <span class="hljs-type">char</span> *) &amp;fes_xfer, <span class="hljs-keyword">sizeof</span>(fes_xfer));<br>    <span class="hljs-keyword">if</span> (ret != <span class="hljs-number">0</span>) {<br>        <span class="hljs-keyword">return</span> EFEX_ERR_USB_TRANSFER;<br>    }<br><br>    <span class="hljs-comment">// 根据传输类型发送或接收数据</span><br>    <span class="hljs-keyword">if</span> (type == FES_XFER_SEND &amp;&amp; len &gt; <span class="hljs-number">0</span>) {<br>        <span class="hljs-keyword">if</span> (!buf) {<br>            <span class="hljs-keyword">return</span> EFEX_ERR_NULL_PTR;<br>        }<br>        ret = sunxi_usb_bulk_send(ctx-&gt;hdl, ctx-&gt;epout, buf, len);<br>        <span class="hljs-keyword">if</span> (ret != <span class="hljs-number">0</span>) {<br>            <span class="hljs-keyword">return</span> EFEX_ERR_USB_TRANSFER;<br>        }<br>    } <span class="hljs-keyword">else</span> <span class="hljs-keyword">if</span> (type == FES_XFER_RECV &amp;&amp; len &gt; <span class="hljs-number">0</span>) {<br>        <span class="hljs-keyword">if</span> (!buf) {<br>            <span class="hljs-keyword">return</span> EFEX_ERR_NULL_PTR;<br>        }<br>        ret = sunxi_usb_bulk_recv(ctx-&gt;hdl, ctx-&gt;epin, (<span class="hljs-type">char</span> *) buf, len);<br>        <span class="hljs-keyword">if</span> (ret != <span class="hljs-number">0</span>) {<br>            <span class="hljs-keyword">return</span> EFEX_ERR_USB_TRANSFER;<br>        }<br>    }<br><br>    <span class="hljs-comment">// 读取响应状态</span><br>    ret = sunxi_read_usb_response(ctx);<br>    <span class="hljs-keyword">if</span> (ret != <span class="hljs-number">0</span>) {<br>        <span class="hljs-keyword">return</span> EFEX_ERR_PROTOCOL;<br>    }<br><br>    <span class="hljs-keyword">return</span> EFEX_ERR_SUCCESS;<br>}<br></code></pre></td></tr></tbody></table></figure><h2 id="最大传输大小限制" data-id="最大传输大小限制" class="notion-h"><a href="#最大传输大小限制" class="headerlink" title="最大传输大小限制"></a>最大传输大小限制</h2><p>源码位置: <code>includes/efex-protocol.h:227</code></p><figure class="highlight c"><table><tbody><tr><td class="code"><pre><code class="hljs c"><span class="hljs-meta">#<span class="hljs-keyword">define</span> EFEX_CODE_MAX_SIZE (64 * 1024)  <span class="hljs-comment">// 64KB 单次最大传输</span></span><br></code></pre></td></tr></tbody></table></figure><h2 id="USB-后端抽象层" data-id="USB-后端抽象层" class="notion-h"><a href="#USB-后端抽象层" class="headerlink" title="USB 后端抽象层"></a>USB 后端抽象层</h2><pre class="mermaid">graph TB    subgraph Interface["USB 后端接口"]        I1[usb_layer.c]    end    subgraph Libusb["libusb 后端"]        L1[usb_layer_libusb.c]        L2[libusb_bulk_transfer]    end    subgraph WinUSB["WinUSB 后端"]        W1[usb_layer_winusb.c]        W2[WinUsb_WritePipe/ReadPipe]    end    I1 --&gt; L1 --&gt; L2    I1 --&gt; W1 --&gt; W2</pre>]]>
    </content>
    <id>https://gloomyghost.com/posts/libefex-usb-protocol/</id>
    <link href="https://gloomyghost.com/posts/libefex-usb-protocol/"/>
    <published>2026-04-29T16:00:00.000Z</published>
    <summary>
      <![CDATA[<h1 id="libefex-USB-协议层" data-id="libefex-USB-协议层" class="notion-h"><a href="#libefex-USB-协议层" class="headerlink" title="libefex USB 协议层"></]]>
    </summary>
    <title>libefex USB 协议层详解：数据包结构与通信流程</title>
    <updated>2026-05-29T20:33:31.220Z</updated>
  </entry>
  <entry>
    <author>
      <name>柚木 鉉</name>
    </author>
    <category term="Rust" scheme="https://gloomyghost.com/tags/Rust/"/>
    <category term="嵌入式" scheme="https://gloomyghost.com/tags/%E5%B5%8C%E5%85%A5%E5%BC%8F/"/>
    <category term="OpenixCLI" scheme="https://gloomyghost.com/tags/OpenixCLI/"/>
    <category term="CLI" scheme="https://gloomyghost.com/tags/CLI/"/>
    <category term="clap" scheme="https://gloomyghost.com/tags/clap/"/>
    <content>
      <![CDATA[<h2 id="概述" data-id="概述" class="notion-h"><a href="#概述" class="headerlink" title="概述"></a>概述</h2><p>命令行接口（CLI）是嵌入式工具与用户交互的第一道关卡。OpenixCLI 使用 Rust 最流行的 CLI 解析库 <code>clap</code>（Command Line Argument Parser），通过 derive 宏模式实现声明式的命令定义，让开发者专注于业务逻辑而非参数解析细节。</p><h3 id="模块结构" data-id="模块结构" class="notion-h"><a href="#模块结构" class="headerlink" title="模块结构"></a>模块结构</h3><figure class="highlight plaintext"><table><tbody><tr><td class="code"><pre><code class="hljs plaintext">src/<br>├── cli.rs           # CLI 定义（derive 宏）<br>├── main.rs          # 入口点和命令路由<br>└── commands/<br>    └── types.rs     # FlashArgs 和 FlashMode 类型<br></code></pre></td></tr></tbody></table></figure><h3 id="核心导出" data-id="核心导出" class="notion-h"><a href="#核心导出" class="headerlink" title="核心导出"></a>核心导出</h3><figure class="highlight rust"><table><tbody><tr><td class="code"><pre><code class="hljs rust"><span class="hljs-keyword">use</span> cli::{Cli, Commands};<br><span class="hljs-keyword">use</span> commands::FlashArgs;<br></code></pre></td></tr></tbody></table></figure><hr><h2 id="架构与依赖" data-id="架构与依赖" class="notion-h"><a href="#架构与依赖" class="headerlink" title="架构与依赖"></a>架构与依赖</h2><h3 id="clap-Derive-模式" data-id="clap-Derive-模式" class="notion-h"><a href="#clap-Derive-模式" class="headerlink" title="clap Derive 模式"></a>clap Derive 模式</h3><p>clap 提供三种 API 风格：</p><table><thead><tr><th>风格</th><th>特点</th><th>适用场景</th></tr></thead><tbody><tr><td>Derive</td><td>声明式，通过属性宏定义</td><td>类型安全，推荐使用</td></tr><tr><td>Builder</td><td>构造器模式，链式调用</td><td>动态配置，更灵活</td></tr><tr><td>YAML</td><td>配置文件定义</td><td>多语言支持，较旧</td></tr></tbody></table><p>OpenixCLI 使用 <strong>Derive 模式</strong>，优势在于：</p><ol><li><strong>类型安全</strong> - 参数直接映射到结构体字段</li><li><strong>自动生成帮助文档</strong> - 从 doc comments 生成 help 信息</li><li><strong>编译时检查</strong> - 错误在编译期暴露</li></ol><h3 id="命令树结构" data-id="命令树结构" class="notion-h"><a href="#命令树结构" class="headerlink" title="命令树结构"></a>命令树结构</h3><figure class="highlight plaintext"><table><tbody><tr><td class="code"><pre><code class="hljs plaintext">openixcli (根命令)<br>├── --verbose, -v    [全局参数]<br>├── --version        [版本信息]<br>├── scan             [子命令]<br>│   └── --detailed, -l<br>├── flash            [子命令]<br>│   ├── firmware     [必需参数]<br>│   ├── --bus, -b    [可选参数]<br>│   ├── --port, -P   [可选参数]<br>│   ├── --verify, -V [默认 true]<br>│   ├── --mode, -m   [默认 full_erase]<br>│   ├── --partitions, -p [可选]<br>│   └── --post-action, -a [默认 reboot]<br>└── tui              [子命令，无参数]<br></code></pre></td></tr></tbody></table></figure><hr><h2 id="核心数据结构" data-id="核心数据结构" class="notion-h"><a href="#核心数据结构" class="headerlink" title="核心数据结构"></a>核心数据结构</h2><h3 id="Cli-主结构" data-id="Cli-主结构" class="notion-h"><a href="#Cli-主结构" class="headerlink" title="Cli 主结构"></a>Cli 主结构</h3><figure class="highlight rust"><table><tbody><tr><td class="code"><pre><code class="hljs rust"><span class="hljs-comment">/// CLI 主结构 - 使用 clap Parser derive 宏</span><br><span class="hljs-meta">#[derive(Parser)]</span><br><span class="hljs-meta">#[command(name = <span class="hljs-string">"openixcli"</span>)]</span><br><span class="hljs-meta">#[command(about = <span class="hljs-string">"Firmware flashing CLI tool for Allwinner chips"</span>, long_about = None)]</span><br><span class="hljs-meta">#[command(version)]</span><br><span class="hljs-keyword">pub</span> <span class="hljs-keyword">struct</span> <span class="hljs-title class_">Cli</span> {<br>    <span class="hljs-comment">/// 要执行的子命令（默认为 TUI）</span><br>    <span class="hljs-meta">#[command(subcommand)]</span><br>    <span class="hljs-keyword">pub</span> command: <span class="hljs-type">Option</span>&lt;Commands&gt;,<br><br>    <span class="hljs-comment">/// 启用详细输出</span><br>    <span class="hljs-meta">#[arg(short, long, global = true, help = <span class="hljs-string">"Enable verbose output"</span>)]</span><br>    <span class="hljs-keyword">pub</span> verbose: <span class="hljs-type">bool</span>,<br>}<br></code></pre></td></tr></tbody></table></figure><p><strong>关键属性解析：</strong></p><table><thead><tr><th>属性</th><th>作用</th></tr></thead><tbody><tr><td><code>#[derive(Parser)]</code></td><td>实现 <code>clap::Parser</code> trait</td></tr><tr><td><code>#[command(name = "...")]</code></td><td>设置命令名称</td></tr><tr><td><code>#[command(about = "...")]</code></td><td>设置简短描述</td></tr><tr><td><code>#[command(version)]</code></td><td>自动从 Cargo.toml 读取版本</td></tr><tr><td><code>#[command(subcommand)]</code></td><td>声明子命令字段</td></tr><tr><td><code>#[arg(global = true)]</code></td><td>参数对所有子命令生效</td></tr></tbody></table><h3 id="Commands-子命令枚举" data-id="Commands-子命令枚举" class="notion-h"><a href="#Commands-子命令枚举" class="headerlink" title="Commands 子命令枚举"></a>Commands 子命令枚举</h3><figure class="highlight rust"><table><tbody><tr><td class="code"><pre><code class="hljs rust"><span class="hljs-comment">/// 可用的 CLI 命令</span><br><span class="hljs-meta">#[derive(Subcommand)]</span><br><span class="hljs-keyword">pub</span> <span class="hljs-keyword">enum</span> <span class="hljs-title class_">Commands</span> {<br>    <span class="hljs-comment">/// 扫描连接的设备</span><br>    Scan {<br>        <span class="hljs-comment">/// 获取详细设备信息（需要初始化设备）</span><br>        <span class="hljs-meta">#[arg(short = 'l', long, help = <span class="hljs-string">"Get detailed device information"</span>)]</span><br>        detailed: <span class="hljs-type">bool</span>,<br>    },<br><br>    <span class="hljs-comment">/// 刷写固件到设备</span><br>    Flash {<br>        <span class="hljs-comment">/// 固件文件路径</span><br>        <span class="hljs-meta">#[arg(help = <span class="hljs-string">"Path to firmware file"</span>)]</span><br>        firmware: <span class="hljs-type">String</span>,<br><br>        <span class="hljs-comment">/// USB 总线号</span><br>        <span class="hljs-meta">#[arg(short, long, help = <span class="hljs-string">"USB bus number"</span>)]</span><br>        bus: <span class="hljs-type">Option</span>&lt;<span class="hljs-type">u8</span>&gt;,<br><br>        <span class="hljs-comment">/// USB 端口号</span><br>        <span class="hljs-meta">#[arg(short = 'P', long, help = <span class="hljs-string">"USB port number"</span>)]</span><br>        port: <span class="hljs-type">Option</span>&lt;<span class="hljs-type">u8</span>&gt;,<br><br>        <span class="hljs-comment">/// 启用写入后验证</span><br>        <span class="hljs-meta">#[arg(short = 'V', long, default_value = <span class="hljs-string">"true"</span>,</span><br><span class="hljs-meta">              help = <span class="hljs-string">"Enable verification after write"</span>)]</span><br>        verify: <span class="hljs-type">bool</span>,<br><br>        <span class="hljs-comment">/// 刷写模式</span><br>        <span class="hljs-comment">/// - partition: 仅刷写指定分区</span><br>        <span class="hljs-comment">/// - keep_data: 保留现有数据</span><br>        <span class="hljs-comment">/// - partition_erase: 刷写前擦除分区</span><br>        <span class="hljs-comment">/// - full_erase: 刷写前完全擦除</span><br>        <span class="hljs-meta">#[arg(short, long, default_value = <span class="hljs-string">"full_erase"</span>,</span><br><span class="hljs-meta">              help = <span class="hljs-string">"Flash mode: partition, keep_data, partition_erase, full_erase"</span>)]</span><br>        mode: <span class="hljs-type">String</span>,<br><br>        <span class="hljs-comment">/// 要刷写的分区（逗号分隔）</span><br>        <span class="hljs-meta">#[arg(short = 'p', long,</span><br><span class="hljs-meta">              help = <span class="hljs-string">"Partitions to flash (comma-separated)"</span>)]</span><br>        partitions: <span class="hljs-type">Option</span>&lt;<span class="hljs-type">String</span>&gt;,<br><br>        <span class="hljs-comment">/// 刷写后操作</span><br>        <span class="hljs-comment">/// - reboot: 重启设备</span><br>        <span class="hljs-comment">/// - poweroff: 关闭设备电源</span><br>        <span class="hljs-comment">/// - shutdown: 关闭设备</span><br>        <span class="hljs-meta">#[arg(short = 'a', long, default_value = <span class="hljs-string">"reboot"</span>,</span><br><span class="hljs-meta">              help = <span class="hljs-string">"Post-flash action: reboot, poweroff, shutdown"</span>)]</span><br>        post_action: <span class="hljs-type">String</span>,<br>    },<br><br>    <span class="hljs-comment">/// 启动交互式 TUI 模式</span><br>    Tui,<br>}<br></code></pre></td></tr></tbody></table></figure><p><strong>参数属性详解：</strong></p><table><thead><tr><th>属性</th><th>示例</th><th>作用</th></tr></thead><tbody><tr><td><code>short</code></td><td><code>short = 'l'</code></td><td>短选项 <code>-l</code></td></tr><tr><td><code>long</code></td><td><code>long</code></td><td>长选项 <code>--detailed</code></td></tr><tr><td><code>help</code></td><td><code>help = "..."</code></td><td>帮助文档文本</td></tr><tr><td><code>default_value</code></td><td><code>default_value = "true"</code></td><td>默认值</td></tr><tr><td><code>short = 'P'</code></td><td>自定义短选项字符</td><td>避免与 <code>-p</code> (partitions) 冲突</td></tr></tbody></table><h3 id="FlashArgs-参数结构" data-id="FlashArgs-参数结构" class="notion-h"><a href="#FlashArgs-参数结构" class="headerlink" title="FlashArgs 参数结构"></a>FlashArgs 参数结构</h3><p>从 CLI 参数转换到内部使用的参数结构：</p><figure class="highlight rust"><table><tbody><tr><td class="code"><pre><code class="hljs rust"><span class="hljs-comment">/// Flash 命令参数</span><br><span class="hljs-comment">///</span><br><span class="hljs-comment">/// 将 CLI 解析结果转换为内部模块可用的结构化数据</span><br><span class="hljs-keyword">pub</span> <span class="hljs-keyword">struct</span> <span class="hljs-title class_">FlashArgs</span> {<br>    <span class="hljs-keyword">pub</span> firmware_path: PathBuf,      <span class="hljs-comment">// 固件路径</span><br>    <span class="hljs-keyword">pub</span> bus: <span class="hljs-type">Option</span>&lt;<span class="hljs-type">u8</span>&gt;,             <span class="hljs-comment">// USB 总线号</span><br>    <span class="hljs-keyword">pub</span> port: <span class="hljs-type">Option</span>&lt;<span class="hljs-type">u8</span>&gt;,            <span class="hljs-comment">// USB 端口号</span><br>    <span class="hljs-keyword">pub</span> verify: <span class="hljs-type">bool</span>,                <span class="hljs-comment">// 验证开关</span><br>    <span class="hljs-keyword">pub</span> mode: FlashMode,             <span class="hljs-comment">// 刷写模式（枚举）</span><br>    <span class="hljs-keyword">pub</span> partitions: <span class="hljs-type">Option</span>&lt;<span class="hljs-type">Vec</span>&lt;<span class="hljs-type">String</span>&gt;&gt;, <span class="hljs-comment">// 分区列表</span><br>    <span class="hljs-keyword">pub</span> post_action: <span class="hljs-type">String</span>,         <span class="hljs-comment">// 后续操作</span><br>    <span class="hljs-keyword">pub</span> verbose: <span class="hljs-type">bool</span>,               <span class="hljs-comment">// 详细模式</span><br>}<br></code></pre></td></tr></tbody></table></figure><h3 id="FlashMode-枚举" data-id="FlashMode-枚举" class="notion-h"><a href="#FlashMode-枚举" class="headerlink" title="FlashMode 枚举"></a>FlashMode 枚举</h3><figure class="highlight rust"><table><tbody><tr><td class="code"><pre><code class="hljs rust"><span class="hljs-comment">/// 刷写模式选项</span><br><span class="hljs-meta">#[derive(Debug, Clone, Copy, PartialEq, Eq)]</span><br><span class="hljs-keyword">pub</span> <span class="hljs-keyword">enum</span> <span class="hljs-title class_">FlashMode</span> {<br>    Partition,       <span class="hljs-comment">// 仅刷写指定分区</span><br>    KeepData,        <span class="hljs-comment">// 保留现有数据</span><br>    PartitionErase,  <span class="hljs-comment">// 刷写前擦除分区</span><br>    FullErase,       <span class="hljs-comment">// 刷写前完全擦除</span><br>}<br><br><span class="hljs-comment">/// 实现 FromStr trait - 支持字符串解析</span><br><span class="hljs-keyword">impl</span> <span class="hljs-title class_">FromStr</span> <span class="hljs-keyword">for</span> <span class="hljs-title class_">FlashMode</span> {<br>    <span class="hljs-keyword">type</span> <span class="hljs-title class_">Err</span> = <span class="hljs-type">String</span>;<br><br>    <span class="hljs-keyword">fn</span> <span class="hljs-title function_">from_str</span>(s: &amp;<span class="hljs-type">str</span>) <span class="hljs-punctuation">-&gt;</span> <span class="hljs-type">Result</span>&lt;<span class="hljs-keyword">Self</span>, <span class="hljs-keyword">Self</span>::<span class="hljs-literal">Err</span>&gt; {<br>        <span class="hljs-keyword">match</span> s {<br>            <span class="hljs-string">"partition"</span> =&gt; <span class="hljs-title function_ invoke__">Ok</span>(<span class="hljs-keyword">Self</span>::Partition),<br>            <span class="hljs-string">"keep_data"</span> =&gt; <span class="hljs-title function_ invoke__">Ok</span>(<span class="hljs-keyword">Self</span>::KeepData),<br>            <span class="hljs-string">"partition_erase"</span> =&gt; <span class="hljs-title function_ invoke__">Ok</span>(<span class="hljs-keyword">Self</span>::PartitionErase),<br>            <span class="hljs-string">"full_erase"</span> =&gt; <span class="hljs-title function_ invoke__">Ok</span>(<span class="hljs-keyword">Self</span>::FullErase),<br>            _ =&gt; <span class="hljs-title function_ invoke__">Err</span>(<span class="hljs-built_in">format!</span>(<span class="hljs-string">"Invalid flash mode: {}"</span>, s)),<br>        }<br>    }<br>}<br><br><span class="hljs-comment">/// 实现 Display trait - 支持字符串输出</span><br><span class="hljs-keyword">impl</span> <span class="hljs-title class_">std</span>::fmt::Display <span class="hljs-keyword">for</span> <span class="hljs-title class_">FlashMode</span> {<br>    <span class="hljs-keyword">fn</span> <span class="hljs-title function_">fmt</span>(&amp;<span class="hljs-keyword">self</span>, f: &amp;<span class="hljs-keyword">mut</span> std::fmt::Formatter&lt;<span class="hljs-symbol">'_</span>&gt;) <span class="hljs-punctuation">-&gt;</span> std::fmt::<span class="hljs-type">Result</span> {<br>        <span class="hljs-keyword">match</span> <span class="hljs-keyword">self</span> {<br>            FlashMode::Partition =&gt; <span class="hljs-built_in">write!</span>(f, <span class="hljs-string">"partition"</span>),<br>            FlashMode::KeepData =&gt; <span class="hljs-built_in">write!</span>(f, <span class="hljs-string">"keep_data"</span>),<br>            FlashMode::PartitionErase =&gt; <span class="hljs-built_in">write!</span>(f, <span class="hljs-string">"partition_erase"</span>),<br>            FlashMode::FullErase =&gt; <span class="hljs-built_in">write!</span>(f, <span class="hljs-string">"full_erase"</span>),<br>        }<br>    }<br>}<br></code></pre></td></tr></tbody></table></figure><hr><h2 id="调用流程分析" data-id="调用流程分析" class="notion-h"><a href="#调用流程分析" class="headerlink" title="调用流程分析"></a>调用流程分析</h2><h3 id="程序入口与命令路由" data-id="程序入口与命令路由" class="notion-h"><a href="#程序入口与命令路由" class="headerlink" title="程序入口与命令路由"></a>程序入口与命令路由</h3><pre class="mermaid">sequenceDiagram    participant User as 用户    participant main as main.rs    participant clap as clap::Parser    participant cmd as Commands    User-&gt;&gt;main: openixcli flash firmware.fex    main-&gt;&gt;clap: Cli::parse()    clap-&gt;&gt;cmd: 解析参数    cmd--&gt;&gt;main: Some(Commands::Flash{...})    main-&gt;&gt;main: setup_logging(verbose)    main-&gt;&gt;main: FlashMode::from_str(&amp;mode)    main-&gt;&gt;main: 构建 FlashArgs    main-&gt;&gt;commands: flash::execute(args)</pre><h3 id="main-rs-完整实现" data-id="main-rs-完整实现" class="notion-h"><a href="#main-rs-完整实现" class="headerlink" title="main.rs 完整实现"></a>main.rs 完整实现</h3><figure class="highlight rust"><table><tbody><tr><td class="code"><pre><code class="hljs rust"><span class="hljs-keyword">use</span> clap::Parser;<br><span class="hljs-keyword">use</span> std::<span class="hljs-type">str</span>::FromStr;<br><br><span class="hljs-keyword">mod</span> cli;<br><span class="hljs-keyword">mod</span> commands;<br><br><span class="hljs-keyword">use</span> cli::{Cli, Commands};<br><span class="hljs-keyword">use</span> commands::FlashArgs;<br><span class="hljs-keyword">use</span> utils::TermLogger;<br><br><span class="hljs-comment">/// 初始化日志系统</span><br><span class="hljs-keyword">fn</span> <span class="hljs-title function_">setup_logging</span>(verbose: <span class="hljs-type">bool</span>) {<br>    <span class="hljs-keyword">if</span> <span class="hljs-keyword">let</span> <span class="hljs-variable">Err</span>(e) = TermLogger::<span class="hljs-title function_ invoke__">init</span>(verbose) {<br>        <span class="hljs-built_in">eprintln!</span>(<span class="hljs-string">"Failed to initialize logger: {}"</span>, e);<br>    }<br>}<br><br><span class="hljs-meta">#[tokio::main]</span><br><span class="hljs-keyword">async</span> <span class="hljs-keyword">fn</span> <span class="hljs-title function_">main</span>() <span class="hljs-punctuation">-&gt;</span> anyhow::<span class="hljs-type">Result</span>&lt;()&gt; {<br>    <span class="hljs-comment">// 1. 解析命令行参数</span><br>    <span class="hljs-keyword">let</span> <span class="hljs-variable">cli</span> = Cli::<span class="hljs-title function_ invoke__">parse</span>();<br><br>    <span class="hljs-comment">// 2. 命令路由</span><br>    <span class="hljs-keyword">match</span> cli.command {<br>        <span class="hljs-comment">// 无子命令或 TUI -&gt; 启动交互界面</span><br>        <span class="hljs-literal">None</span> | <span class="hljs-title function_ invoke__">Some</span>(Commands::Tui) =&gt; {<br>            tui::<span class="hljs-title function_ invoke__">run</span>().<span class="hljs-keyword">await</span>?;<br>        }<br><br>        <span class="hljs-comment">// Scan 命令 -&gt; 扫描 USB 设备</span><br>        <span class="hljs-title function_ invoke__">Some</span>(Commands::Scan { detailed }) =&gt; {<br>            <span class="hljs-title function_ invoke__">setup_logging</span>(cli.verbose);<br>            commands::scan::<span class="hljs-title function_ invoke__">execute</span>(detailed).<span class="hljs-keyword">await</span>?;<br>        }<br><br>        <span class="hljs-comment">// Flash 命令 -&gt; 执行固件刷写</span><br>        <span class="hljs-title function_ invoke__">Some</span>(Commands::Flash {<br>            firmware,<br>            bus,<br>            port,<br>            verify,<br>            mode,<br>            partitions,<br>            post_action,<br>        }) =&gt; {<br>            <span class="hljs-title function_ invoke__">setup_logging</span>(cli.verbose);<br><br>            <span class="hljs-comment">// 3. 解析刷写模式</span><br>            <span class="hljs-keyword">let</span> <span class="hljs-variable">flash_mode</span> = FlashMode::<span class="hljs-title function_ invoke__">from_str</span>(&amp;mode)<br>                .<span class="hljs-title function_ invoke__">map_err</span>(|e| anyhow::anyhow!(<span class="hljs-string">"{}"</span>, e))?;<br><br>            <span class="hljs-comment">// 4. 解析分区列表（逗号分隔）</span><br>            <span class="hljs-keyword">let</span> <span class="hljs-variable">partition_list</span> = partitions<br>                .<span class="hljs-title function_ invoke__">map</span>(|s| s.<span class="hljs-title function_ invoke__">split</span>(<span class="hljs-string">','</span>)<br>                       .<span class="hljs-title function_ invoke__">map</span>(|p| p.<span class="hljs-title function_ invoke__">trim</span>().<span class="hljs-title function_ invoke__">to_string</span>())<br>                       .<span class="hljs-title function_ invoke__">collect</span>());<br><br>            <span class="hljs-comment">// 5. 构建 FlashArgs</span><br>            <span class="hljs-keyword">let</span> <span class="hljs-variable">args</span> = FlashArgs {<br>                firmware_path: firmware.<span class="hljs-title function_ invoke__">into</span>(),<br>                bus,<br>                port,<br>                verify,<br>                mode: flash_mode,<br>                partitions: partition_list,<br>                post_action,<br>                verbose: cli.verbose,<br>            };<br><br>            <span class="hljs-comment">// 6. 执行刷写</span><br>            commands::flash::<span class="hljs-title function_ invoke__">execute</span>(args).<span class="hljs-keyword">await</span>?;<br>        }<br>    }<br><br>    <span class="hljs-title function_ invoke__">Ok</span>(())<br>}<br></code></pre></td></tr></tbody></table></figure><h3 id="参数解析流程详解" data-id="参数解析流程详解" class="notion-h"><a href="#参数解析流程详解" class="headerlink" title="参数解析流程详解"></a>参数解析流程详解</h3><p><strong>Flash 命令参数转换：</strong></p><figure class="highlight rust"><table><tbody><tr><td class="code"><pre><code class="hljs rust"><span class="hljs-comment">// CLI 字符串参数 -&gt; 内部类型</span><br><br><span class="hljs-comment">// 1. 固件路径：String -&gt; PathBuf</span><br><span class="hljs-keyword">let</span> <span class="hljs-variable">firmware_path</span>: PathBuf = firmware.<span class="hljs-title function_ invoke__">into</span>();<br><br><span class="hljs-comment">// 2. 刷写模式：String -&gt; FlashMode (通过 FromStr)</span><br><span class="hljs-keyword">let</span> <span class="hljs-variable">mode</span>: FlashMode = FlashMode::<span class="hljs-title function_ invoke__">from_str</span>(<span class="hljs-string">"full_erase"</span>)?;<br><span class="hljs-comment">// 结果: FlashMode::FullErase</span><br><br><span class="hljs-comment">// 3. 分区列表：Option&lt;String&gt; -&gt; Option&lt;Vec&lt;String&gt;&gt;</span><br><span class="hljs-keyword">let</span> <span class="hljs-variable">partitions</span>: <span class="hljs-type">Option</span>&lt;<span class="hljs-type">Vec</span>&lt;<span class="hljs-type">String</span>&gt;&gt; = <span class="hljs-title function_ invoke__">Some</span>(<span class="hljs-string">"boot,rootfs,userdata"</span>)<br>    .<span class="hljs-title function_ invoke__">map</span>(|s| s.<span class="hljs-title function_ invoke__">split</span>(<span class="hljs-string">','</span>)<br>           .<span class="hljs-title function_ invoke__">map</span>(|p| p.<span class="hljs-title function_ invoke__">trim</span>().<span class="hljs-title function_ invoke__">to_string</span>())<br>           .<span class="hljs-title function_ invoke__">collect</span>());<br><span class="hljs-comment">// 结果: Some(["boot", "rootfs", "userdata"])</span><br></code></pre></td></tr></tbody></table></figure><hr><h2 id="代码亮点" data-id="代码亮点" class="notion-h"><a href="#代码亮点" class="headerlink" title="代码亮点"></a>代码亮点</h2><h3 id="1-全局参数穿透" data-id="1-全局参数穿透" class="notion-h"><a href="#1-全局参数穿透" class="headerlink" title="1. 全局参数穿透"></a>1. 全局参数穿透</h3><figure class="highlight rust"><table><tbody><tr><td class="code"><pre><code class="hljs rust"><span class="hljs-meta">#[arg(short, long, global = true, help = <span class="hljs-string">"Enable verbose output"</span>)]</span><br><span class="hljs-keyword">pub</span> verbose: <span class="hljs-type">bool</span>,<br></code></pre></td></tr></tbody></table></figure><p><code>global = true</code> 属性使 <code>--verbose</code> 对所有子命令生效：</p><figure class="highlight bash"><table><tbody><tr><td class="code"><pre><code class="hljs bash"><span class="hljs-comment"># 以下都有效：</span><br>openixcli --verbose scan<br>openixcli scan --verbose<br>openixcli --verbose flash firmware.fex<br>openixcli flash firmware.fex --verbose<br></code></pre></td></tr></tbody></table></figure><h3 id="2-默认子命令（无参数启动-TUI）" data-id="2-默认子命令（无参数启动-TUI）" class="notion-h"><a href="#2-默认子命令（无参数启动-TUI）" class="headerlink" title="2. 默认子命令（无参数启动 TUI）"></a>2. 默认子命令（无参数启动 TUI）</h3><figure class="highlight rust"><table><tbody><tr><td class="code"><pre><code class="hljs rust"><span class="hljs-meta">#[command(subcommand)]</span><br><span class="hljs-keyword">pub</span> command: <span class="hljs-type">Option</span>&lt;Commands&gt;,<br></code></pre></td></tr></tbody></table></figure><p><code>Option&lt;Commands&gt;</code> 允许无子命令时的默认行为：</p><figure class="highlight rust"><table><tbody><tr><td class="code"><pre><code class="hljs rust"><span class="hljs-keyword">match</span> cli.command {<br>    <span class="hljs-literal">None</span> | <span class="hljs-title function_ invoke__">Some</span>(Commands::Tui) =&gt; {<br>        tui::<span class="hljs-title function_ invoke__">run</span>().<span class="hljs-keyword">await</span>?;  <span class="hljs-comment">// 无子命令默认启动 TUI</span><br>    }<br>    <span class="hljs-comment">// ...</span><br>}<br></code></pre></td></tr></tbody></table></figure><h3 id="3-短选项字符冲突解决" data-id="3-短选项字符冲突解决" class="notion-h"><a href="#3-短选项字符冲突解决" class="headerlink" title="3. 短选项字符冲突解决"></a>3. 短选项字符冲突解决</h3><figure class="highlight rust"><table><tbody><tr><td class="code"><pre><code class="hljs rust"><span class="hljs-meta">#[arg(short, long)]</span>          <span class="hljs-comment">// bus -&gt; -b, --bus</span><br><span class="hljs-meta">#[arg(short = 'P', long)]</span>    <span class="hljs-comment">// port -&gt; -P, --port (避免与 partitions 的 -p 冲突)</span><br><span class="hljs-meta">#[arg(short = 'p', long)]</span>    <span class="hljs-comment">// partitions -&gt; -p, --partitions</span><br></code></pre></td></tr></tbody></table></figure><h3 id="4-默认值设置" data-id="4-默认值设置" class="notion-h"><a href="#4-默认值设置" class="headerlink" title="4. 默认值设置"></a>4. 默认值设置</h3><figure class="highlight rust"><table><tbody><tr><td class="code"><pre><code class="hljs rust"><span class="hljs-meta">#[arg(default_value = <span class="hljs-string">"true"</span>)]</span>    <span class="hljs-comment">// verify 默认为 true</span><br><span class="hljs-meta">#[arg(default_value = <span class="hljs-string">"reboot"</span>)]</span>  <span class="hljs-comment">// post_action 默认为 reboot</span><br><span class="hljs-meta">#[arg(default_value = <span class="hljs-string">"full_erase"</span>)]</span>  <span class="hljs-comment">// mode 默认为 full_erase</span><br></code></pre></td></tr></tbody></table></figure><p>用户可以省略这些参数：</p><figure class="highlight bash"><table><tbody><tr><td class="code"><pre><code class="hljs bash"><span class="hljs-comment"># 等效于 openixcli flash firmware.fex --verify --mode full_erase --post-action reboot</span><br>openixcli flash firmware.fex<br></code></pre></td></tr></tbody></table></figure><h3 id="5-FromStr-trait-实现" data-id="5-FromStr-trait-实现" class="notion-h"><a href="#5-FromStr-trait-实现" class="headerlink" title="5. FromStr trait 实现"></a>5. FromStr trait 实现</h3><figure class="highlight rust"><table><tbody><tr><td class="code"><pre><code class="hljs rust"><span class="hljs-keyword">impl</span> <span class="hljs-title class_">FromStr</span> <span class="hljs-keyword">for</span> <span class="hljs-title class_">FlashMode</span> {<br>    <span class="hljs-keyword">type</span> <span class="hljs-title class_">Err</span> = <span class="hljs-type">String</span>;<br><br>    <span class="hljs-keyword">fn</span> <span class="hljs-title function_">from_str</span>(s: &amp;<span class="hljs-type">str</span>) <span class="hljs-punctuation">-&gt;</span> <span class="hljs-type">Result</span>&lt;<span class="hljs-keyword">Self</span>, <span class="hljs-keyword">Self</span>::<span class="hljs-literal">Err</span>&gt; {<br>        <span class="hljs-keyword">match</span> s.<span class="hljs-title function_ invoke__">trim</span>() {<br>            <span class="hljs-string">"partition"</span> =&gt; <span class="hljs-title function_ invoke__">Ok</span>(<span class="hljs-keyword">Self</span>::Partition),<br>            <span class="hljs-string">"keep_data"</span> =&gt; <span class="hljs-title function_ invoke__">Ok</span>(<span class="hljs-keyword">Self</span>::KeepData),<br>            <span class="hljs-string">"partition_erase"</span> =&gt; <span class="hljs-title function_ invoke__">Ok</span>(<span class="hljs-keyword">Self</span>::PartitionErase),<br>            <span class="hljs-string">"full_erase"</span> =&gt; <span class="hljs-title function_ invoke__">Ok</span>(<span class="hljs-keyword">Self</span>::FullErase),<br>            _ =&gt; <span class="hljs-title function_ invoke__">Err</span>(<span class="hljs-built_in">format!</span>(<span class="hljs-string">"Invalid flash mode: {}. Valid modes: partition, keep_data, partition_erase, full_erase"</span>, s)),<br>        }<br>    }<br>}<br></code></pre></td></tr></tbody></table></figure><p><strong>优势：</strong></p><ul><li>类型安全的字符串解析</li><li>与 <code>str::parse()</code> 方法配合</li><li>错误信息清晰完整</li></ul><h3 id="6-Doc-Comments-作为帮助文档" data-id="6-Doc-Comments-作为帮助文档" class="notion-h"><a href="#6-Doc-Comments-作为帮助文档" class="headerlink" title="6. Doc Comments 作为帮助文档"></a>6. Doc Comments 作为帮助文档</h3><figure class="highlight rust"><table><tbody><tr><td class="code"><pre><code class="hljs rust"><span class="hljs-comment">/// 扫描连接的设备</span><br>Scan {<br>    <span class="hljs-comment">/// 获取详细设备信息（需要初始化设备）</span><br>    <span class="hljs-meta">#[arg(short = 'l', long)]</span><br>    detailed: <span class="hljs-type">bool</span>,<br>},<br></code></pre></td></tr></tbody></table></figure><p>生成的帮助信息：</p><figure class="highlight plaintext"><table><tbody><tr><td class="code"><pre><code class="hljs plaintext">Commands:<br>  scan     扫描连接的设备<br>    -l, --detailed    获取详细设备信息（需要初始化设备）<br></code></pre></td></tr></tbody></table></figure><hr><h2 id="实践示例" data-id="实践示例" class="notion-h"><a href="#实践示例" class="headerlink" title="实践示例"></a>实践示例</h2><h3 id="常见命令用法" data-id="常见命令用法" class="notion-h"><a href="#常见命令用法" class="headerlink" title="常见命令用法"></a>常见命令用法</h3><figure class="highlight bash"><table><tbody><tr><td class="code"><pre><code class="hljs bash"><span class="hljs-comment"># 交互式 TUI（默认）</span><br>openixcli<br>openixcli tui<br><br><span class="hljs-comment"># 扫描设备</span><br>openixcli scan<br>openixcli scan --detailed<br><br><span class="hljs-comment"># 刷写固件</span><br>openixcli flash firmware.fex<br><br><span class="hljs-comment"># 指定 USB 设备</span><br>openixcli flash firmware.fex --bus 1 --port 2<br><br><span class="hljs-comment"># 仅刷写特定分区</span><br>openixcli flash firmware.fex --mode partition --partitions boot,rootfs<br><br><span class="hljs-comment"># 详细输出</span><br>openixcli --verbose flash firmware.fex<br><br><span class="hljs-comment"># 不验证写入</span><br>openixcli flash firmware.fex --verify <span class="hljs-literal">false</span><br><br><span class="hljs-comment"># 刷写后关闭设备</span><br>openixcli flash firmware.fex --post-action poweroff<br></code></pre></td></tr></tbody></table></figure><h3 id="完整刷写命令示例" data-id="完整刷写命令示例" class="notion-h"><a href="#完整刷写命令示例" class="headerlink" title="完整刷写命令示例"></a>完整刷写命令示例</h3><figure class="highlight bash"><table><tbody><tr><td class="code"><pre><code class="hljs bash">openixcli flash \<br>    firmware.fex \<br>    --bus 1 \<br>    --port 2 \<br>    --verify \<br>    --mode partition_erase \<br>    --partitions boot,rootfs,userdata \<br>    --post-action reboot \<br>    --verbose<br></code></pre></td></tr></tbody></table></figure><p><strong>等价内部结构：</strong></p><figure class="highlight rust"><table><tbody><tr><td class="code"><pre><code class="hljs rust">FlashArgs {<br>    firmware_path: PathBuf::<span class="hljs-title function_ invoke__">from</span>(<span class="hljs-string">"firmware.fex"</span>),<br>    bus: <span class="hljs-title function_ invoke__">Some</span>(<span class="hljs-number">1</span>),<br>    port: <span class="hljs-title function_ invoke__">Some</span>(<span class="hljs-number">2</span>),<br>    verify: <span class="hljs-literal">true</span>,<br>    mode: FlashMode::PartitionErase,<br>    partitions: <span class="hljs-title function_ invoke__">Some</span>([<span class="hljs-string">"boot"</span>, <span class="hljs-string">"rootfs"</span>, <span class="hljs-string">"userdata"</span>]),<br>    post_action: <span class="hljs-string">"reboot"</span>,<br>    verbose: <span class="hljs-literal">true</span>,<br>}<br></code></pre></td></tr></tbody></table></figure><h3 id="错误处理示例" data-id="错误处理示例" class="notion-h"><a href="#错误处理示例" class="headerlink" title="错误处理示例"></a>错误处理示例</h3><figure class="highlight rust"><table><tbody><tr><td class="code"><pre><code class="hljs rust"><span class="hljs-comment">// 无效刷写模式</span><br><span class="hljs-keyword">let</span> <span class="hljs-variable">result</span> = FlashMode::<span class="hljs-title function_ invoke__">from_str</span>(<span class="hljs-string">"invalid_mode"</span>);<br><span class="hljs-comment">// Err("Invalid flash mode: invalid_mode")</span><br><br><span class="hljs-comment">// 使用 ? 操作符传播错误</span><br><span class="hljs-keyword">let</span> <span class="hljs-variable">mode</span> = FlashMode::<span class="hljs-title function_ invoke__">from_str</span>(&amp;mode_str)<br>    .<span class="hljs-title function_ invoke__">map_err</span>(|e| anyhow::anyhow!(<span class="hljs-string">"{}"</span>, e))?;<br></code></pre></td></tr></tbody></table></figure><hr><h2 id="自动生成的帮助信息" data-id="自动生成的帮助信息" class="notion-h"><a href="#自动生成的帮助信息" class="headerlink" title="自动生成的帮助信息"></a>自动生成的帮助信息</h2><h3 id="根命令帮助" data-id="根命令帮助" class="notion-h"><a href="#根命令帮助" class="headerlink" title="根命令帮助"></a>根命令帮助</h3><figure class="highlight plaintext"><table><tbody><tr><td class="code"><pre><code class="hljs plaintext">$ openixcli --help<br>Firmware flashing CLI tool for Allwinner chips<br><br>Usage: openixcli [COMMAND]<br><br>Commands:<br>  scan   Scan for connected devices<br>  flash  Flash firmware to device<br>  tui    Launch interactive TUI mode<br>  help   Print this message or the help of the given subcommand(s)<br><br>Options:<br>  -v, --verbose  Enable verbose output<br>  -h, --help     Print help<br>  -V, --version  Print version<br></code></pre></td></tr></tbody></table></figure><h3 id="Flash-子命令帮助" data-id="Flash-子命令帮助" class="notion-h"><a href="#Flash-子命令帮助" class="headerlink" title="Flash 子命令帮助"></a>Flash 子命令帮助</h3><figure class="highlight plaintext"><table><tbody><tr><td class="code"><pre><code class="hljs plaintext">$ openixcli flash --help<br>Flash firmware to device<br><br>Usage: openixcli flash [OPTIONS] &lt;FIRMWARE&gt;<br><br>Arguments:<br>  &lt;FIRMWARE&gt;  Path to firmware file<br><br>Options:<br>  -b, --bus &lt;BUS&gt;                USB bus number<br>  -P, --port &lt;PORT&gt;              USB port number<br>  -V, --verify &lt;VERIFY&gt;          Enable verification after write [default: true]<br>  -m, --mode &lt;MODE&gt;              Flash mode: partition, keep_data, partition_erase, full_erase [default: full_erase]<br>  -p, --partitions &lt;PARTITIONS&gt;  Partitions to flash (comma-separated)<br>  -a, --post-action &lt;POST_ACTION&gt;  Post-flash action: reboot, poweroff, shutdown [default: reboot]<br>  -v, --verbose                  Enable verbose output<br>  -h, --help                     Print help<br></code></pre></td></tr></tbody></table></figure><hr><h2 id="CLI-命令树图" data-id="CLI-命令树图" class="notion-h"><a href="#CLI-命令树图" class="headerlink" title="CLI 命令树图"></a>CLI 命令树图</h2><pre class="mermaid">graph TD    ROOT[openixcli] --&gt; V[--verbose]    ROOT --&gt; VER[--version]    ROOT --&gt; SCAN[scan]    ROOT --&gt; FLASH[flash]    ROOT --&gt; TUI[tui]    ROOT --&gt; DEFAULT[无子命令 -&gt; TUI]    SCAN --&gt; SL[-l/--detailed]    FLASH --&gt; FIRM[firmware 必需]    FLASH --&gt; B[-b/--bus]    FLASH --&gt; P[-P/--port]    FLASH --&gt; VERF[-V/--verify 默认true]    FLASH --&gt; M[-m/--mode 默认full_erase]    FLASH --&gt; PART[-p/--partitions]    FLASH --&gt; ACT[-a/--post-action 默认reboot]    V -.-&gt;|global| SCAN    V -.-&gt;|global| FLASH    V -.-&gt;|global| TUI</pre>]]>
    </content>
    <id>https://gloomyghost.com/posts/openixcli-cli-module/</id>
    <link href="https://gloomyghost.com/posts/openixcli-cli-module/"/>
    <published>2026-04-25T16:00:00.000Z</published>
    <summary>
      <![CDATA[<h2 id="概述" data-id="概述" class="notion-h"><a href="#概述" class="headerlink" title="概述"></a>概述</h2><p>命令行接口（CLI）是嵌入式工具与用户交互的第一道关卡。OpenixCLI 使用]]>
    </summary>
    <title>OpenixCLI CLI 模块深度解析：命令行接口设计</title>
    <updated>2026-05-29T20:33:31.218Z</updated>
  </entry>
  <entry>
    <author>
      <name>柚木 鉉</name>
    </author>
    <category term="Rust" scheme="https://gloomyghost.com/tags/Rust/"/>
    <category term="嵌入式" scheme="https://gloomyghost.com/tags/%E5%B5%8C%E5%85%A5%E5%BC%8F/"/>
    <category term="OpenixCLI" scheme="https://gloomyghost.com/tags/OpenixCLI/"/>
    <category term="设备扫描" scheme="https://gloomyghost.com/tags/%E8%AE%BE%E5%A4%87%E6%89%AB%E6%8F%8F/"/>
    <category term="固件刷写" scheme="https://gloomyghost.com/tags/%E5%9B%BA%E4%BB%B6%E5%88%B7%E5%86%99/"/>
    <content>
      <![CDATA[<h2 id="概述" data-id="概述" class="notion-h"><a href="#概述" class="headerlink" title="概述"></a>概述</h2><p>Commands 模块是 CLI 与核心刷写逻辑之间的桥梁。它实现了 <code>scan</code> 和 <code>flash</code> 两个主要命令，负责设备检测、固件加载、参数转换等准备工作，然后将任务交给 Flash 模块执行实际的刷写流程。</p><h3 id="模块结构" data-id="模块结构" class="notion-h"><a href="#模块结构" class="headerlink" title="模块结构"></a>模块结构</h3><figure class="highlight plaintext"><table><tbody><tr><td class="code"><pre><code class="hljs plaintext">src/commands/<br>├── mod.rs       # 模块导出<br>├── scan.rs      # 设备扫描命令<br>├── flash.rs     # 固件刷写命令<br>└── types.rs     # FlashArgs 和 FlashMode 类型定义<br></code></pre></td></tr></tbody></table></figure><h3 id="核心导出" data-id="核心导出" class="notion-h"><a href="#核心导出" class="headerlink" title="核心导出"></a>核心导出</h3><figure class="highlight rust"><table><tbody><tr><td class="code"><pre><code class="hljs rust"><span class="hljs-keyword">pub</span> <span class="hljs-keyword">use</span> types::{FlashArgs, FlashMode};<br></code></pre></td></tr></tbody></table></figure><hr><h2 id="核心数据结构" data-id="核心数据结构" class="notion-h"><a href="#核心数据结构" class="headerlink" title="核心数据结构"></a>核心数据结构</h2><h3 id="FlashArgs-参数结构" data-id="FlashArgs-参数结构" class="notion-h"><a href="#FlashArgs-参数结构" class="headerlink" title="FlashArgs 参数结构"></a>FlashArgs 参数结构</h3><figure class="highlight rust"><table><tbody><tr><td class="code"><pre><code class="hljs rust"><span class="hljs-comment">/// Flash 命令参数</span><br><span class="hljs-comment">///</span><br><span class="hljs-comment">/// 从 CLI 解析结果转换而来，供 Flasher 使用</span><br><span class="hljs-keyword">pub</span> <span class="hljs-keyword">struct</span> <span class="hljs-title class_">FlashArgs</span> {<br>    <span class="hljs-comment">/// 固件文件路径</span><br>    <span class="hljs-keyword">pub</span> firmware_path: PathBuf,<br><br>    <span class="hljs-comment">/// USB 总线号（可选）</span><br>    <span class="hljs-keyword">pub</span> bus: <span class="hljs-type">Option</span>&lt;<span class="hljs-type">u8</span>&gt;,<br><br>    <span class="hljs-comment">/// USB 端口号（可选）</span><br>    <span class="hljs-keyword">pub</span> port: <span class="hljs-type">Option</span>&lt;<span class="hljs-type">u8</span>&gt;,<br><br>    <span class="hljs-comment">/// 是否验证写入</span><br>    <span class="hljs-keyword">pub</span> verify: <span class="hljs-type">bool</span>,<br><br>    <span class="hljs-comment">/// 刷写模式</span><br>    <span class="hljs-keyword">pub</span> mode: FlashMode,<br><br>    <span class="hljs-comment">/// 要刷写的分区列表（可选）</span><br>    <span class="hljs-keyword">pub</span> partitions: <span class="hljs-type">Option</span>&lt;<span class="hljs-type">Vec</span>&lt;<span class="hljs-type">String</span>&gt;&gt;,<br><br>    <span class="hljs-comment">/// 刷写后的操作</span><br>    <span class="hljs-keyword">pub</span> post_action: <span class="hljs-type">String</span>,<br><br>    <span class="hljs-comment">/// 详细模式</span><br>    <span class="hljs-keyword">pub</span> verbose: <span class="hljs-type">bool</span>,<br>}<br></code></pre></td></tr></tbody></table></figure><h3 id="FlashMode-枚举" data-id="FlashMode-枚举" class="notion-h"><a href="#FlashMode-枚举" class="headerlink" title="FlashMode 枚举"></a>FlashMode 枚举</h3><figure class="highlight rust"><table><tbody><tr><td class="code"><pre><code class="hljs rust"><span class="hljs-comment">/// 刷写模式</span><br><span class="hljs-meta">#[derive(Debug, Clone, Copy, PartialEq, Eq)]</span><br><span class="hljs-keyword">pub</span> <span class="hljs-keyword">enum</span> <span class="hljs-title class_">FlashMode</span> {<br>    <span class="hljs-comment">/// 仅刷写指定分区</span><br>    Partition,<br><br>    <span class="hljs-comment">/// 保留现有数据</span><br>    KeepData,<br><br>    <span class="hljs-comment">/// 刷写前擦除分区</span><br>    PartitionErase,<br><br>    <span class="hljs-comment">/// 刷写前完全擦除</span><br>    FullErase,<br>}<br><br><span class="hljs-keyword">impl</span> <span class="hljs-title class_">FromStr</span> <span class="hljs-keyword">for</span> <span class="hljs-title class_">FlashMode</span> {<br>    <span class="hljs-keyword">type</span> <span class="hljs-title class_">Err</span> = <span class="hljs-type">String</span>;<br><br>    <span class="hljs-keyword">fn</span> <span class="hljs-title function_">from_str</span>(s: &amp;<span class="hljs-type">str</span>) <span class="hljs-punctuation">-&gt;</span> <span class="hljs-type">Result</span>&lt;<span class="hljs-keyword">Self</span>, <span class="hljs-keyword">Self</span>::<span class="hljs-literal">Err</span>&gt; {<br>        <span class="hljs-keyword">match</span> s {<br>            <span class="hljs-string">"partition"</span> =&gt; <span class="hljs-title function_ invoke__">Ok</span>(<span class="hljs-keyword">Self</span>::Partition),<br>            <span class="hljs-string">"keep_data"</span> =&gt; <span class="hljs-title function_ invoke__">Ok</span>(<span class="hljs-keyword">Self</span>::KeepData),<br>            <span class="hljs-string">"partition_erase"</span> =&gt; <span class="hljs-title function_ invoke__">Ok</span>(<span class="hljs-keyword">Self</span>::PartitionErase),<br>            <span class="hljs-string">"full_erase"</span> =&gt; <span class="hljs-title function_ invoke__">Ok</span>(<span class="hljs-keyword">Self</span>::FullErase),<br>            _ =&gt; <span class="hljs-title function_ invoke__">Err</span>(<span class="hljs-built_in">format!</span>(<span class="hljs-string">"Invalid flash mode: {}"</span>, s)),<br>        }<br>    }<br>}<br></code></pre></td></tr></tbody></table></figure><hr><h2 id="Scan-命令实现" data-id="Scan-命令实现" class="notion-h"><a href="#Scan-命令实现" class="headerlink" title="Scan 命令实现"></a>Scan 命令实现</h2><h3 id="基本扫描流程" data-id="基本扫描流程" class="notion-h"><a href="#基本扫描流程" class="headerlink" title="基本扫描流程"></a>基本扫描流程</h3><figure class="highlight rust"><table><tbody><tr><td class="code"><pre><code class="hljs rust"><span class="hljs-comment">/// 执行设备扫描命令</span><br><span class="hljs-keyword">pub</span> <span class="hljs-keyword">async</span> <span class="hljs-keyword">fn</span> <span class="hljs-title function_">execute</span>(detailed: <span class="hljs-type">bool</span>) <span class="hljs-punctuation">-&gt;</span> anyhow::<span class="hljs-type">Result</span>&lt;()&gt; {<br>    <span class="hljs-built_in">println!</span>(<span class="hljs-string">"{}"</span>, <span class="hljs-string">"Scanning USB devices..."</span>.<span class="hljs-title function_ invoke__">cyan</span>().<span class="hljs-title function_ invoke__">bold</span>());<br>    <span class="hljs-built_in">println!</span>();<br><br>    <span class="hljs-comment">// 1. 使用 libefex 扫描 USB 设备</span><br>    <span class="hljs-keyword">let</span> <span class="hljs-variable">devices</span> = Context::<span class="hljs-title function_ invoke__">scan_usb_devices</span>()?;<br><br>    <span class="hljs-keyword">if</span> devices.<span class="hljs-title function_ invoke__">is_empty</span>() {<br>        <span class="hljs-built_in">println!</span>(<span class="hljs-string">"{}"</span>, <span class="hljs-string">"No devices found."</span>.<span class="hljs-title function_ invoke__">yellow</span>());<br>        <span class="hljs-keyword">return</span> <span class="hljs-title function_ invoke__">Ok</span>(());<br>    }<br><br>    <span class="hljs-built_in">println!</span>(<span class="hljs-string">"Found {} device(s):\n"</span>, devices.<span class="hljs-title function_ invoke__">len</span>());<br><br>    <span class="hljs-comment">// 2. 显示设备列表</span><br>    <span class="hljs-keyword">for</span> (idx, dev) <span class="hljs-keyword">in</span> devices.<span class="hljs-title function_ invoke__">iter</span>().<span class="hljs-title function_ invoke__">enumerate</span>() {<br>        <span class="hljs-built_in">println!</span>(<br>            <span class="hljs-string">"[{}] Bus {:03}, Port {:03}"</span>,<br>            (idx + <span class="hljs-number">1</span>).<span class="hljs-title function_ invoke__">to_string</span>().<span class="hljs-title function_ invoke__">cyan</span>(),<br>            dev.bus,<br>            dev.port<br>        );<br><br>        <span class="hljs-comment">// 3. 详细模式下初始化设备获取更多信息</span><br>        <span class="hljs-keyword">if</span> detailed {<br>            <span class="hljs-keyword">let</span> <span class="hljs-keyword">mut </span><span class="hljs-variable">ctx</span> = Context::<span class="hljs-title function_ invoke__">new</span>();<br>            <span class="hljs-keyword">if</span> ctx.<span class="hljs-title function_ invoke__">scan_usb_device_at</span>(dev.bus, dev.port).<span class="hljs-title function_ invoke__">is_err</span>() {<br>                <span class="hljs-built_in">println!</span>(<span class="hljs-string">"    {}"</span>, <span class="hljs-string">"Failed to initialize device"</span>.<span class="hljs-title function_ invoke__">red</span>());<br>                <span class="hljs-keyword">continue</span>;<br>            }<br><br>            <span class="hljs-keyword">if</span> ctx.<span class="hljs-title function_ invoke__">usb_init</span>().<span class="hljs-title function_ invoke__">is_err</span>() {<br>                <span class="hljs-built_in">println!</span>(<span class="hljs-string">"    {}"</span>, <span class="hljs-string">"Failed to initialize USB"</span>.<span class="hljs-title function_ invoke__">red</span>());<br>                <span class="hljs-keyword">continue</span>;<br>            }<br><br>            <span class="hljs-keyword">if</span> ctx.<span class="hljs-title function_ invoke__">efex_init</span>().<span class="hljs-title function_ invoke__">is_err</span>() {<br>                <span class="hljs-built_in">println!</span>(<span class="hljs-string">"    {}"</span>, <span class="hljs-string">"Failed to initialize EFEX"</span>.<span class="hljs-title function_ invoke__">red</span>());<br>                <span class="hljs-keyword">continue</span>;<br>            }<br><br>            <span class="hljs-comment">// 4. 获取芯片版本和设备模式</span><br>            <span class="hljs-keyword">let</span> <span class="hljs-variable">mode_str</span> = ctx.<span class="hljs-title function_ invoke__">get_device_mode_str</span>().<span class="hljs-title function_ invoke__">to_string</span>();<br>            <span class="hljs-keyword">let</span> <span class="hljs-variable">chip_version</span> = <span class="hljs-keyword">unsafe</span> { (*ctx.<span class="hljs-title function_ invoke__">as_ptr</span>()).resp.id };<br><br>            <span class="hljs-built_in">println!</span>(<br>                <span class="hljs-string">"    Chip: {} (0x{:08x})"</span>,<br>                mode_str.<span class="hljs-title function_ invoke__">white</span>().<span class="hljs-title function_ invoke__">bold</span>(),<br>                chip_version<br>            );<br>            <span class="hljs-built_in">println!</span>(<br>                <span class="hljs-string">"    Mode: {}"</span>,<br>                <span class="hljs-keyword">match</span> ctx.<span class="hljs-title function_ invoke__">get_device_mode</span>() {<br>                    DeviceMode::Fel =&gt; <span class="hljs-string">"FEL (USB Boot)"</span>,<br>                    DeviceMode::Srv =&gt; <span class="hljs-string">"FES (U-Boot)"</span>,<br>                    _ =&gt; <span class="hljs-string">"Unknown"</span>,<br>                }<br>            );<br>        }<br>        <span class="hljs-built_in">println!</span>();<br>    }<br><br>    <span class="hljs-title function_ invoke__">Ok</span>(())<br>}<br></code></pre></td></tr></tbody></table></figure><h3 id="扫描流程图" data-id="扫描流程图" class="notion-h"><a href="#扫描流程图" class="headerlink" title="扫描流程图"></a>扫描流程图</h3><pre class="mermaid">flowchart TD    A[execute - detailed] --&gt; B[scan_usb_devices]    B --&gt; C{设备列表?}    C --&gt;|空| D[No devices found]    C --&gt;|有设备| E[遍历设备列表]    E --&gt; F[打印 Bus/Port]    F --&gt; G{detailed?}    G --&gt;|否| E    G --&gt;|是| H[Context::new]    H --&gt; I[scan_usb_device_at]    I --&gt; J[usb_init]    J --&gt; K[efex_init]    K --&gt; L[获取芯片版本和模式]    L --&gt; M[打印详细信息]    M --&gt; E    E --&gt;|完成| N[Ok]</pre><h3 id="DeviceMode-设备模式" data-id="DeviceMode-设备模式" class="notion-h"><a href="#DeviceMode-设备模式" class="headerlink" title="DeviceMode 设备模式"></a>DeviceMode 设备模式</h3><figure class="highlight rust"><table><tbody><tr><td class="code"><pre><code class="hljs rust"><span class="hljs-comment">/// 设备模式（来自 libefex）</span><br><span class="hljs-keyword">pub</span> <span class="hljs-keyword">enum</span> <span class="hljs-title class_">DeviceMode</span> {<br>    Fel,  <span class="hljs-comment">// FEL 模式（USB 启动，需要 DRAM 初始化）</span><br>    Srv,  <span class="hljs-comment">// FES/Srv 模式（U-Boot 运行中）</span><br>    <span class="hljs-comment">// ...</span><br>}<br></code></pre></td></tr></tbody></table></figure><p><strong>FEL vs FES 模式：</strong></p><table><thead><tr><th>模式</th><th>状态</th><th>操作</th></tr></thead><tbody><tr><td>FEL</td><td>蚕复位后的 USB Boot 模式</td><td>需要 DRAM 初始化 + U-Boot 下载</td></tr><tr><td>FES/Srv</td><td>U-Boot 已运行</td><td>直接执行分区刷写</td></tr></tbody></table><hr><h2 id="Flash-命令实现" data-id="Flash-命令实现" class="notion-h"><a href="#Flash-命令实现" class="headerlink" title="Flash 命令实现"></a>Flash 命令实现</h2><h3 id="基本刷写流程" data-id="基本刷写流程" class="notion-h"><a href="#基本刷写流程" class="headerlink" title="基本刷写流程"></a>基本刷写流程</h3><figure class="highlight rust"><table><tbody><tr><td class="code"><pre><code class="hljs rust"><span class="hljs-comment">/// 执行刷写命令</span><br><span class="hljs-keyword">pub</span> <span class="hljs-keyword">async</span> <span class="hljs-keyword">fn</span> <span class="hljs-title function_">execute</span>(args: FlashArgs) <span class="hljs-punctuation">-&gt;</span> anyhow::<span class="hljs-type">Result</span>&lt;()&gt; {<br>    <span class="hljs-keyword">let</span> <span class="hljs-variable">logger</span> = Logger::<span class="hljs-title function_ invoke__">with_verbose</span>(args.verbose);<br><br>    <span class="hljs-comment">// 1. 打印固件路径</span><br>    logger.<span class="hljs-title function_ invoke__">info</span>(&amp;<span class="hljs-built_in">format!</span>(<br>        <span class="hljs-string">"Loading firmware: {}"</span>,<br>        args.firmware_path.<span class="hljs-title function_ invoke__">display</span>()<br>    ));<br><br>    <span class="hljs-comment">// 2. 检查固件文件是否存在</span><br>    <span class="hljs-keyword">if</span> !args.firmware_path.<span class="hljs-title function_ invoke__">exists</span>() {<br>        logger.<span class="hljs-title function_ invoke__">error</span>(&amp;<span class="hljs-built_in">format!</span>(<br>            <span class="hljs-string">"Firmware file not found: {}"</span>,<br>            args.firmware_path.<span class="hljs-title function_ invoke__">display</span>()<br>        ));<br>        <span class="hljs-keyword">return</span> <span class="hljs-title function_ invoke__">Err</span>(anyhow::anyhow!(<span class="hljs-string">"Firmware file not found"</span>));<br>    }<br><br>    <span class="hljs-comment">// 3. 加载固件</span><br>    <span class="hljs-keyword">let</span> <span class="hljs-keyword">mut </span><span class="hljs-variable">packer</span> = crate::firmware::OpenixPacker::<span class="hljs-title function_ invoke__">new</span>();<br>    packer.<span class="hljs-title function_ invoke__">load</span>(&amp;args.firmware_path)?;<br><br>    <span class="hljs-comment">// 4. 获取固件信息</span><br>    <span class="hljs-keyword">let</span> <span class="hljs-variable">image_info</span> = packer.<span class="hljs-title function_ invoke__">get_image_info</span>();<br>    logger.<span class="hljs-title function_ invoke__">info</span>(&amp;<span class="hljs-built_in">format!</span>(<br>        <span class="hljs-string">"Firmware size: {} MB, {} files"</span>,<br>        image_info.image_size / (<span class="hljs-number">1024</span> * <span class="hljs-number">1024</span>),<br>        image_info.num_files<br>    ));<br><br>    <span class="hljs-comment">// 5. 打印设备选择信息</span><br>    <span class="hljs-keyword">if</span> <span class="hljs-keyword">let</span> (<span class="hljs-title function_ invoke__">Some</span>(bus), <span class="hljs-title function_ invoke__">Some</span>(port)) = (args.bus, args.port) {<br>        logger.<span class="hljs-title function_ invoke__">info</span>(&amp;<span class="hljs-built_in">format!</span>(<span class="hljs-string">"Selected device: Bus {}, Port {}"</span>, bus, port));<br>    } <span class="hljs-keyword">else</span> {<br>        logger.<span class="hljs-title function_ invoke__">info</span>(<span class="hljs-string">"No device specified, will use first available device"</span>);<br>    }<br><br>    <span class="hljs-comment">// 6. 构建 FlashOptions</span><br>    <span class="hljs-keyword">let</span> <span class="hljs-variable">options</span> = FlashOptions {<br>        bus: args.bus,<br>        port: args.port,<br>        verify: args.verify,<br>        mode: <span class="hljs-keyword">match</span> args.mode {<br>            crate::commands::FlashMode::Partition =&gt; FlashMode::Partition,<br>            crate::commands::FlashMode::KeepData =&gt; FlashMode::KeepData,<br>            crate::commands::FlashMode::PartitionErase =&gt; FlashMode::PartitionErase,<br>            crate::commands::FlashMode::FullErase =&gt; FlashMode::FullErase,<br>        },<br>        partitions: args.partitions,<br>        post_action: args.post_action,<br>    };<br><br>    <span class="hljs-comment">// 7. 创建 Flasher 并执行刷写</span><br>    <span class="hljs-keyword">let</span> <span class="hljs-keyword">mut </span><span class="hljs-variable">flasher</span> = Flasher::<span class="hljs-title function_ invoke__">new</span>(packer, options, logger.<span class="hljs-title function_ invoke__">clone</span>());<br>    <span class="hljs-keyword">if</span> <span class="hljs-keyword">let</span> <span class="hljs-variable">Err</span>(e) = flasher.<span class="hljs-title function_ invoke__">execute</span>().<span class="hljs-keyword">await</span> {<br>        logger.<span class="hljs-title function_ invoke__">error</span>(&amp;<span class="hljs-built_in">format!</span>(<span class="hljs-string">"Flash failed: {}"</span>, e));<br>        <span class="hljs-keyword">return</span> <span class="hljs-title function_ invoke__">Err</span>(anyhow::anyhow!(<span class="hljs-string">"{}"</span>, e));<br>    }<br><br>    <span class="hljs-comment">// 8. 打印成功消息</span><br>    <span class="hljs-built_in">println!</span>();<br>    logger.<span class="hljs-title function_ invoke__">stage_complete</span>(<span class="hljs-string">"All partitions flashed successfully"</span>);<br><br>    <span class="hljs-title function_ invoke__">Ok</span>(())<br>}<br></code></pre></td></tr></tbody></table></figure><h3 id="FlashOptions-结构" data-id="FlashOptions-结构" class="notion-h"><a href="#FlashOptions-结构" class="headerlink" title="FlashOptions 结构"></a>FlashOptions 结构</h3><figure class="highlight rust"><table><tbody><tr><td class="code"><pre><code class="hljs rust"><span class="hljs-comment">/// Flash 选项（供 Flasher 使用）</span><br><span class="hljs-keyword">pub</span> <span class="hljs-keyword">struct</span> <span class="hljs-title class_">FlashOptions</span> {<br>    <span class="hljs-keyword">pub</span> bus: <span class="hljs-type">Option</span>&lt;<span class="hljs-type">u8</span>&gt;,<br>    <span class="hljs-keyword">pub</span> port: <span class="hljs-type">Option</span>&lt;<span class="hljs-type">u8</span>&gt;,<br>    <span class="hljs-keyword">pub</span> verify: <span class="hljs-type">bool</span>,<br>    <span class="hljs-keyword">pub</span> mode: FlashMode,<br>    <span class="hljs-keyword">pub</span> partitions: <span class="hljs-type">Option</span>&lt;<span class="hljs-type">Vec</span>&lt;<span class="hljs-type">String</span>&gt;&gt;,<br>    <span class="hljs-keyword">pub</span> post_action: <span class="hljs-type">String</span>,<br>}<br></code></pre></td></tr></tbody></table></figure><h3 id="FlashArgs-到-FlashOptions-的转换" data-id="FlashArgs-到-FlashOptions-的转换" class="notion-h"><a href="#FlashArgs-到-FlashOptions-的转换" class="headerlink" title="FlashArgs 到 FlashOptions 的转换"></a>FlashArgs 到 FlashOptions 的转换</h3><figure class="highlight rust"><table><tbody><tr><td class="code"><pre><code class="hljs rust"><span class="hljs-comment">// 模式枚举转换</span><br>mode: <span class="hljs-keyword">match</span> args.mode {<br>    crate::commands::FlashMode::Partition =&gt; FlashMode::Partition,<br>    crate::commands::FlashMode::KeepData =&gt; FlashMode::KeepData,<br>    crate::commands::FlashMode::PartitionErase =&gt; FlashMode::PartitionErase,<br>    crate::commands::FlashMode::FullErase =&gt; FlashMode::FullErase,<br>}<br></code></pre></td></tr></tbody></table></figure><hr><h2 id="调用流程分析" data-id="调用流程分析" class="notion-h"><a href="#调用流程分析" class="headerlink" title="调用流程分析"></a>调用流程分析</h2><h3 id="Flash-命令完整流程" data-id="Flash-命令完整流程" class="notion-h"><a href="#Flash-命令完整流程" class="headerlink" title="Flash 命令完整流程"></a>Flash 命令完整流程</h3><pre class="mermaid">sequenceDiagram    participant CLI as main.rs    participant Flash as flash::execute    participant Packer as OpenixPacker    participant Flasher as Flasher    CLI-&gt;&gt;Flash: execute(FlashArgs)    Flash-&gt;&gt;Flash: 检查固件路径存在    Flash-&gt;&gt;Packer: OpenixPacker::new()    Flash-&gt;&gt;Packer: load(firmware_path)    Packer--&gt;&gt;Flash: Ok()    Flash-&gt;&gt;Packer: get_image_info()    Packer--&gt;&gt;Flash: ImageInfo    Flash-&gt;&gt;Flash: 构建 FlashOptions    Flash-&gt;&gt;Flasher: Flasher::new(packer, options, logger)    Flash-&gt;&gt;Flasher: execute()    Note over Flasher: FEL 模式处理    Note over Flasher: DRAM 初始化    Note over Flasher: U-Boot 下载    Note over Flasher: 设备重连    Note over Flasher: FES 模式处理    Note over Flasher: 设备查询    Note over Flasher: Flash 擦除    Note over Flasher: MBR 写入    Note over Flasher: 分区刷写    Note over Flasher: Boot 写入    Flasher--&gt;&gt;Flash: Ok()    Flash--&gt;&gt;CLI: Ok()</pre><h3 id="CLI-参数解析到-FlashArgs" data-id="CLI-参数解析到-FlashArgs" class="notion-h"><a href="#CLI-参数解析到-FlashArgs" class="headerlink" title="CLI 参数解析到 FlashArgs"></a>CLI 参数解析到 FlashArgs</h3><figure class="highlight rust"><table><tbody><tr><td class="code"><pre><code class="hljs rust"><span class="hljs-comment">// main.rs 中的转换</span><br><span class="hljs-title function_ invoke__">Some</span>(Commands::Flash {<br>    firmware,<br>    bus,<br>    port,<br>    verify,<br>    mode,<br>    partitions,<br>    post_action,<br>}) =&gt; {<br>    <span class="hljs-comment">// 解析刷写模式</span><br>    <span class="hljs-keyword">let</span> <span class="hljs-variable">flash_mode</span> = FlashMode::<span class="hljs-title function_ invoke__">from_str</span>(&amp;mode)?;<br><br>    <span class="hljs-comment">// 解析分区列表</span><br>    <span class="hljs-keyword">let</span> <span class="hljs-variable">partition_list</span> = partitions<br>        .<span class="hljs-title function_ invoke__">map</span>(|s| s.<span class="hljs-title function_ invoke__">split</span>(<span class="hljs-string">','</span>)<br>               .<span class="hljs-title function_ invoke__">map</span>(|p| p.<span class="hljs-title function_ invoke__">trim</span>().<span class="hljs-title function_ invoke__">to_string</span>())<br>               .<span class="hljs-title function_ invoke__">collect</span>());<br><br>    <span class="hljs-comment">// 构建 FlashArgs</span><br>    <span class="hljs-keyword">let</span> <span class="hljs-variable">args</span> = FlashArgs {<br>        firmware_path: firmware.<span class="hljs-title function_ invoke__">into</span>(),<br>        bus,<br>        port,<br>        verify,<br>        mode: flash_mode,<br>        partitions: partition_list,<br>        post_action,<br>        verbose: cli.verbose,<br>    };<br><br>    commands::flash::<span class="hljs-title function_ invoke__">execute</span>(args).<span class="hljs-keyword">await</span>?;<br>}<br></code></pre></td></tr></tbody></table></figure><hr><h2 id="代码亮点" data-id="代码亮点" class="notion-h"><a href="#代码亮点" class="headerlink" title="代码亮点"></a>代码亮点</h2><h3 id="1-设备模式检测" data-id="1-设备模式检测" class="notion-h"><a href="#1-设备模式检测" class="headerlink" title="1. 设备模式检测"></a>1. 设备模式检测</h3><figure class="highlight rust"><table><tbody><tr><td class="code"><pre><code class="hljs rust"><span class="hljs-comment">// 获取设备模式字符串</span><br><span class="hljs-keyword">let</span> <span class="hljs-variable">mode_str</span> = ctx.<span class="hljs-title function_ invoke__">get_device_mode_str</span>().<span class="hljs-title function_ invoke__">to_string</span>();<br><br><span class="hljs-comment">// 获取芯片版本（需要 unsafe）</span><br><span class="hljs-keyword">let</span> <span class="hljs-variable">chip_version</span> = <span class="hljs-keyword">unsafe</span> { (*ctx.<span class="hljs-title function_ invoke__">as_ptr</span>()).resp.id };<br></code></pre></td></tr></tbody></table></figure><h3 id="2-colored-库彩色输出" data-id="2-colored-库彩色输出" class="notion-h"><a href="#2-colored-库彩色输出" class="headerlink" title="2. colored 库彩色输出"></a>2. colored 库彩色输出</h3><figure class="highlight rust"><table><tbody><tr><td class="code"><pre><code class="hljs rust"><span class="hljs-built_in">println!</span>(<span class="hljs-string">"{}"</span>, <span class="hljs-string">"Scanning USB devices..."</span>.<span class="hljs-title function_ invoke__">cyan</span>().<span class="hljs-title function_ invoke__">bold</span>());<br><span class="hljs-built_in">println!</span>(<span class="hljs-string">"{}"</span>, <span class="hljs-string">"No devices found."</span>.<span class="hljs-title function_ invoke__">yellow</span>());<br><span class="hljs-built_in">println!</span>(<span class="hljs-string">"    {}"</span>, <span class="hljs-string">"Failed to initialize device"</span>.<span class="hljs-title function_ invoke__">red</span>());<br></code></pre></td></tr></tbody></table></figure><h3 id="3-多阶段错误处理" data-id="3-多阶段错误处理" class="notion-h"><a href="#3-多阶段错误处理" class="headerlink" title="3. 多阶段错误处理"></a>3. 多阶段错误处理</h3><figure class="highlight rust"><table><tbody><tr><td class="code"><pre><code class="hljs rust"><span class="hljs-keyword">if</span> detailed {<br>    <span class="hljs-keyword">let</span> <span class="hljs-keyword">mut </span><span class="hljs-variable">ctx</span> = Context::<span class="hljs-title function_ invoke__">new</span>();<br>    <span class="hljs-keyword">if</span> ctx.<span class="hljs-title function_ invoke__">scan_usb_device_at</span>(dev.bus, dev.port).<span class="hljs-title function_ invoke__">is_err</span>() {<br>        <span class="hljs-built_in">println!</span>(<span class="hljs-string">"    {}"</span>, <span class="hljs-string">"Failed to initialize device"</span>.<span class="hljs-title function_ invoke__">red</span>());<br>        <span class="hljs-keyword">continue</span>;  <span class="hljs-comment">// 继续处理下一个设备</span><br>    }<br>    <span class="hljs-keyword">if</span> ctx.<span class="hljs-title function_ invoke__">usb_init</span>().<span class="hljs-title function_ invoke__">is_err</span>() {<br>        <span class="hljs-built_in">println!</span>(<span class="hljs-string">"    {}"</span>, <span class="hljs-string">"Failed to initialize USB"</span>.<span class="hljs-title function_ invoke__">red</span>());<br>        <span class="hljs-keyword">continue</span>;<br>    }<br>    <span class="hljs-keyword">if</span> ctx.<span class="hljs-title function_ invoke__">efex_init</span>().<span class="hljs-title function_ invoke__">is_err</span>() {<br>        <span class="hljs-built_in">println!</span>(<span class="hljs-string">"    {}"</span>, <span class="hljs-string">"Failed to initialize EFEX"</span>.<span class="hljs-title function_ invoke__">red</span>());<br>        <span class="hljs-keyword">continue</span>;<br>    }<br>    <span class="hljs-comment">// ... 成功后获取详细信息</span><br>}<br></code></pre></td></tr></tbody></table></figure><h3 id="4-模式枚举映射" data-id="4-模式枚举映射" class="notion-h"><a href="#4-模式枚举映射" class="headerlink" title="4. 模式枚举映射"></a>4. 模式枚举映射</h3><figure class="highlight rust"><table><tbody><tr><td class="code"><pre><code class="hljs rust"><span class="hljs-comment">// commands::FlashMode -&gt; flash::FlashMode</span><br>mode: <span class="hljs-keyword">match</span> args.mode {<br>    crate::commands::FlashMode::Partition =&gt; FlashMode::Partition,<br>    crate::commands::FlashMode::KeepData =&gt; FlashMode::KeepData,<br>    crate::commands::FlashMode::PartitionErase =&gt; FlashMode::PartitionErase,<br>    crate::commands::FlashMode::FullErase =&gt; FlashMode::FullErase,<br>}<br></code></pre></td></tr></tbody></table></figure><h3 id="5-分区列表解析" data-id="5-分区列表解析" class="notion-h"><a href="#5-分区列表解析" class="headerlink" title="5. 分区列表解析"></a>5. 分区列表解析</h3><figure class="highlight rust"><table><tbody><tr><td class="code"><pre><code class="hljs rust"><span class="hljs-comment">// "boot,rootfs,userdata" -&gt; ["boot", "rootfs", "userdata"]</span><br><span class="hljs-keyword">let</span> <span class="hljs-variable">partition_list</span> = partitions<br>    .<span class="hljs-title function_ invoke__">map</span>(|s| s.<span class="hljs-title function_ invoke__">split</span>(<span class="hljs-string">','</span>)<br>           .<span class="hljs-title function_ invoke__">map</span>(|p| p.<span class="hljs-title function_ invoke__">trim</span>().<span class="hljs-title function_ invoke__">to_string</span>())<br>           .<span class="hljs-title function_ invoke__">collect</span>());<br></code></pre></td></tr></tbody></table></figure><hr><h2 id="实践示例" data-id="实践示例" class="notion-h"><a href="#实践示例" class="headerlink" title="实践示例"></a>实践示例</h2><h3 id="Scan-命令输出（基本模式）" data-id="Scan-命令输出（基本模式）" class="notion-h"><a href="#Scan-命令输出（基本模式）" class="headerlink" title="Scan 命令输出（基本模式）"></a>Scan 命令输出（基本模式）</h3><figure class="highlight bash"><table><tbody><tr><td class="code"><pre><code class="hljs bash">$ openixcli scan<br><br>Scanning USB devices...<br><br>Found 1 device(s):<br><br>[1] Bus 001, Port 002<br></code></pre></td></tr></tbody></table></figure><h3 id="Scan-命令输出（详细模式）" data-id="Scan-命令输出（详细模式）" class="notion-h"><a href="#Scan-命令输出（详细模式）" class="headerlink" title="Scan 命令输出（详细模式）"></a>Scan 命令输出（详细模式）</h3><figure class="highlight bash"><table><tbody><tr><td class="code"><pre><code class="hljs bash">$ openixcli scan --detailed<br><br>Scanning USB devices...<br><br>Found 1 device(s):<br><br>[1] Bus 001, Port 002<br>    Chip: sun50iw10 (0x00182300)<br>    Mode: FEL (USB Boot)<br><br>[2] Bus 002, Port 001<br>    Chip: sun8iw15 (0x00181500)<br>    Mode: FES (U-Boot)<br></code></pre></td></tr></tbody></table></figure><h3 id="Flash-命令执行" data-id="Flash-命令执行" class="notion-h"><a href="#Flash-命令执行" class="headerlink" title="Flash 命令执行"></a>Flash 命令执行</h3><figure class="highlight bash"><table><tbody><tr><td class="code"><pre><code class="hljs bash">$ openixcli flash firmware.fex<br><br>[INFO] Loading firmware: firmware.fex<br>[INFO] Firmware size: 128 MB, 12 files<br>[INFO] No device specified, will use first available device<br><br>⠋ [00:00:15] [████████████████████████████████████] 100% [boot] 3.2 MB/s<br><br>[OKAY] All partitions flashed successfully<br></code></pre></td></tr></tbody></table></figure><h3 id="指定设备和分区" data-id="指定设备和分区" class="notion-h"><a href="#指定设备和分区" class="headerlink" title="指定设备和分区"></a>指定设备和分区</h3><figure class="highlight bash"><table><tbody><tr><td class="code"><pre><code class="hljs bash">$ openixcli flash firmware.fex \<br>    --bus 1 \<br>    --port 2 \<br>    --mode partition \<br>    --partitions boot,rootfs \<br>    --post-action reboot \<br>    --verbose<br><br>[INFO] Loading firmware: firmware.fex<br>[INFO] Firmware size: 128 MB, 12 files<br>[INFO] Selected device: Bus 1, Port 2<br>[DEBG] Found device <span class="hljs-keyword">in</span> FEL mode<br>[DEBG] Initializing DRAM...<br>[INFO] DRAM initialized<br>[DEBG] Downloading U-Boot...<br>[INFO] U-Boot downloaded<br>[INFO] Reconnecting device...<br>[INFO] Device connected <span class="hljs-keyword">in</span> FES mode<br>[INFO] Querying device...<br>[INFO] Flashing partition: boot<br>[INFO] Flashing partition: rootfs<br>[INFO] Rebooting device...<br><br>[OKAY] All partitions flashed successfully<br></code></pre></td></tr></tbody></table></figure><hr><h2 id="错误处理流程" data-id="错误处理流程" class="notion-h"><a href="#错误处理流程" class="headerlink" title="错误处理流程"></a>错误处理流程</h2><h3 id="固件不存在" data-id="固件不存在" class="notion-h"><a href="#固件不存在" class="headerlink" title="固件不存在"></a>固件不存在</h3><figure class="highlight rust"><table><tbody><tr><td class="code"><pre><code class="hljs rust"><span class="hljs-keyword">if</span> !args.firmware_path.<span class="hljs-title function_ invoke__">exists</span>() {<br>    logger.<span class="hljs-title function_ invoke__">error</span>(&amp;<span class="hljs-built_in">format!</span>(<br>        <span class="hljs-string">"Firmware file not found: {}"</span>,<br>        args.firmware_path.<span class="hljs-title function_ invoke__">display</span>()<br>    ));<br>    <span class="hljs-keyword">return</span> <span class="hljs-title function_ invoke__">Err</span>(anyhow::anyhow!(<span class="hljs-string">"Firmware file not found"</span>));<br>}<br></code></pre></td></tr></tbody></table></figure><h3 id="刷写失败" data-id="刷写失败" class="notion-h"><a href="#刷写失败" class="headerlink" title="刷写失败"></a>刷写失败</h3><figure class="highlight rust"><table><tbody><tr><td class="code"><pre><code class="hljs rust"><span class="hljs-keyword">if</span> <span class="hljs-keyword">let</span> <span class="hljs-variable">Err</span>(e) = flasher.<span class="hljs-title function_ invoke__">execute</span>().<span class="hljs-keyword">await</span> {<br>    logger.<span class="hljs-title function_ invoke__">error</span>(&amp;<span class="hljs-built_in">format!</span>(<span class="hljs-string">"Flash failed: {}"</span>, e));<br>    <span class="hljs-keyword">return</span> <span class="hljs-title function_ invoke__">Err</span>(anyhow::anyhow!(<span class="hljs-string">"{}"</span>, e));<br>}<br></code></pre></td></tr></tbody></table></figure><h3 id="设备初始化失败" data-id="设备初始化失败" class="notion-h"><a href="#设备初始化失败" class="headerlink" title="设备初始化失败"></a>设备初始化失败</h3><figure class="highlight rust"><table><tbody><tr><td class="code"><pre><code class="hljs rust"><span class="hljs-keyword">if</span> ctx.<span class="hljs-title function_ invoke__">scan_usb_device_at</span>(dev.bus, dev.port).<span class="hljs-title function_ invoke__">is_err</span>() {<br>    <span class="hljs-built_in">println!</span>(<span class="hljs-string">"    {}"</span>, <span class="hljs-string">"Failed to initialize device"</span>.<span class="hljs-title function_ invoke__">red</span>());<br>    <span class="hljs-built_in">println!</span>();  <span class="hljs-comment">// 添加空行分隔</span><br>    <span class="hljs-keyword">continue</span>;    <span class="hljs-comment">// 继续处理下一个设备</span><br>}<br></code></pre></td></tr></tbody></table></figure><hr><h2 id="数据结构关系图" data-id="数据结构关系图" class="notion-h"><a href="#数据结构关系图" class="headerlink" title="数据结构关系图"></a>数据结构关系图</h2><pre class="mermaid">classDiagram    class FlashArgs {        +firmware_path: PathBuf        +bus: Option~u8~        +port: Option~u8~        +verify: bool        +mode: FlashMode        +partitions: Option~Vec~String~~        +post_action: String        +verbose: bool    }    class FlashMode {        +Partition        +KeepData        +PartitionErase        +FullErase        +from_str(s) Result        +fmt() Display    }    class FlashOptions {        +bus: Option~u8~        +port: Option~u8~        +verify: bool        +mode: FlashMode        +partitions: Option~Vec~String~~        +post_action: String    }    class Flasher {        +packer: OpenixPacker        +options: FlashOptions        +logger: Logger        +execute()    }    class Logger {        +info(message)        +error(message)        +stage_complete(message)    }    FlashArgs --&gt; FlashMode    FlashArgs --&gt; FlashOptions    FlashOptions --&gt; Flasher    Flasher --&gt; Logger</pre>]]>
    </content>
    <id>https://gloomyghost.com/posts/openixcli-commands-module/</id>
    <link href="https://gloomyghost.com/posts/openixcli-commands-module/"/>
    <published>2026-04-25T16:00:00.000Z</published>
    <summary>
      <![CDATA[<h2 id="概述" data-id="概述" class="notion-h"><a href="#概述" class="headerlink" title="概述"></a>概述</h2><p>Commands 模块是 CLI 与核心刷写逻辑之间的桥梁。它实现了 <code]]>
    </summary>
    <title>OpenixCLI Commands 模块深度解析：设备扫描与刷写命令</title>
    <updated>2026-05-29T20:33:31.218Z</updated>
  </entry>
  <entry>
    <author>
      <name>柚木 鉉</name>
    </author>
    <category term="Rust" scheme="https://gloomyghost.com/tags/Rust/"/>
    <category term="嵌入式" scheme="https://gloomyghost.com/tags/%E5%B5%8C%E5%85%A5%E5%BC%8F/"/>
    <category term="OpenixCLI" scheme="https://gloomyghost.com/tags/OpenixCLI/"/>
    <category term="FEL" scheme="https://gloomyghost.com/tags/FEL/"/>
    <category term="FES" scheme="https://gloomyghost.com/tags/FES/"/>
    <category term="刷写引擎" scheme="https://gloomyghost.com/tags/%E5%88%B7%E5%86%99%E5%BC%95%E6%93%8E/"/>
    <content>
      <![CDATA[<h2 id="概述" data-id="概述" class="notion-h"><a href="#概述" class="headerlink" title="概述"></a>概述</h2><p>Flash 模块是 OpenixCLI 的核心刷写引擎，负责协调 FEL（USB Boot）和 FES（U-Boot）两种模式的固件刷写流程。它处理设备检测、DRAM 初始化、U-Boot 下载、设备重连、分区刷写、Boot 写入等完整流程，是嵌入式固件刷写的典型实现。</p><h3 id="模块结构" data-id="模块结构" class="notion-h"><a href="#模块结构" class="headerlink" title="模块结构"></a>模块结构</h3><figure class="highlight plaintext"><table><tbody><tr><td class="code"><pre><code class="hljs plaintext">src/flash/<br>├── mod.rs                  # Flasher 主控制器<br>├── fel_handler/            # FEL 模式处理<br>│   ├── mod.rs              # FelHandler 入口<br>│   ├── dram_init.rs        # DRAM 初始化<br>│   └── uboot_download.rs   # U-Boot 下载<br>└── fes_handler/            # FES 模式处理<br>    ├── mod.rs              # FesHandler 入口<br>    ├── constants.rs        # 常量定义<br>    ├── erase_flag.rs       # Flash 擦除<br>    ├── mbr_download.rs     # MBR 写入<br>    ├── ubifs_config.rs     # UBIFS 配置<br>    ├── boot_download.rs    # Boot 写入<br>    ├── types.rs            # PartitionDownloadInfo<br>    └── partition/          # 分区刷写<br>        ├── mod.rs<br>        ├── mod_impl.rs<br>        ├── raw_download.rs<br>        └── sparse_parser.rs<br></code></pre></td></tr></tbody></table></figure><hr><h2 id="核心数据结构" data-id="核心数据结构" class="notion-h"><a href="#核心数据结构" class="headerlink" title="核心数据结构"></a>核心数据结构</h2><h3 id="FlashMode-枚举" data-id="FlashMode-枚举" class="notion-h"><a href="#FlashMode-枚举" class="headerlink" title="FlashMode 枚举"></a>FlashMode 枚举</h3><figure class="highlight rust"><table><tbody><tr><td class="code"><pre><code class="hljs rust"><span class="hljs-comment">/// 刷写模式</span><br><span class="hljs-meta">#[derive(Debug, Clone, Copy, PartialEq)]</span><br><span class="hljs-keyword">pub</span> <span class="hljs-keyword">enum</span> <span class="hljs-title class_">FlashMode</span> {<br>    <span class="hljs-comment">/// 仅刷写指定分区</span><br>    Partition,<br><br>    <span class="hljs-comment">/// 保留现有数据（跳过 udisk/private 等分区）</span><br>    KeepData,<br><br>    <span class="hljs-comment">/// 刷写前擦除分区</span><br>    PartitionErase,<br><br>    <span class="hljs-comment">/// 刷写前完全擦除</span><br>    FullErase,<br>}<br><br><span class="hljs-keyword">impl</span> <span class="hljs-title class_">FlashMode</span> {<br>    <span class="hljs-comment">/// 获取擦除标志</span><br>    <span class="hljs-keyword">pub</span> <span class="hljs-keyword">fn</span> <span class="hljs-title function_">erase_flag</span>(&amp;<span class="hljs-keyword">self</span>) <span class="hljs-punctuation">-&gt;</span> <span class="hljs-type">u32</span> {<br>        <span class="hljs-keyword">match</span> <span class="hljs-keyword">self</span> {<br>            FlashMode::Partition =&gt; <span class="hljs-number">0x0</span>,<br>            FlashMode::KeepData =&gt; <span class="hljs-number">0x0</span>,<br>            FlashMode::PartitionErase =&gt; <span class="hljs-number">0x1</span>,<br>            FlashMode::FullErase =&gt; <span class="hljs-number">0x12</span>,<br>        }<br>    }<br>}<br></code></pre></td></tr></tbody></table></figure><h3 id="FlashOptions-结构" data-id="FlashOptions-结构" class="notion-h"><a href="#FlashOptions-结构" class="headerlink" title="FlashOptions 结构"></a>FlashOptions 结构</h3><figure class="highlight rust"><table><tbody><tr><td class="code"><pre><code class="hljs rust"><span class="hljs-comment">/// 刷写选项配置</span><br><span class="hljs-meta">#[derive(Debug, Clone)]</span><br><span class="hljs-keyword">pub</span> <span class="hljs-keyword">struct</span> <span class="hljs-title class_">FlashOptions</span> {<br>    <span class="hljs-comment">/// USB 总线号（可选）</span><br>    <span class="hljs-keyword">pub</span> bus: <span class="hljs-type">Option</span>&lt;<span class="hljs-type">u8</span>&gt;,<br><br>    <span class="hljs-comment">/// USB 端口号（可选）</span><br>    <span class="hljs-keyword">pub</span> port: <span class="hljs-type">Option</span>&lt;<span class="hljs-type">u8</span>&gt;,<br><br>    <span class="hljs-comment">/// 是否验证写入</span><br>    <span class="hljs-keyword">pub</span> verify: <span class="hljs-type">bool</span>,<br><br>    <span class="hljs-comment">/// 刷写模式</span><br>    <span class="hljs-keyword">pub</span> mode: FlashMode,<br><br>    <span class="hljs-comment">/// 要刷写的分区列表（可选）</span><br>    <span class="hljs-keyword">pub</span> partitions: <span class="hljs-type">Option</span>&lt;<span class="hljs-type">Vec</span>&lt;<span class="hljs-type">String</span>&gt;&gt;,<br><br>    <span class="hljs-comment">/// 刷写后的操作</span><br>    <span class="hljs-keyword">pub</span> post_action: <span class="hljs-type">String</span>,<br>}<br></code></pre></td></tr></tbody></table></figure><h3 id="Flasher-主控制器" data-id="Flasher-主控制器" class="notion-h"><a href="#Flasher-主控制器" class="headerlink" title="Flasher 主控制器"></a>Flasher 主控制器</h3><figure class="highlight rust"><table><tbody><tr><td class="code"><pre><code class="hljs rust"><span class="hljs-comment">/// Flash 主控制器</span><br><span class="hljs-comment">///</span><br><span class="hljs-comment">/// 协调 FEL 初始化、FES 处理、分区刷写</span><br><span class="hljs-keyword">pub</span> <span class="hljs-keyword">struct</span> <span class="hljs-title class_">Flasher</span> {<br>    <span class="hljs-comment">/// 固件解析器</span><br>    packer: OpenixPacker,<br><br>    <span class="hljs-comment">/// 刷写选项</span><br>    options: FlashOptions,<br><br>    <span class="hljs-comment">/// 日志器</span><br>    logger: Logger,<br>}<br></code></pre></td></tr></tbody></table></figure><h3 id="PartitionDownloadInfo-结构" data-id="PartitionDownloadInfo-结构" class="notion-h"><a href="#PartitionDownloadInfo-结构" class="headerlink" title="PartitionDownloadInfo 结构"></a>PartitionDownloadInfo 结构</h3><figure class="highlight rust"><table><tbody><tr><td class="code"><pre><code class="hljs rust"><span class="hljs-comment">/// 分区下载信息</span><br><span class="hljs-keyword">pub</span> <span class="hljs-keyword">struct</span> <span class="hljs-title class_">PartitionDownloadInfo</span> {<br>    <span class="hljs-comment">/// 分区名称</span><br>    <span class="hljs-keyword">pub</span> partition_name: <span class="hljs-type">String</span>,<br><br>    <span class="hljs-comment">/// 分区起始地址</span><br>    <span class="hljs-keyword">pub</span> partition_address: <span class="hljs-type">u64</span>,<br><br>    <span class="hljs-comment">/// 下载文件名</span><br>    <span class="hljs-keyword">pub</span> download_filename: <span class="hljs-type">String</span>,<br><br>    <span class="hljs-comment">/// 下载 SubType</span><br>    <span class="hljs-keyword">pub</span> download_subtype: <span class="hljs-type">String</span>,<br><br>    <span class="hljs-comment">/// 数据偏移（固件内）</span><br>    <span class="hljs-keyword">pub</span> data_offset: <span class="hljs-type">u64</span>,<br><br>    <span class="hljs-comment">/// 数据长度</span><br>    <span class="hljs-keyword">pub</span> data_length: <span class="hljs-type">u64</span>,<br>}<br></code></pre></td></tr></tbody></table></figure><hr><h2 id="FEL-vs-FES-模式对比" data-id="FEL-vs-FES-模式对比" class="notion-h"><a href="#FEL-vs-FES-模式对比" class="headerlink" title="FEL vs FES 模式对比"></a>FEL vs FES 模式对比</h2><h3 id="设备模式识别" data-id="设备模式识别" class="notion-h"><a href="#设备模式识别" class="headerlink" title="设备模式识别"></a>设备模式识别</h3><figure class="highlight rust"><table><tbody><tr><td class="code"><pre><code class="hljs rust"><span class="hljs-comment">// 获取设备模式</span><br><span class="hljs-keyword">let</span> <span class="hljs-variable">mode</span> = ctx.<span class="hljs-title function_ invoke__">get_device_mode</span>();<br><span class="hljs-keyword">let</span> <span class="hljs-variable">has_fel</span> = mode == libefex::DeviceMode::Fel;<br></code></pre></td></tr></tbody></table></figure><h3 id="流程差异" data-id="流程差异" class="notion-h"><a href="#流程差异" class="headerlink" title="流程差异"></a>流程差异</h3><table><thead><tr><th>阶段</th><th>FEL 模式</th><th>FES 模式</th></tr></thead><tbody><tr><td>DRAM 初始化</td><td>需要</td><td>不需要（已在 U-Boot 中）</td></tr><tr><td>U-Boot 下载</td><td>需要</td><td>不需要（已运行）</td></tr><tr><td>设备重连</td><td>需要</td><td>不需要</td></tr><tr><td>设备查询</td><td>需要</td><td>需要</td></tr><tr><td>Flash 擦除</td><td>可选</td><td>可选</td></tr><tr><td>MBR 写入</td><td>需要</td><td>需要</td></tr><tr><td>分区刷写</td><td>需要</td><td>需要</td></tr><tr><td>Boot 写入</td><td>需要</td><td>需要</td></tr><tr><td>设备重启</td><td>需要</td><td>需要</td></tr></tbody></table><hr><h2 id="Flasher-execute-主流程" data-id="Flasher-execute-主流程" class="notion-h"><a href="#Flasher-execute-主流程" class="headerlink" title="Flasher::execute 主流程"></a>Flasher::execute 主流程</h2><h3 id="完整刷写流程" data-id="完整刷写流程" class="notion-h"><a href="#完整刷写流程" class="headerlink" title="完整刷写流程"></a>完整刷写流程</h3><figure class="highlight rust"><table><tbody><tr><td class="code"><pre><code class="hljs rust"><span class="hljs-comment">/// 执行刷写流程</span><br><span class="hljs-keyword">pub</span> <span class="hljs-keyword">async</span> <span class="hljs-keyword">fn</span> <span class="hljs-title function_">execute</span>(&amp;<span class="hljs-keyword">mut</span> <span class="hljs-keyword">self</span>) <span class="hljs-punctuation">-&gt;</span> FlashResult&lt;()&gt; {<br>    <span class="hljs-comment">// 1. 加载 FES 数据（DRAM 初始化脚本）</span><br>    <span class="hljs-keyword">let</span> <span class="hljs-variable">fes_data</span> = <span class="hljs-keyword">self</span>.packer.<span class="hljs-title function_ invoke__">get_fes</span>()?;<br><br>    <span class="hljs-comment">// 2. 初始化 USB 设备</span><br>    <span class="hljs-keyword">let</span> <span class="hljs-keyword">mut </span><span class="hljs-variable">ctx</span> = <span class="hljs-keyword">self</span>.<span class="hljs-title function_ invoke__">init_device</span>()?;<br><br>    <span class="hljs-comment">// 3. 获取设备模式</span><br>    <span class="hljs-keyword">let</span> <span class="hljs-variable">mode</span> = ctx.<span class="hljs-title function_ invoke__">get_device_mode</span>();<br>    <span class="hljs-keyword">let</span> <span class="hljs-variable">has_fel</span> = mode == libefex::DeviceMode::Fel;<br><br>    <span class="hljs-comment">// 4. 根据模式定义进度阶段</span><br>    <span class="hljs-keyword">if</span> has_fel {<br>        <span class="hljs-keyword">let</span> <span class="hljs-variable">stages</span> = FlashStages::for_fel_mode();  <span class="hljs-comment">// 10 个阶段</span><br>        <span class="hljs-keyword">self</span>.logger.<span class="hljs-title function_ invoke__">define_stages</span>(stages.<span class="hljs-title function_ invoke__">stages</span>());<br>    } <span class="hljs-keyword">else</span> {<br>        <span class="hljs-keyword">let</span> <span class="hljs-variable">stages</span> = FlashStages::for_fes_mode();  <span class="hljs-comment">// 7 个阶段</span><br>        <span class="hljs-keyword">self</span>.logger.<span class="hljs-title function_ invoke__">define_stages</span>(stages.<span class="hljs-title function_ invoke__">stages</span>());<br>    }<br><br>    <span class="hljs-keyword">self</span>.logger.<span class="hljs-title function_ invoke__">start_global_progress</span>();<br><br>    <span class="hljs-comment">// 5. 初始化阶段</span><br>    <span class="hljs-keyword">self</span>.logger.<span class="hljs-title function_ invoke__">begin_stage</span>(StageType::Init);<br>    <span class="hljs-keyword">self</span>.logger.<span class="hljs-title function_ invoke__">info</span>(&amp;<span class="hljs-built_in">format!</span>(<span class="hljs-string">"FES data loaded ({} bytes)"</span>, fes_data.<span class="hljs-title function_ invoke__">len</span>()));<br>    <span class="hljs-keyword">self</span>.logger.<span class="hljs-title function_ invoke__">complete_stage</span>();<br><br>    <span class="hljs-comment">// 6. FEL 模式处理（如果需要）</span><br>    <span class="hljs-keyword">if</span> has_fel {<br>        <span class="hljs-comment">// DRAM 初始化</span><br>        <span class="hljs-keyword">self</span>.logger.<span class="hljs-title function_ invoke__">begin_stage</span>(StageType::FelDram);<br>        <span class="hljs-keyword">let</span> <span class="hljs-variable">fel_handler</span> = FelHandler::<span class="hljs-title function_ invoke__">new</span>(&amp;<span class="hljs-keyword">self</span>.logger);<br>        fel_handler.<span class="hljs-title function_ invoke__">handle</span>(&amp;<span class="hljs-keyword">mut</span> ctx, &amp;fes_data).<span class="hljs-keyword">await</span>?;<br>        <span class="hljs-keyword">self</span>.logger.<span class="hljs-title function_ invoke__">complete_stage</span>();<br><br>        <span class="hljs-comment">// U-Boot 下载</span><br>        <span class="hljs-keyword">self</span>.logger.<span class="hljs-title function_ invoke__">begin_stage</span>(StageType::FelUboot);<br>        <span class="hljs-keyword">let</span> <span class="hljs-variable">uboot_data</span> = <span class="hljs-keyword">self</span>.packer.<span class="hljs-title function_ invoke__">get_uboot</span>()?;<br>        <span class="hljs-keyword">let</span> <span class="hljs-variable">dtb_data</span> = <span class="hljs-keyword">self</span>.packer.<span class="hljs-title function_ invoke__">get_dtb</span>().<span class="hljs-title function_ invoke__">ok</span>();<br>        <span class="hljs-keyword">let</span> <span class="hljs-variable">sysconfig_data</span> = <span class="hljs-keyword">self</span>.packer.<span class="hljs-title function_ invoke__">get_sys_config_bin</span>()?;<br>        <span class="hljs-keyword">let</span> <span class="hljs-variable">board_config_data</span> = <span class="hljs-keyword">self</span>.packer.<span class="hljs-title function_ invoke__">get_board_config</span>().<span class="hljs-title function_ invoke__">ok</span>();<br><br>        fel_handler.<span class="hljs-title function_ invoke__">download_uboot</span>(<br>            &amp;ctx,<br>            &amp;uboot_data,<br>            dtb_data.<span class="hljs-title function_ invoke__">as_deref</span>(),<br>            &amp;sysconfig_data,<br>            board_config_data.<span class="hljs-title function_ invoke__">as_deref</span>(),<br>        ).<span class="hljs-keyword">await</span>?;<br>        <span class="hljs-keyword">self</span>.logger.<span class="hljs-title function_ invoke__">complete_stage</span>();<br><br>        <span class="hljs-comment">// 设备重连（等待 FES 模式）</span><br>        <span class="hljs-keyword">self</span>.logger.<span class="hljs-title function_ invoke__">begin_stage</span>(StageType::FelReconnect);<br>        ctx = <span class="hljs-keyword">self</span>.<span class="hljs-title function_ invoke__">reconnect_device</span>().<span class="hljs-keyword">await</span>?;<br>        <span class="hljs-keyword">self</span>.logger.<span class="hljs-title function_ invoke__">complete_stage</span>();<br>    }<br><br>    <span class="hljs-comment">// 7. FES 模式处理</span><br>    <span class="hljs-keyword">let</span> <span class="hljs-keyword">mut </span><span class="hljs-variable">fes_handler</span> = FesHandler::<span class="hljs-title function_ invoke__">new</span>(&amp;<span class="hljs-keyword">mut</span> <span class="hljs-keyword">self</span>.logger);<br>    fes_handler.<span class="hljs-title function_ invoke__">handle</span>(&amp;ctx, &amp;<span class="hljs-keyword">mut</span> <span class="hljs-keyword">self</span>.packer, &amp;<span class="hljs-keyword">self</span>.options).<span class="hljs-keyword">await</span>?;<br><br>    <span class="hljs-comment">// 8. 结束进度追踪</span><br>    <span class="hljs-keyword">self</span>.logger.<span class="hljs-title function_ invoke__">finish_progress</span>();<br><br>    <span class="hljs-comment">// 9. 设置设备模式（重启/关机）</span><br>    <span class="hljs-keyword">self</span>.<span class="hljs-title function_ invoke__">set_device_mode</span>(&amp;ctx).<span class="hljs-keyword">await</span>?;<br><br>    <span class="hljs-title function_ invoke__">Ok</span>(())<br>}<br></code></pre></td></tr></tbody></table></figure><h3 id="流程图" data-id="流程图" class="notion-h"><a href="#流程图" class="headerlink" title="流程图"></a>流程图</h3><pre class="mermaid">flowchart TD    A[开始] --&gt; B[加载 FES 数据]    B --&gt; C[初始化 USB 设备]    C --&gt; D{设备模式?}    D --&gt;|FEL| E[FEL 模式处理]    E --&gt; E1[DRAM 初始化]    E1 --&gt; E2[U-Boot 下载]    E2 --&gt; E3[设备重连]    E3 --&gt; F    D --&gt;|FES| F[FES 模式处理]    F --&gt; F1[设备查询]    F1 --&gt; F2{需要擦除?}    F2 --&gt;|是| F3[Flash 擦除]    F2 --&gt;|否| F4    F3 --&gt; F4[MBR 写入]    F4 --&gt; F5[分区刷写]    F5 --&gt; F6[Boot 写入]    F6 --&gt; G[设置设备模式]    G --&gt; H[结束]</pre><hr><h2 id="FEL-Handler-详解" data-id="FEL-Handler-详解" class="notion-h"><a href="#FEL-Handler-详解" class="headerlink" title="FEL Handler 详解"></a>FEL Handler 详解</h2><h3 id="FelHandler-结构" data-id="FelHandler-结构" class="notion-h"><a href="#FelHandler-结构" class="headerlink" title="FelHandler 结构"></a>FelHandler 结构</h3><figure class="highlight rust"><table><tbody><tr><td class="code"><pre><code class="hljs rust"><span class="hljs-comment">/// FEL 处理器（处理 USB Boot 模式设备）</span><br><span class="hljs-keyword">pub</span> <span class="hljs-keyword">struct</span> <span class="hljs-title class_">FelHandler</span>&lt;<span class="hljs-symbol">'a</span>&gt; {<br>    logger: &amp;<span class="hljs-symbol">'a</span> Logger,<br>}<br><br><span class="hljs-keyword">impl</span>&lt;<span class="hljs-symbol">'a</span>&gt; FelHandler&lt;<span class="hljs-symbol">'a</span>&gt; {<br>    <span class="hljs-keyword">pub</span> <span class="hljs-keyword">fn</span> <span class="hljs-title function_">new</span>(logger: &amp;<span class="hljs-symbol">'a</span> Logger) <span class="hljs-punctuation">-&gt;</span> <span class="hljs-keyword">Self</span> {<br>        <span class="hljs-keyword">Self</span> { logger }<br>    }<br><br>    <span class="hljs-comment">/// 处理 FEL 模式操作</span><br>    <span class="hljs-keyword">pub</span> <span class="hljs-keyword">async</span> <span class="hljs-keyword">fn</span> <span class="hljs-title function_">handle</span>(<br>        &amp;<span class="hljs-keyword">self</span>,<br>        ctx: &amp;<span class="hljs-keyword">mut</span> libefex::Context,<br>        fes_data: &amp;[<span class="hljs-type">u8</span>],<br>    ) <span class="hljs-punctuation">-&gt;</span> FlashResult&lt;()&gt; {<br>        <span class="hljs-keyword">let</span> <span class="hljs-variable">dram_init</span> = DramInit::<span class="hljs-title function_ invoke__">new</span>(<span class="hljs-keyword">self</span>.logger);<br>        dram_init.<span class="hljs-title function_ invoke__">execute</span>(ctx, fes_data).<span class="hljs-keyword">await</span><br>    }<br><br>    <span class="hljs-comment">/// 下载 U-Boot</span><br>    <span class="hljs-keyword">pub</span> <span class="hljs-keyword">async</span> <span class="hljs-keyword">fn</span> <span class="hljs-title function_">download_uboot</span>(<br>        &amp;<span class="hljs-keyword">self</span>,<br>        ctx: &amp;libefex::Context,<br>        uboot_data: &amp;[<span class="hljs-type">u8</span>],<br>        dtb_data: <span class="hljs-type">Option</span>&lt;&amp;[<span class="hljs-type">u8</span>]&gt;,<br>        sysconfig_data: &amp;[<span class="hljs-type">u8</span>],<br>        board_config_data: <span class="hljs-type">Option</span>&lt;&amp;[<span class="hljs-type">u8</span>]&gt;,<br>    ) <span class="hljs-punctuation">-&gt;</span> FlashResult&lt;()&gt; {<br>        <span class="hljs-keyword">let</span> <span class="hljs-variable">uboot_download</span> = UbootDownload::<span class="hljs-title function_ invoke__">new</span>(<span class="hljs-keyword">self</span>.logger);<br>        uboot_download.<span class="hljs-title function_ invoke__">execute</span>(ctx, uboot_data, dtb_data, sysconfig_data, board_config_data).<span class="hljs-keyword">await</span><br>    }<br>}<br></code></pre></td></tr></tbody></table></figure><h3 id="DRAM-初始化流程" data-id="DRAM-初始化流程" class="notion-h"><a href="#DRAM-初始化流程" class="headerlink" title="DRAM 初始化流程"></a>DRAM 初始化流程</h3><p>DRAM 初始化使用 FES 数据（Flash Eraser Script）通过 USB 下载到设备内存：</p><figure class="highlight rust"><table><tbody><tr><td class="code"><pre><code class="hljs rust"><span class="hljs-comment">/// DRAM 初始化器</span><br><span class="hljs-keyword">pub</span> <span class="hljs-keyword">struct</span> <span class="hljs-title class_">DramInit</span>&lt;<span class="hljs-symbol">'a</span>&gt; {<br>    logger: &amp;<span class="hljs-symbol">'a</span> Logger,<br>}<br><br><span class="hljs-keyword">impl</span>&lt;<span class="hljs-symbol">'a</span>&gt; DramInit&lt;<span class="hljs-symbol">'a</span>&gt; {<br>    <span class="hljs-keyword">pub</span> <span class="hljs-keyword">async</span> <span class="hljs-keyword">fn</span> <span class="hljs-title function_">execute</span>(<br>        &amp;<span class="hljs-keyword">self</span>,<br>        ctx: &amp;<span class="hljs-keyword">mut</span> libefex::Context,<br>        fes_data: &amp;[<span class="hljs-type">u8</span>],<br>    ) <span class="hljs-punctuation">-&gt;</span> FlashResult&lt;()&gt; {<br>        <span class="hljs-comment">// 1. 解析 Boot0 头</span><br>        <span class="hljs-keyword">let</span> <span class="hljs-variable">boot0_header</span> = Boot0Header::<span class="hljs-title function_ invoke__">parse</span>(fes_data)?;<br><br>        <span class="hljs-comment">// 2. 验证魔数</span><br>        <span class="hljs-keyword">if</span> boot0_header.<span class="hljs-title function_ invoke__">magic_str</span>() != BOOT0_MAGIC {<br>            <span class="hljs-keyword">return</span> <span class="hljs-title function_ invoke__">Err</span>(FlashError::DramInitFailed);<br>        }<br><br>        <span class="hljs-comment">// 3. 获取运行地址</span><br>        <span class="hljs-keyword">let</span> <span class="hljs-variable">run_addr</span> = boot0_header.run_addr;<br><br>        <span class="hljs-comment">// 4. 下载 FES 到指定地址</span><br>        ctx.<span class="hljs-title function_ invoke__">fel_download</span>(fes_data, run_addr)?;<br><br>        <span class="hljs-comment">// 5. 执行 FES（初始化 DRAM）</span><br>        ctx.<span class="hljs-title function_ invoke__">fel_run</span>(run_addr)?;<br><br>        <span class="hljs-comment">// 6. 等待 DRAM 初始化完成</span><br>        <span class="hljs-keyword">self</span>.<span class="hljs-title function_ invoke__">wait_for_dram_init</span>(ctx).<span class="hljs-keyword">await</span>?;<br><br>        <span class="hljs-title function_ invoke__">Ok</span>(())<br>    }<br>}<br></code></pre></td></tr></tbody></table></figure><h3 id="U-Boot-下载流程" data-id="U-Boot-下载流程" class="notion-h"><a href="#U-Boot-下载流程" class="headerlink" title="U-Boot 下载流程"></a>U-Boot 下载流程</h3><figure class="highlight rust"><table><tbody><tr><td class="code"><pre><code class="hljs rust"><span class="hljs-comment">/// U-Boot 下载器</span><br><span class="hljs-keyword">pub</span> <span class="hljs-keyword">struct</span> <span class="hljs-title class_">UbootDownload</span>&lt;<span class="hljs-symbol">'a</span>&gt; {<br>    logger: &amp;<span class="hljs-symbol">'a</span> Logger,<br>}<br><br><span class="hljs-keyword">impl</span>&lt;<span class="hljs-symbol">'a</span>&gt; UbootDownload&lt;<span class="hljs-symbol">'a</span>&gt; {<br>    <span class="hljs-keyword">pub</span> <span class="hljs-keyword">async</span> <span class="hljs-keyword">fn</span> <span class="hljs-title function_">execute</span>(<br>        &amp;<span class="hljs-keyword">self</span>,<br>        ctx: &amp;libefex::Context,<br>        uboot_data: &amp;[<span class="hljs-type">u8</span>],<br>        dtb_data: <span class="hljs-type">Option</span>&lt;&amp;[<span class="hljs-type">u8</span>]&gt;,<br>        sysconfig_data: &amp;[<span class="hljs-type">u8</span>],<br>        board_config_data: <span class="hljs-type">Option</span>&lt;&amp;[<span class="hljs-type">u8</span>]&gt;,<br>    ) <span class="hljs-punctuation">-&gt;</span> FlashResult&lt;()&gt; {<br>        <span class="hljs-comment">// 1. 解析 U-Boot 头</span><br>        <span class="hljs-keyword">let</span> <span class="hljs-variable">uboot_header</span> = UBootHeader::<span class="hljs-title function_ invoke__">parse</span>(uboot_data)?;<br><br>        <span class="hljs-comment">// 2. 设置工作模式为 USB 产品模式</span><br>        UBootHeader::<span class="hljs-title function_ invoke__">set_work_mode</span>(uboot_data, WORK_MODE_USB_PRODUCT);<br><br>        <span class="hljs-comment">// 3. 计算下载地址</span><br>        <span class="hljs-keyword">let</span> <span class="hljs-variable">run_addr</span> = uboot_header.uboot_head.run_addr;<br>        <span class="hljs-keyword">let</span> <span class="hljs-variable">uboot_length</span> = uboot_header.uboot_head.uboot_length;<br><br>        <span class="hljs-comment">// 4. 下载 U-Boot</span><br>        ctx.<span class="hljs-title function_ invoke__">fel_download</span>(uboot_data, run_addr)?;<br><br>        <span class="hljs-comment">// 5. 下载 DTB（如果有）</span><br>        <span class="hljs-keyword">if</span> <span class="hljs-keyword">let</span> <span class="hljs-variable">Some</span>(dtb) = dtb_data {<br>            <span class="hljs-keyword">let</span> <span class="hljs-variable">dtb_addr</span> = run_addr + uboot_length;<br>            ctx.<span class="hljs-title function_ invoke__">fel_download</span>(dtb, dtb_addr)?;<br>        }<br><br>        <span class="hljs-comment">// 6. 下载 sys_config</span><br>        <span class="hljs-keyword">let</span> <span class="hljs-variable">sysconfig_addr</span> = run_addr + uboot_length + dtb_data.<span class="hljs-title function_ invoke__">len</span>();<br>        ctx.<span class="hljs-title function_ invoke__">fel_download</span>(sysconfig_data, sysconfig_addr)?;<br><br>        <span class="hljs-comment">// 7. 下载 board_config（如果有）</span><br>        <span class="hljs-keyword">if</span> <span class="hljs-keyword">let</span> <span class="hljs-variable">Some</span>(board_config) = board_config_data {<br>            <span class="hljs-keyword">let</span> <span class="hljs-variable">board_addr</span> = sysconfig_addr + sysconfig_data.<span class="hljs-title function_ invoke__">len</span>();<br>            ctx.<span class="hljs-title function_ invoke__">fel_download</span>(board_config, board_addr)?;<br>        }<br><br>        <span class="hljs-comment">// 8. 执行 U-Boot</span><br>        ctx.<span class="hljs-title function_ invoke__">fel_run</span>(run_addr)?;<br><br>        <span class="hljs-title function_ invoke__">Ok</span>(())<br>    }<br>}<br></code></pre></td></tr></tbody></table></figure><h3 id="内存布局" data-id="内存布局" class="notion-h"><a href="#内存布局" class="headerlink" title="内存布局"></a>内存布局</h3><figure class="highlight plaintext"><table><tbody><tr><td class="code"><pre><code class="hljs plaintext">┌─────────────────────────────────────────────────────────────┐<br>│                    设备内存布局                               │<br>├─────────────────────────────────────────────────────────────┤<br>│ run_addr                                                    │<br>│ ┌─────────────────────────────────────────────────────────┐ │<br>│ │              U-Boot (uboot_length bytes)                │ │<br>│ └─────────────────────────────────────────────────────────┘ │<br>│ run_addr + uboot_length                                     │<br>│ ┌─────────────────────────────────────────────────────────┐ │<br>│ │              DTB (variable size)                        │ │<br>│ └─────────────────────────────────────────────────────────┘ │<br>│ run_addr + uboot_length + dtb_len                           │<br>│ ┌─────────────────────────────────────────────────────────┐ │<br>│ │              sys_config_bin (variable size)              │ │<br>│ └─────────────────────────────────────────────────────────┘ │<br>│ run_addr + uboot_length + dtb_len + sysconfig_len            │<br>│ ┌─────────────────────────────────────────────────────────┐ │<br>│ │              board_config (optional)                     │ │<br>│ └─────────────────────────────────────────────────────────┘ │<br>└─────────────────────────────────────────────────────────────┘<br></code></pre></td></tr></tbody></table></figure><h3 id="设备重连机制" data-id="设备重连机制" class="notion-h"><a href="#设备重连机制" class="headerlink" title="设备重连机制"></a>设备重连机制</h3><figure class="highlight rust"><table><tbody><tr><td class="code"><pre><code class="hljs rust"><span class="hljs-comment">/// 重连设备（等待 FES 模式）</span><br><span class="hljs-keyword">async</span> <span class="hljs-keyword">fn</span> <span class="hljs-title function_">reconnect_device</span>(&amp;<span class="hljs-keyword">self</span>) <span class="hljs-punctuation">-&gt;</span> FlashResult&lt;libefex::Context&gt; {<br>    <span class="hljs-comment">// 等待 2 秒让设备启动</span><br>    tokio::time::<span class="hljs-title function_ invoke__">sleep</span>(Duration::<span class="hljs-title function_ invoke__">from_secs</span>(<span class="hljs-number">2</span>)).<span class="hljs-keyword">await</span>;<br><br>    <span class="hljs-keyword">let</span> <span class="hljs-variable">max_retries</span> = <span class="hljs-number">25</span>;  <span class="hljs-comment">// 最多尝试 25 次</span><br>    <span class="hljs-keyword">let</span> <span class="hljs-keyword">mut </span><span class="hljs-variable">retries</span> = <span class="hljs-number">0</span>;<br><br>    <span class="hljs-keyword">while</span> retries &lt; max_retries {<br>        tokio::time::<span class="hljs-title function_ invoke__">sleep</span>(Duration::<span class="hljs-title function_ invoke__">from_secs</span>(<span class="hljs-number">1</span>)).<span class="hljs-keyword">await</span>;<br><br>        <span class="hljs-comment">// 扫描 USB 设备</span><br>        <span class="hljs-keyword">let</span> <span class="hljs-variable">devices</span> = libefex::Context::<span class="hljs-title function_ invoke__">scan_usb_devices</span>()?;<br><br>        <span class="hljs-keyword">for</span> <span class="hljs-variable">dev</span> <span class="hljs-keyword">in</span> devices {<br>            <span class="hljs-keyword">let</span> <span class="hljs-keyword">mut </span><span class="hljs-variable">new_ctx</span> = libefex::Context::<span class="hljs-title function_ invoke__">new</span>();<br>            <span class="hljs-keyword">if</span> new_ctx.<span class="hljs-title function_ invoke__">scan_usb_device_at</span>(dev.bus, dev.port).<span class="hljs-title function_ invoke__">is_err</span>() {<br>                <span class="hljs-keyword">continue</span>;<br>            }<br>            <span class="hljs-keyword">if</span> new_ctx.<span class="hljs-title function_ invoke__">usb_init</span>().<span class="hljs-title function_ invoke__">is_err</span>() {<br>                <span class="hljs-keyword">continue</span>;<br>            }<br>            <span class="hljs-keyword">if</span> new_ctx.<span class="hljs-title function_ invoke__">efex_init</span>().<span class="hljs-title function_ invoke__">is_err</span>() {<br>                <span class="hljs-keyword">continue</span>;<br>            }<br><br>            <span class="hljs-comment">// 检查是否为 FES/Srv 模式</span><br>            <span class="hljs-keyword">if</span> new_ctx.<span class="hljs-title function_ invoke__">get_device_mode</span>() == libefex::DeviceMode::Srv {<br>                <span class="hljs-keyword">self</span>.logger.<span class="hljs-title function_ invoke__">debug</span>(&amp;<span class="hljs-built_in">format!</span>(<br>                    <span class="hljs-string">"Device found at bus {}, port {}"</span>,<br>                    dev.bus, dev.port<br>                ));<br>                <span class="hljs-keyword">return</span> <span class="hljs-title function_ invoke__">Ok</span>(new_ctx);<br>            }<br>        }<br><br>        retries += <span class="hljs-number">1</span>;<br>        <span class="hljs-keyword">self</span>.logger.<span class="hljs-title function_ invoke__">debug</span>(&amp;<span class="hljs-built_in">format!</span>(<span class="hljs-string">"Reconnect attempt {}/{}"</span>, retries, max_retries));<br>    }<br><br>    <span class="hljs-title function_ invoke__">Err</span>(FlashError::ReconnectFailed)<br>}<br></code></pre></td></tr></tbody></table></figure><hr><h2 id="FES-Handler-详解" data-id="FES-Handler-详解" class="notion-h"><a href="#FES-Handler-详解" class="headerlink" title="FES Handler 详解"></a>FES Handler 详解</h2><h3 id="FesHandler-结构" data-id="FesHandler-结构" class="notion-h"><a href="#FesHandler-结构" class="headerlink" title="FesHandler 结构"></a>FesHandler 结构</h3><figure class="highlight rust"><table><tbody><tr><td class="code"><pre><code class="hljs rust"><span class="hljs-comment">/// FES 处理器（处理 U-Boot 模式设备）</span><br><span class="hljs-keyword">pub</span> <span class="hljs-keyword">struct</span> <span class="hljs-title class_">FesHandler</span>&lt;<span class="hljs-symbol">'a</span>&gt; {<br>    logger: &amp;<span class="hljs-symbol">'a</span> <span class="hljs-keyword">mut</span> Logger,<br>}<br><br><span class="hljs-keyword">impl</span>&lt;<span class="hljs-symbol">'a</span>&gt; FesHandler&lt;<span class="hljs-symbol">'a</span>&gt; {<br>    <span class="hljs-keyword">pub</span> <span class="hljs-keyword">fn</span> <span class="hljs-title function_">new</span>(logger: &amp;<span class="hljs-symbol">'a</span> <span class="hljs-keyword">mut</span> Logger) <span class="hljs-punctuation">-&gt;</span> <span class="hljs-keyword">Self</span> {<br>        <span class="hljs-keyword">Self</span> { logger }<br>    }<br><br>    <span class="hljs-comment">/// 处理 FES 模式操作</span><br>    <span class="hljs-keyword">pub</span> <span class="hljs-keyword">async</span> <span class="hljs-keyword">fn</span> <span class="hljs-title function_">handle</span>(<br>        &amp;<span class="hljs-keyword">mut</span> <span class="hljs-keyword">self</span>,<br>        ctx: &amp;libefex::Context,<br>        packer: &amp;<span class="hljs-keyword">mut</span> OpenixPacker,<br>        options: &amp;FlashOptions,<br>    ) <span class="hljs-punctuation">-&gt;</span> FlashResult&lt;()&gt; {<br>        <span class="hljs-comment">// 1. 设备查询</span><br>        <span class="hljs-keyword">self</span>.logger.<span class="hljs-title function_ invoke__">begin_stage</span>(StageType::FesQuery);<br>        <span class="hljs-keyword">let</span> <span class="hljs-variable">secure</span> = ctx.<span class="hljs-title function_ invoke__">fes_query_secure</span>()?;<br>        <span class="hljs-keyword">let</span> <span class="hljs-variable">storage_type</span> = ctx.<span class="hljs-title function_ invoke__">fes_query_storage</span>()?;<br>        <span class="hljs-keyword">let</span> <span class="hljs-variable">flash_size</span> = ctx.<span class="hljs-title function_ invoke__">fes_probe_flash_size</span>()?;<br>        <span class="hljs-keyword">self</span>.logger.<span class="hljs-title function_ invoke__">complete_stage</span>();<br><br>        <span class="hljs-comment">// 2. Flash 擦除（如果需要）</span><br>        <span class="hljs-keyword">if</span> options.mode != FlashMode::Partition {<br>            <span class="hljs-keyword">self</span>.logger.<span class="hljs-title function_ invoke__">begin_stage</span>(StageType::FesErase);<br>            <span class="hljs-keyword">let</span> <span class="hljs-variable">erase_flag</span> = EraseFlag::<span class="hljs-title function_ invoke__">new</span>(&amp;*<span class="hljs-keyword">self</span>.logger);<br>            erase_flag.<span class="hljs-title function_ invoke__">execute</span>(ctx, options.mode).<span class="hljs-keyword">await</span>?;<br>            <span class="hljs-keyword">self</span>.logger.<span class="hljs-title function_ invoke__">complete_stage</span>();<br>        }<br><br>        <span class="hljs-comment">// 3. MBR 写入</span><br>        <span class="hljs-keyword">self</span>.logger.<span class="hljs-title function_ invoke__">begin_stage</span>(StageType::FesMbr);<br>        <span class="hljs-keyword">let</span> <span class="hljs-variable">mbr_data</span> = packer.<span class="hljs-title function_ invoke__">get_mbr</span>()?;<br>        <span class="hljs-keyword">let</span> <span class="hljs-variable">mbr</span> = SunxiMbr::<span class="hljs-title function_ invoke__">parse</span>(&amp;mbr_data)?;<br>        <span class="hljs-keyword">let</span> <span class="hljs-variable">download_list</span> = <span class="hljs-keyword">self</span>.<span class="hljs-title function_ invoke__">prepare_partition_download_list</span>(packer, &amp;mbr_info, options)?;<br><br>        <span class="hljs-comment">// UBIFS 配置</span><br>        <span class="hljs-keyword">let</span> <span class="hljs-variable">ubifs_config</span> = UbifsConfig::<span class="hljs-title function_ invoke__">new</span>(&amp;*<span class="hljs-keyword">self</span>.logger);<br>        ubifs_config.<span class="hljs-title function_ invoke__">execute</span>(ctx, packer, &amp;download_list, storage_type)?;<br><br>        <span class="hljs-comment">// MBR 下载</span><br>        <span class="hljs-keyword">let</span> <span class="hljs-variable">mbr_download</span> = MbrDownload::<span class="hljs-title function_ invoke__">new</span>(&amp;*<span class="hljs-keyword">self</span>.logger);<br>        mbr_download.<span class="hljs-title function_ invoke__">execute</span>(ctx, &amp;mbr_data).<span class="hljs-keyword">await</span>?;<br>        <span class="hljs-keyword">self</span>.logger.<span class="hljs-title function_ invoke__">complete_stage</span>();<br><br>        <span class="hljs-comment">// 4. 分区刷写</span><br>        <span class="hljs-keyword">if</span> !download_list.<span class="hljs-title function_ invoke__">is_empty</span>() {<br>            <span class="hljs-keyword">self</span>.logger.<span class="hljs-title function_ invoke__">begin_stage</span>(StageType::FesPartitions);<br>            <span class="hljs-keyword">let</span> <span class="hljs-variable">total_bytes</span>: <span class="hljs-type">u64</span> = download_list.<span class="hljs-title function_ invoke__">iter</span>().<span class="hljs-title function_ invoke__">map</span>(|p| p.data_length).<span class="hljs-title function_ invoke__">sum</span>();<br>            <span class="hljs-keyword">self</span>.logger.<span class="hljs-title function_ invoke__">set_partition_stage_weight</span>(total_bytes);<br><br>            <span class="hljs-keyword">let</span> <span class="hljs-keyword">mut </span><span class="hljs-variable">partition_download</span> = PartitionDownload::<span class="hljs-title function_ invoke__">new</span>(&amp;<span class="hljs-keyword">mut</span> *<span class="hljs-keyword">self</span>.logger);<br>            partition_download.<span class="hljs-title function_ invoke__">execute</span>(ctx, packer, &amp;download_list, options.verify).<span class="hljs-keyword">await</span>?;<br>            <span class="hljs-keyword">self</span>.logger.<span class="hljs-title function_ invoke__">complete_stage</span>();<br><br>            <span class="hljs-comment">// 5. Boot 写入</span><br>            <span class="hljs-keyword">self</span>.logger.<span class="hljs-title function_ invoke__">begin_stage</span>(StageType::FesBoot);<br>            <span class="hljs-keyword">let</span> <span class="hljs-variable">boot_download</span> = BootDownload::<span class="hljs-title function_ invoke__">new</span>(&amp;*<span class="hljs-keyword">self</span>.logger);<br>            boot_download.<span class="hljs-title function_ invoke__">execute</span>(ctx, packer, secure, storage_type).<span class="hljs-keyword">await</span>?;<br>            <span class="hljs-keyword">self</span>.logger.<span class="hljs-title function_ invoke__">complete_stage</span>();<br>        }<br><br>        <span class="hljs-title function_ invoke__">Ok</span>(())<br>    }<br>}<br></code></pre></td></tr></tbody></table></figure><h3 id="分区下载列表准备" data-id="分区下载列表准备" class="notion-h"><a href="#分区下载列表准备" class="headerlink" title="分区下载列表准备"></a>分区下载列表准备</h3><figure class="highlight rust"><table><tbody><tr><td class="code"><pre><code class="hljs rust"><span class="hljs-comment">/// 准备分区下载列表</span><br><span class="hljs-keyword">fn</span> <span class="hljs-title function_">prepare_partition_download_list</span>(<br>    &amp;<span class="hljs-keyword">self</span>,<br>    packer: &amp;<span class="hljs-keyword">mut</span> OpenixPacker,<br>    mbr_info: &amp;MbrInfo,<br>    options: &amp;FlashOptions,<br>) <span class="hljs-punctuation">-&gt;</span> FlashResult&lt;<span class="hljs-type">Vec</span>&lt;PartitionDownloadInfo&gt;&gt; {<br>    <span class="hljs-comment">// 解析 sys_partition 配置</span><br>    <span class="hljs-keyword">let</span> <span class="hljs-keyword">mut </span><span class="hljs-variable">partition_parser</span> = OpenixPartition::<span class="hljs-title function_ invoke__">new</span>();<br>    <span class="hljs-keyword">if</span> <span class="hljs-keyword">let</span> <span class="hljs-variable">Ok</span>(data) = packer.<span class="hljs-title function_ invoke__">get_sys_partition</span>() {<br>        partition_parser.<span class="hljs-title function_ invoke__">parse_from_data</span>(&amp;data);<br>    }<br><br>    <span class="hljs-keyword">let</span> <span class="hljs-keyword">mut </span><span class="hljs-variable">download_list</span> = <span class="hljs-type">Vec</span>::<span class="hljs-title function_ invoke__">new</span>();<br><br>    <span class="hljs-keyword">for</span> <span class="hljs-variable">mbr_partition</span> <span class="hljs-keyword">in</span> &amp;mbr_info.partitions {<br>        <span class="hljs-keyword">let</span> <span class="hljs-variable">partition_name</span> = &amp;mbr_partition.name;<br><br>        <span class="hljs-comment">// KeepData 模式：跳过用户数据分区</span><br>        <span class="hljs-keyword">if</span> options.mode == FlashMode::KeepData {<br>            <span class="hljs-keyword">let</span> <span class="hljs-variable">name_lower</span> = partition_name.<span class="hljs-title function_ invoke__">to_lowercase</span>();<br>            <span class="hljs-keyword">if</span> name_lower == <span class="hljs-string">"udisk"</span> || name_lower == <span class="hljs-string">"private"</span> || name_lower == <span class="hljs-string">"reserve"</span> {<br>                <span class="hljs-keyword">self</span>.logger.<span class="hljs-title function_ invoke__">info</span>(&amp;<span class="hljs-built_in">format!</span>(<span class="hljs-string">"Skipping user data partition: {}"</span>, partition_name));<br>                <span class="hljs-keyword">continue</span>;<br>            }<br>        }<br><br>        <span class="hljs-comment">// Partition 模式：仅刷写指定分区</span><br>        <span class="hljs-keyword">if</span> options.mode == FlashMode::Partition {<br>            <span class="hljs-keyword">if</span> <span class="hljs-keyword">let</span> <span class="hljs-variable">Some</span>(<span class="hljs-keyword">ref</span> partitions) = options.partitions {<br>                <span class="hljs-keyword">if</span> !partitions.<span class="hljs-title function_ invoke__">iter</span>().<span class="hljs-title function_ invoke__">any</span>(|p| p == partition_name) {<br>                    <span class="hljs-keyword">continue</span>;<br>                }<br>            }<br>        }<br><br>        <span class="hljs-comment">// 查找下载文件</span><br>        <span class="hljs-keyword">let</span> <span class="hljs-variable">config_partition</span> = config_partitions.<span class="hljs-title function_ invoke__">iter</span>().<span class="hljs-title function_ invoke__">find</span>(|p| p.name == *partition_name);<br>        <span class="hljs-keyword">let</span> <span class="hljs-variable">download_filename</span> = config_partition?.downloadfile.<span class="hljs-title function_ invoke__">clone</span>();<br><br>        <span class="hljs-comment">// 构建 SubType</span><br>        <span class="hljs-keyword">let</span> <span class="hljs-variable">download_subtype</span> = packer.<span class="hljs-title function_ invoke__">build_subtype_by_filename</span>(&amp;download_filename);<br><br>        <span class="hljs-comment">// 查找固件中的数据位置</span><br>        <span class="hljs-keyword">let</span> <span class="hljs-variable">data_info</span> = packer<br>            .<span class="hljs-title function_ invoke__">get_file_info_by_maintype_subtype</span>(<span class="hljs-string">"ITEM_ROOTFSFAT16"</span>, &amp;download_subtype)<br>            .<span class="hljs-title function_ invoke__">or_else</span>(|| packer.<span class="hljs-title function_ invoke__">get_file_info_by_maintype_subtype</span>(<span class="hljs-string">"12345678"</span>, &amp;download_subtype))<br>            .<span class="hljs-title function_ invoke__">or_else</span>(|| packer.<span class="hljs-title function_ invoke__">get_file_info_by_filename</span>(&amp;download_filename));<br><br>        <span class="hljs-keyword">if</span> <span class="hljs-keyword">let</span> <span class="hljs-variable">Some</span>((offset, length)) = data_info {<br>            download_list.<span class="hljs-title function_ invoke__">push</span>(PartitionDownloadInfo {<br>                partition_name: partition_name.<span class="hljs-title function_ invoke__">clone</span>(),<br>                partition_address: mbr_partition.<span class="hljs-title function_ invoke__">address</span>(),<br>                download_filename,<br>                download_subtype,<br>                data_offset: offset,<br>                data_length: length,<br>            });<br>        }<br>    }<br><br>    <span class="hljs-title function_ invoke__">Ok</span>(download_list)<br>}<br></code></pre></td></tr></tbody></table></figure><hr><h2 id="分区刷写流程" data-id="分区刷写流程" class="notion-h"><a href="#分区刷写流程" class="headerlink" title="分区刷写流程"></a>分区刷写流程</h2><h3 id="Raw-vs-Sparse-格式检测" data-id="Raw-vs-Sparse-格式检测" class="notion-h"><a href="#Raw-vs-Sparse-格式检测" class="headerlink" title="Raw vs Sparse 格式检测"></a>Raw vs Sparse 格式检测</h3><figure class="highlight rust"><table><tbody><tr><td class="code"><pre><code class="hljs rust"><span class="hljs-comment">/// 分区下载器</span><br><span class="hljs-keyword">pub</span> <span class="hljs-keyword">struct</span> <span class="hljs-title class_">PartitionDownload</span>&lt;<span class="hljs-symbol">'a</span>&gt; {<br>    logger: &amp;<span class="hljs-symbol">'a</span> <span class="hljs-keyword">mut</span> Logger,<br>}<br><br><span class="hljs-keyword">impl</span>&lt;<span class="hljs-symbol">'a</span>&gt; PartitionDownload&lt;<span class="hljs-symbol">'a</span>&gt; {<br>    <span class="hljs-keyword">pub</span> <span class="hljs-keyword">async</span> <span class="hljs-keyword">fn</span> <span class="hljs-title function_">execute</span>(<br>        &amp;<span class="hljs-keyword">mut</span> <span class="hljs-keyword">self</span>,<br>        ctx: &amp;libefex::Context,<br>        packer: &amp;<span class="hljs-keyword">mut</span> OpenixPacker,<br>        download_list: &amp;[PartitionDownloadInfo],<br>        verify: <span class="hljs-type">bool</span>,<br>    ) <span class="hljs-punctuation">-&gt;</span> FlashResult&lt;()&gt; {<br>        <span class="hljs-keyword">for</span> <span class="hljs-variable">partition_info</span> <span class="hljs-keyword">in</span> download_list {<br>            <span class="hljs-keyword">self</span>.logger.<span class="hljs-title function_ invoke__">set_current_partition</span>(&amp;partition_info.partition_name);<br><br>            <span class="hljs-comment">// 获取分区数据</span><br>            <span class="hljs-keyword">let</span> <span class="hljs-variable">partition_data</span> = packer.<span class="hljs-title function_ invoke__">read_data_at_offset</span>(<br>                partition_info.data_offset <span class="hljs-keyword">as</span> <span class="hljs-type">u32</span>,<br>                partition_info.data_length <span class="hljs-keyword">as</span> <span class="hljs-type">u32</span>,<br>            )?;<br><br>            <span class="hljs-comment">// 检测格式</span><br>            <span class="hljs-keyword">if</span> <span class="hljs-title function_ invoke__">is_sparse_format</span>(&amp;partition_data) {<br>                <span class="hljs-comment">// Sparse 格式：解析后逐块下载</span><br>                <span class="hljs-keyword">self</span>.<span class="hljs-title function_ invoke__">download_sparse</span>(ctx, &amp;partition_info, &amp;partition_data, verify).<span class="hljs-keyword">await</span>?;<br>            } <span class="hljs-keyword">else</span> {<br>                <span class="hljs-comment">// Raw 格式：直接下载</span><br>                <span class="hljs-keyword">self</span>.<span class="hljs-title function_ invoke__">download_raw</span>(ctx, &amp;partition_info, &amp;partition_data, verify).<span class="hljs-keyword">await</span>?;<br>            }<br>        }<br><br>        <span class="hljs-title function_ invoke__">Ok</span>(())<br>    }<br>}<br></code></pre></td></tr></tbody></table></figure><h3 id="Raw-格式下载" data-id="Raw-格式下载" class="notion-h"><a href="#Raw-格式下载" class="headerlink" title="Raw 格式下载"></a>Raw 格式下载</h3><figure class="highlight rust"><table><tbody><tr><td class="code"><pre><code class="hljs rust"><span class="hljs-comment">/// 下载 Raw 格式分区</span><br><span class="hljs-keyword">async</span> <span class="hljs-keyword">fn</span> <span class="hljs-title function_">download_raw</span>(<br>    &amp;<span class="hljs-keyword">self</span>,<br>    ctx: &amp;libefex::Context,<br>    partition_info: &amp;PartitionDownloadInfo,<br>    data: &amp;[<span class="hljs-type">u8</span>],<br>    verify: <span class="hljs-type">bool</span>,<br>) <span class="hljs-punctuation">-&gt;</span> FlashResult&lt;()&gt; {<br>    <span class="hljs-keyword">let</span> <span class="hljs-variable">address</span> = partition_info.partition_address;<br>    <span class="hljs-keyword">let</span> <span class="hljs-variable">length</span> = data.<span class="hljs-title function_ invoke__">len</span>();<br><br>    <span class="hljs-comment">// 分块下载（每次 8KB 或更大）</span><br>    <span class="hljs-keyword">let</span> <span class="hljs-variable">chunk_size</span> = <span class="hljs-number">8</span> * <span class="hljs-number">1024</span>;<br>    <span class="hljs-keyword">let</span> <span class="hljs-keyword">mut </span><span class="hljs-variable">offset</span> = <span class="hljs-number">0</span>;<br><br>    <span class="hljs-keyword">while</span> offset &lt; length {<br>        <span class="hljs-keyword">let</span> <span class="hljs-variable">chunk_end</span> = (offset + chunk_size).<span class="hljs-title function_ invoke__">min</span>(length);<br>        <span class="hljs-keyword">let</span> <span class="hljs-variable">chunk</span> = &amp;data[offset..chunk_end];<br><br>        <span class="hljs-comment">// 写入数据</span><br>        ctx.<span class="hljs-title function_ invoke__">fes_write</span>(address + offset <span class="hljs-keyword">as</span> <span class="hljs-type">u64</span>, chunk)?;<br><br>        <span class="hljs-comment">// 更新进度</span><br>        <span class="hljs-keyword">self</span>.logger.<span class="hljs-title function_ invoke__">update_progress_with_speed</span>(chunk_end <span class="hljs-keyword">as</span> <span class="hljs-type">u64</span>);<br><br>        offset = chunk_end;<br>    }<br><br>    <span class="hljs-comment">// 验证（如果启用）</span><br>    <span class="hljs-keyword">if</span> verify {<br>        <span class="hljs-keyword">self</span>.<span class="hljs-title function_ invoke__">verify_partition</span>(ctx, partition_info, data)?;<br>    }<br><br>    <span class="hljs-title function_ invoke__">Ok</span>(())<br>}<br></code></pre></td></tr></tbody></table></figure><h3 id="Sparse-格式下载" data-id="Sparse-格式下载" class="notion-h"><a href="#Sparse-格式下载" class="headerlink" title="Sparse 格式下载"></a>Sparse 格式下载</h3><figure class="highlight rust"><table><tbody><tr><td class="code"><pre><code class="hljs rust"><span class="hljs-comment">/// 下载 Sparse 格式分区</span><br><span class="hljs-keyword">async</span> <span class="hljs-keyword">fn</span> <span class="hljs-title function_">download_sparse</span>(<br>    &amp;<span class="hljs-keyword">self</span>,<br>    ctx: &amp;libefex::Context,<br>    partition_info: &amp;PartitionDownloadInfo,<br>    data: &amp;[<span class="hljs-type">u8</span>],<br>    verify: <span class="hljs-type">bool</span>,<br>) <span class="hljs-punctuation">-&gt;</span> FlashResult&lt;()&gt; {<br>    <span class="hljs-keyword">let</span> <span class="hljs-variable">header</span> = SparseHeader::<span class="hljs-title function_ invoke__">parse</span>(data)?;<br>    <span class="hljs-keyword">let</span> <span class="hljs-variable">base_address</span> = partition_info.partition_address;<br><br>    <span class="hljs-keyword">let</span> <span class="hljs-keyword">mut </span><span class="hljs-variable">chunk_offset</span> = SPARSE_HEADER_SIZE;<br>    <span class="hljs-keyword">let</span> <span class="hljs-keyword">mut </span><span class="hljs-variable">output_offset</span> = <span class="hljs-number">0u64</span>;<br><br>    <span class="hljs-keyword">for</span> <span class="hljs-variable">_</span> <span class="hljs-keyword">in</span> <span class="hljs-number">0</span>..header.total_chunks {<br>        <span class="hljs-keyword">let</span> <span class="hljs-variable">chunk_header</span> = ChunkHeader::<span class="hljs-title function_ invoke__">parse</span>(&amp;data[chunk_offset..])?;<br><br>        <span class="hljs-keyword">match</span> chunk_header.chunk_type {<br>            CHUNK_TYPE_RAW =&gt; {<br>                <span class="hljs-comment">// 原始数据块：直接写入</span><br>                <span class="hljs-keyword">let</span> <span class="hljs-variable">chunk_data</span> = &amp;data[chunk_offset + CHUNK_HEADER_SIZE..];<br>                <span class="hljs-keyword">let</span> <span class="hljs-variable">write_address</span> = base_address + output_offset * SECTOR_SIZE;<br><br>                ctx.<span class="hljs-title function_ invoke__">fes_write</span>(write_address, chunk_data)?;<br>                <span class="hljs-keyword">self</span>.logger.<span class="hljs-title function_ invoke__">update_progress_with_speed</span>(output_offset);<br>            }<br><br>            CHUNK_TYPE_FILL =&gt; {<br>                <span class="hljs-comment">// 填充块：写入重复值</span><br>                <span class="hljs-keyword">let</span> <span class="hljs-variable">fill_value</span> = <span class="hljs-type">u32</span>::<span class="hljs-title function_ invoke__">from_le_bytes</span>([<br>                    data[chunk_offset + CHUNK_HEADER_SIZE],<br>                    data[chunk_offset + CHUNK_HEADER_SIZE + <span class="hljs-number">1</span>],<br>                    data[chunk_offset + CHUNK_HEADER_SIZE + <span class="hljs-number">2</span>],<br>                    data[chunk_offset + CHUNK_HEADER_SIZE + <span class="hljs-number">3</span>],<br>                ]);<br><br>                ctx.<span class="hljs-title function_ invoke__">fes_write_fill</span>(base_address + output_offset * SECTOR_SIZE, fill_value, chunk_header.chunk_sz)?;<br>            }<br><br>            CHUNK_TYPE_DONT_CARE =&gt; {<br>                <span class="hljs-comment">// 空块：跳过</span><br>            }<br><br>            CHUNK_TYPE_CRC32 =&gt; {<br>                <span class="hljs-comment">// CRC32 块：验证</span><br>            }<br>        }<br><br>        output_offset += chunk_header.chunk_sz <span class="hljs-keyword">as</span> <span class="hljs-type">u64</span>;<br>        chunk_offset += chunk_header.total_sz <span class="hljs-keyword">as</span> <span class="hljs-type">usize</span>;<br>    }<br><br>    <span class="hljs-title function_ invoke__">Ok</span>(())<br>}<br></code></pre></td></tr></tbody></table></figure><hr><h2 id="Boot-写入流程" data-id="Boot-写入流程" class="notion-h"><a href="#Boot-写入流程" class="headerlink" title="Boot 写入流程"></a>Boot 写入流程</h2><figure class="highlight rust"><table><tbody><tr><td class="code"><pre><code class="hljs rust"><span class="hljs-comment">/// Boot 镜像下载器</span><br><span class="hljs-keyword">pub</span> <span class="hljs-keyword">struct</span> <span class="hljs-title class_">BootDownload</span>&lt;<span class="hljs-symbol">'a</span>&gt; {<br>    logger: &amp;<span class="hljs-symbol">'a</span> Logger,<br>}<br><br><span class="hljs-keyword">impl</span>&lt;<span class="hljs-symbol">'a</span>&gt; BootDownload&lt;<span class="hljs-symbol">'a</span>&gt; {<br>    <span class="hljs-keyword">pub</span> <span class="hljs-keyword">async</span> <span class="hljs-keyword">fn</span> <span class="hljs-title function_">execute</span>(<br>        &amp;<span class="hljs-keyword">self</span>,<br>        ctx: &amp;libefex::Context,<br>        packer: &amp;<span class="hljs-keyword">mut</span> OpenixPacker,<br>        secure: <span class="hljs-type">u32</span>,<br>        storage_type: <span class="hljs-type">u32</span>,<br>    ) <span class="hljs-punctuation">-&gt;</span> FlashResult&lt;()&gt; {<br>        <span class="hljs-comment">// 根据启动模式和存储类型选择 Boot 镜像</span><br>        <span class="hljs-keyword">let</span> <span class="hljs-variable">boot_data</span> = <span class="hljs-keyword">match</span> secure {<br>            BOOT_FILE_MODE_PKG =&gt; {<br>                packer.<span class="hljs-title function_ invoke__">get_image_data_by_name</span>(<span class="hljs-string">"bootpkg"</span>)?<br>            }<br>            BOOT_FILE_MODE_NORMAL =&gt; {<br>                <span class="hljs-keyword">match</span> StorageType::<span class="hljs-title function_ invoke__">from</span>(storage_type) {<br>                    StorageType::Spinor =&gt; packer.<span class="hljs-title function_ invoke__">get_image_data_by_name</span>(<span class="hljs-string">"boot0_nor"</span>)?,<br>                    _ =&gt; packer.<span class="hljs-title function_ invoke__">get_image_data_by_name</span>(<span class="hljs-string">"boot0_card"</span>)?,<br>                }<br>            }<br>            _ =&gt; <span class="hljs-keyword">return</span> <span class="hljs-title function_ invoke__">Ok</span>(()),<br>        };<br><br>        <span class="hljs-comment">// 写入 Boot 镜像</span><br>        ctx.<span class="hljs-title function_ invoke__">fes_write_boot</span>(boot_data)?;<br><br>        <span class="hljs-keyword">self</span>.logger.<span class="hljs-title function_ invoke__">info</span>(&amp;<span class="hljs-built_in">format!</span>(<span class="hljs-string">"Boot written ({} bytes)"</span>, boot_data.<span class="hljs-title function_ invoke__">len</span>()));<br><br>        <span class="hljs-title function_ invoke__">Ok</span>(())<br>    }<br>}<br></code></pre></td></tr></tbody></table></figure><hr><h2 id="设备模式设置" data-id="设备模式设置" class="notion-h"><a href="#设备模式设置" class="headerlink" title="设备模式设置"></a>设备模式设置</h2><figure class="highlight rust"><table><tbody><tr><td class="code"><pre><code class="hljs rust"><span class="hljs-comment">/// 设置设备模式（重启/关机）</span><br><span class="hljs-keyword">async</span> <span class="hljs-keyword">fn</span> <span class="hljs-title function_">set_device_mode</span>(&amp;<span class="hljs-keyword">self</span>, ctx: &amp;libefex::Context) <span class="hljs-punctuation">-&gt;</span> FlashResult&lt;()&gt; {<br>    <span class="hljs-keyword">let</span> <span class="hljs-variable">tool_mode</span> = <span class="hljs-keyword">match</span> <span class="hljs-keyword">self</span>.options.post_action.<span class="hljs-title function_ invoke__">as_str</span>() {<br>        <span class="hljs-string">"reboot"</span> =&gt; libefex::FesToolMode::Reboot,<br>        <span class="hljs-string">"poweroff"</span> =&gt; libefex::FesToolMode::PowerOff,<br>        <span class="hljs-string">"shutdown"</span> =&gt; libefex::FesToolMode::PowerOff,<br>        _ =&gt; libefex::FesToolMode::Reboot,<br>    };<br><br>    ctx.<span class="hljs-title function_ invoke__">fes_tool_mode</span>(libefex::FesToolMode::Normal, tool_mode)?;<br><br>    <span class="hljs-title function_ invoke__">Ok</span>(())<br>}<br></code></pre></td></tr></tbody></table></figure><hr><h2 id="代码亮点" data-id="代码亮点" class="notion-h"><a href="#代码亮点" class="headerlink" title="代码亮点"></a>代码亮点</h2><h3 id="1-异步重连机制" data-id="1-异步重连机制" class="notion-h"><a href="#1-异步重连机制" class="headerlink" title="1. 异步重连机制"></a>1. 异步重连机制</h3><figure class="highlight rust"><table><tbody><tr><td class="code"><pre><code class="hljs rust"><span class="hljs-keyword">async</span> <span class="hljs-keyword">fn</span> <span class="hljs-title function_">reconnect_device</span>(&amp;<span class="hljs-keyword">self</span>) <span class="hljs-punctuation">-&gt;</span> FlashResult&lt;libefex::Context&gt; {<br>    tokio::time::<span class="hljs-title function_ invoke__">sleep</span>(Duration::<span class="hljs-title function_ invoke__">from_secs</span>(<span class="hljs-number">2</span>)).<span class="hljs-keyword">await</span>;  <span class="hljs-comment">// 等待设备启动</span><br><br>    <span class="hljs-keyword">let</span> <span class="hljs-variable">max_retries</span> = <span class="hljs-number">25</span>;<br>    <span class="hljs-keyword">while</span> retries &lt; max_retries {<br>        tokio::time::<span class="hljs-title function_ invoke__">sleep</span>(Duration::<span class="hljs-title function_ invoke__">from_secs</span>(<span class="hljs-number">1</span>)).<span class="hljs-keyword">await</span>;  <span class="hljs-comment">// 每秒尝试一次</span><br>        <span class="hljs-comment">// ... 扫描和初始化 ...</span><br>    }<br>}<br></code></pre></td></tr></tbody></table></figure><h3 id="2-模式动态选择" data-id="2-模式动态选择" class="notion-h"><a href="#2-模式动态选择" class="headerlink" title="2. 模式动态选择"></a>2. 模式动态选择</h3><figure class="highlight rust"><table><tbody><tr><td class="code"><pre><code class="hljs rust"><span class="hljs-keyword">if</span> has_fel {<br>    <span class="hljs-keyword">let</span> <span class="hljs-variable">stages</span> = FlashStages::for_fel_mode();  <span class="hljs-comment">// 10 个阶段</span><br>} <span class="hljs-keyword">else</span> {<br>    <span class="hljs-keyword">let</span> <span class="hljs-variable">stages</span> = FlashStages::for_fes_mode();  <span class="hljs-comment">// 7 个阶段</span><br>}<br></code></pre></td></tr></tbody></table></figure><h3 id="3-分区过滤逻辑" data-id="3-分区过滤逻辑" class="notion-h"><a href="#3-分区过滤逻辑" class="headerlink" title="3. 分区过滤逻辑"></a>3. 分区过滤逻辑</h3><figure class="highlight rust"><table><tbody><tr><td class="code"><pre><code class="hljs rust"><span class="hljs-comment">// KeepData 模式保护用户数据</span><br><span class="hljs-keyword">if</span> options.mode == FlashMode::KeepData {<br>    <span class="hljs-keyword">if</span> name_lower == <span class="hljs-string">"udisk"</span> || name_lower == <span class="hljs-string">"private"</span> {<br>        <span class="hljs-keyword">continue</span>;  <span class="hljs-comment">// 跳过</span><br>    }<br>}<br><br><span class="hljs-comment">// Partition 模式仅刷写指定分区</span><br><span class="hljs-keyword">if</span> options.mode == FlashMode::Partition {<br>    <span class="hljs-keyword">if</span> !partitions.<span class="hljs-title function_ invoke__">iter</span>().<span class="hljs-title function_ invoke__">any</span>(|p| p == partition_name) {<br>        <span class="hljs-keyword">continue</span>;  <span class="hljs-comment">// 跳过</span><br>    }<br>}<br></code></pre></td></tr></tbody></table></figure><h3 id="4-Sparse-格式块处理" data-id="4-Sparse-格式块处理" class="notion-h"><a href="#4-Sparse-格式块处理" class="headerlink" title="4. Sparse 格式块处理"></a>4. Sparse 格式块处理</h3><figure class="highlight rust"><table><tbody><tr><td class="code"><pre><code class="hljs rust"><span class="hljs-keyword">match</span> chunk_header.chunk_type {<br>    CHUNK_TYPE_RAW =&gt; ctx.<span class="hljs-title function_ invoke__">fes_write</span>(address, chunk_data)?,<br>    CHUNK_TYPE_FILL =&gt; ctx.<span class="hljs-title function_ invoke__">fes_write_fill</span>(address, fill_value, size)?,<br>    CHUNK_TYPE_DONT_CARE =&gt; { <span class="hljs-comment">/* 跳过 */</span> },<br>    CHUNK_TYPE_CRC32 =&gt; { <span class="hljs-comment">/* 验证 */</span> },<br>}<br></code></pre></td></tr></tbody></table></figure><hr><h2 id="完整刷写流程图" data-id="完整刷写流程图" class="notion-h"><a href="#完整刷写流程图" class="headerlink" title="完整刷写流程图"></a>完整刷写流程图</h2><pre class="mermaid">sequenceDiagram    participant Flasher as Flasher    participant FEL as FelHandler    participant FES as FesHandler    participant Device as USB Device    Flasher-&gt;&gt;Flasher: 加载 FES 数据    Flasher-&gt;&gt;Device: 初始化 USB    Device--&gt;&gt;Flasher: 设备模式    alt FEL 模式        Flasher-&gt;&gt;FEL: handle(DRAM init)        FEL-&gt;&gt;Device: 下载 FES        FEL-&gt;&gt;Device: 执行 FES        Device--&gt;&gt;FEL: DRAM 就绪        FEL--&gt;&gt;Flasher: Ok        Flasher-&gt;&gt;FEL: download_uboot        FEL-&gt;&gt;Device: 下载 U-Boot        FEL-&gt;&gt;Device: 下载 DTB        FEL-&gt;&gt;Device: 下载 sys_config        FEL-&gt;&gt;Device: 执行 U-Boot        FEL--&gt;&gt;Flasher: Ok        Flasher-&gt;&gt;Flasher: reconnect_device        Note over Flasher: 等待 FES 模式        Flasher-&gt;&gt;Device: 扫描 USB        Device--&gt;&gt;Flasher: FES 模式设备    end    Flasher-&gt;&gt;FES: handle    FES-&gt;&gt;Device: 查询设备信息    Device--&gt;&gt;FES: secure/storage/flash_size    alt 需要擦除        FES-&gt;&gt;Device: 擦除 Flash    end    FES-&gt;&gt;Device: 写入 MBR    loop 每个分区        FES-&gt;&gt;FES: 检测 Sparse/Raw        alt Raw 格式            FES-&gt;&gt;Device: 直接写入        else Sparse 格式            FES-&gt;&gt;Device: 逐块写入        end    end    FES-&gt;&gt;Device: 写入 Boot    FES--&gt;&gt;Flasher: Ok    Flasher-&gt;&gt;Device: 设置模式（reboot/poweroff）    Device--&gt;&gt;Flasher: Ok</pre>]]>
    </content>
    <id>https://gloomyghost.com/posts/openixcli-flash-module/</id>
    <link href="https://gloomyghost.com/posts/openixcli-flash-module/"/>
    <published>2026-04-25T16:00:00.000Z</published>
    <summary>
      <![CDATA[<h2 id="概述" data-id="概述" class="notion-h"><a href="#概述" class="headerlink" title="概述"></a>概述</h2><p>Flash 模块是 OpenixCLI 的核心刷写引擎，负责协调 FEL（USB]]>
    </summary>
    <title>OpenixCLI Flash 模块深度解析：FEL/FES 双模式刷写引擎</title>
    <updated>2026-05-29T20:33:31.219Z</updated>
  </entry>
  <entry>
    <author>
      <name>柚木 鉉</name>
    </author>
    <category term="Rust" scheme="https://gloomyghost.com/tags/Rust/"/>
    <category term="嵌入式" scheme="https://gloomyghost.com/tags/%E5%B5%8C%E5%85%A5%E5%BC%8F/"/>
    <category term="OpenixCLI" scheme="https://gloomyghost.com/tags/OpenixCLI/"/>
    <category term="MBR" scheme="https://gloomyghost.com/tags/MBR/"/>
    <category term="配置解析" scheme="https://gloomyghost.com/tags/%E9%85%8D%E7%BD%AE%E8%A7%A3%E6%9E%90/"/>
    <content>
      <![CDATA[<h2 id="概述" data-id="概述" class="notion-h"><a href="#概述" class="headerlink" title="概述"></a>概述</h2><p>嵌入式固件刷写涉及多种配置格式的解析：Boot 头（boot0/U-Boot）、分区表（MBR/GPT）、系统配置（sys_config）等。OpenixCLI 的 Config 模块负责解析这些与硬件初始化密切相关的数据结构，为刷写流程提供存储类型、分区信息、DRAM 参数等关键数据。</p><h3 id="模块结构" data-id="模块结构" class="notion-h"><a href="#模块结构" class="headerlink" title="模块结构"></a>模块结构</h3><figure class="highlight plaintext"><table><tbody><tr><td class="code"><pre><code class="hljs plaintext">src/config/<br>├── mod.rs          # 模块导出<br>├── boot_header.rs  # Boot0/U-Boot 头结构<br>├── mbr_parser.rs   # MBR 分区表解析<br>├── partition.rs    # 分区配置解析（INI 格式）<br>└── sys_config.rs   # 系统配置解析（DRAM 参数）<br></code></pre></td></tr></tbody></table></figure><hr><h2 id="Boot-头结构解析" data-id="Boot-头结构解析" class="notion-h"><a href="#Boot-头结构解析" class="headerlink" title="Boot 头结构解析"></a>Boot 头结构解析</h2><h3 id="Boot0-头结构" data-id="Boot0-头结构" class="notion-h"><a href="#Boot0-头结构" class="headerlink" title="Boot0 头结构"></a>Boot0 头结构</h3><p>Boot0 是全志芯片的第一阶段 bootloader，负责最基本的硬件初始化（如 DRAM）。</p><figure class="highlight rust"><table><tbody><tr><td class="code"><pre><code class="hljs rust"><span class="hljs-comment">/// Boot0 魂数</span><br><span class="hljs-keyword">pub</span> <span class="hljs-keyword">const</span> BOOT0_MAGIC: &amp;<span class="hljs-type">str</span> = <span class="hljs-string">"eGON.BT0"</span>;<br><br><span class="hljs-comment">/// Boot0 头结构</span><br><span class="hljs-comment">///</span><br><span class="hljs-comment">/// 第一阶段 bootloader 头，包含 DRAM 初始化参数和运行地址</span><br><span class="hljs-meta">#[repr(C, packed)]</span><br><span class="hljs-meta">#[derive(Debug, Clone, Copy)]</span><br><span class="hljs-keyword">pub</span> <span class="hljs-keyword">struct</span> <span class="hljs-title class_">Boot0Header</span> {<br>    <span class="hljs-comment">/// 跳转指令（ARM 指令）</span><br>    <span class="hljs-keyword">pub</span> jump_instruction: <span class="hljs-type">u32</span>,<br><br>    <span class="hljs-comment">/// 魂数标识：必须为 "eGON.BT0"</span><br>    <span class="hljs-keyword">pub</span> magic: [<span class="hljs-type">u8</span>; <span class="hljs-number">8</span>],<br><br>    <span class="hljs-comment">/// 校验和</span><br>    <span class="hljs-keyword">pub</span> check_sum: <span class="hljs-type">u32</span>,<br><br>    <span class="hljs-comment">/// Boot0 镜像长度</span><br>    <span class="hljs-keyword">pub</span> length: <span class="hljs-type">u32</span>,<br><br>    <span class="hljs-comment">/// 公共头大小</span><br>    <span class="hljs-keyword">pub</span> pub_head_size: <span class="hljs-type">u32</span>,<br><br>    <span class="hljs-comment">/// 公共头版本</span><br>    <span class="hljs-keyword">pub</span> pub_head_vsn: [<span class="hljs-type">u8</span>; <span class="hljs-number">4</span>],<br><br>    <span class="hljs-comment">/// 返回地址</span><br>    <span class="hljs-keyword">pub</span> ret_addr: <span class="hljs-type">u32</span>,<br><br>    <span class="hljs-comment">/// 运行地址（加载到内存后的执行地址）</span><br>    <span class="hljs-keyword">pub</span> run_addr: <span class="hljs-type">u32</span>,<br><br>    <span class="hljs-comment">/// 启动 CPU 核</span><br>    <span class="hljs-keyword">pub</span> boot_cpu: <span class="hljs-type">u32</span>,<br><br>    <span class="hljs-comment">/// 平台标识（如 "sun50iw10"）</span><br>    <span class="hljs-keyword">pub</span> platform: [<span class="hljs-type">u8</span>; <span class="hljs-number">8</span>],<br>}<br></code></pre></td></tr></tbody></table></figure><p><strong>内存布局（十六进制）：</strong></p><figure class="highlight plaintext"><table><tbody><tr><td class="code"><pre><code class="hljs plaintext">偏移    字段              大小    说明<br>0x00    jump_instruction  4      ARM 跳转指令<br>0x04    magic             8      "eGON.BT0"<br>0x0C    check_sum         4      校验和<br>0x10    length            4      镜像长度<br>0x14    pub_head_size     4      头大小<br>0x18    pub_head_vsn      4      版本字符串<br>0x1C    ret_addr          4      返回地址<br>0x20    run_addr          4      运行地址<br>0x24    boot_cpu          4      CPU 核编号<br>0x28    platform          8      平台标识<br>...<br></code></pre></td></tr></tbody></table></figure><h3 id="U-Boot-头结构" data-id="U-Boot-头结构" class="notion-h"><a href="#U-Boot-头结构" class="headerlink" title="U-Boot 头结构"></a>U-Boot 头结构</h3><p>U-Boot 是第二阶段 bootloader，包含更丰富的硬件配置。</p><figure class="highlight rust"><table><tbody><tr><td class="code"><pre><code class="hljs rust"><span class="hljs-comment">/// U-Boot 魂数</span><br><span class="hljs-keyword">pub</span> <span class="hljs-keyword">const</span> UBOOT_MAGIC: &amp;<span class="hljs-type">str</span> = <span class="hljs-string">"uboot"</span>;<br><br><span class="hljs-comment">/// U-Boot 基础头结构</span><br><span class="hljs-meta">#[repr(C, packed)]</span><br><span class="hljs-meta">#[derive(Debug, Clone, Copy)]</span><br><span class="hljs-keyword">pub</span> <span class="hljs-keyword">struct</span> <span class="hljs-title class_">UBootBaseHeader</span> {<br>    <span class="hljs-keyword">pub</span> jump_instruction: <span class="hljs-type">u32</span>,<br>    <span class="hljs-keyword">pub</span> magic: [<span class="hljs-type">u8</span>; <span class="hljs-number">8</span>],           <span class="hljs-comment">// "uboot"</span><br>    <span class="hljs-keyword">pub</span> check_sum: <span class="hljs-type">u32</span>,<br>    <span class="hljs-keyword">pub</span> align_size: <span class="hljs-type">u32</span>,          <span class="hljs-comment">// 对齐大小</span><br>    <span class="hljs-keyword">pub</span> length: <span class="hljs-type">u32</span>,              <span class="hljs-comment">// U-Boot 长度</span><br>    <span class="hljs-keyword">pub</span> uboot_length: <span class="hljs-type">u32</span>,        <span class="hljs-comment">// 包含头后的总长度</span><br>    <span class="hljs-keyword">pub</span> version: [<span class="hljs-type">u8</span>; <span class="hljs-number">8</span>],         <span class="hljs-comment">// 版本字符串</span><br>    <span class="hljs-keyword">pub</span> platform: [<span class="hljs-type">u8</span>; <span class="hljs-number">8</span>],        <span class="hljs-comment">// 平台标识</span><br>    <span class="hljs-keyword">pub</span> run_addr: <span class="hljs-type">u32</span>,            <span class="hljs-comment">// 运行地址</span><br>}<br></code></pre></td></tr></tbody></table></figure><h3 id="U-Boot-数据头结构" data-id="U-Boot-数据头结构" class="notion-h"><a href="#U-Boot-数据头结构" class="headerlink" title="U-Boot 数据头结构"></a>U-Boot 数据头结构</h3><p>包含 DRAM 参数、GPIO 配置、工作模式等：</p><figure class="highlight rust"><table><tbody><tr><td class="code"><pre><code class="hljs rust"><span class="hljs-comment">/// U-Boot 数据头结构</span><br><span class="hljs-comment">///</span><br><span class="hljs-comment">/// 包含 DRAM 参数和硬件初始化数据</span><br><span class="hljs-meta">#[repr(C, packed)]</span><br><span class="hljs-meta">#[derive(Debug, Clone, Copy)]</span><br><span class="hljs-keyword">pub</span> <span class="hljs-keyword">struct</span> <span class="hljs-title class_">UBootDataHeader</span> {<br>    <span class="hljs-comment">/// DRAM 参数数组（32 个 u32）</span><br>    <span class="hljs-keyword">pub</span> dram_para: [<span class="hljs-type">u32</span>; <span class="hljs-number">32</span>],<br><br>    <span class="hljs-comment">/// 运行时钟频率</span><br>    <span class="hljs-keyword">pub</span> run_clock: <span class="hljs-type">i32</span>,<br><br>    <span class="hljs-comment">/// 核心电压</span><br>    <span class="hljs-keyword">pub</span> run_core_vol: <span class="hljs-type">i32</span>,<br><br>    <span class="hljs-comment">/// UART 端口编号</span><br>    <span class="hljs-keyword">pub</span> uart_port: <span class="hljs-type">i32</span>,<br><br>    <span class="hljs-comment">/// UART GPIO 配置（2 个）</span><br>    <span class="hljs-keyword">pub</span> uart_gpio: [UBootNormalGpioCfg; <span class="hljs-number">2</span>],<br><br>    <span class="hljs-comment">/// TWI (I2C) 端口编号</span><br>    <span class="hljs-keyword">pub</span> twi_port: <span class="hljs-type">i32</span>,<br><br>    <span class="hljs-comment">/// TWI GPIO 配置（2 个）</span><br>    <span class="hljs-keyword">pub</span> twi_gpio: [UBootNormalGpioCfg; <span class="hljs-number">2</span>],<br><br>    <span class="hljs-comment">/// 工作模式</span><br>    <span class="hljs-keyword">pub</span> work_mode: <span class="hljs-type">i32</span>,<br><br>    <span class="hljs-comment">/// 存储类型</span><br>    <span class="hljs-keyword">pub</span> storage_type: <span class="hljs-type">i32</span>,<br>}<br></code></pre></td></tr></tbody></table></figure><h3 id="GPIO-配置结构" data-id="GPIO-配置结构" class="notion-h"><a href="#GPIO-配置结构" class="headerlink" title="GPIO 配置结构"></a>GPIO 配置结构</h3><figure class="highlight rust"><table><tbody><tr><td class="code"><pre><code class="hljs rust"><span class="hljs-comment">/// GPIO 配置结构</span><br><span class="hljs-meta">#[repr(C, packed)]</span><br><span class="hljs-meta">#[derive(Debug, Clone, Copy)]</span><br><span class="hljs-keyword">pub</span> <span class="hljs-keyword">struct</span> <span class="hljs-title class_">UBootNormalGpioCfg</span> {<br>    <span class="hljs-comment">/// GPIO 端口组（如 PA, PB, PC...）</span><br>    <span class="hljs-keyword">pub</span> port: <span class="hljs-type">u8</span>,<br><br>    <span class="hljs-comment">/// GPIO 端口内编号</span><br>    <span class="hljs-keyword">pub</span> port_num: <span class="hljs-type">u8</span>,<br><br>    <span class="hljs-comment">/// 功能选择（复用配置）</span><br>    <span class="hljs-keyword">pub</span> mul_sel: <span class="hljs-type">u8</span>,<br><br>    <span class="hljs-comment">/// 上下拉配置</span><br>    <span class="hljs-keyword">pub</span> pull: <span class="hljs-type">u8</span>,<br><br>    <span class="hljs-comment">/// 驱动能力等级</span><br>    <span class="hljs-keyword">pub</span> drv_level: <span class="hljs-type">u8</span>,<br><br>    <span class="hljs-comment">/// 数据值</span><br>    <span class="hljs-keyword">pub</span> data: <span class="hljs-type">u8</span>,<br><br>    <span class="hljs-comment">/// 保留字段</span><br>    <span class="hljs-keyword">pub</span> reserved: [<span class="hljs-type">u8</span>; <span class="hljs-number">2</span>],<br>}<br></code></pre></td></tr></tbody></table></figure><h3 id="工作模式常量" data-id="工作模式常量" class="notion-h"><a href="#工作模式常量" class="headerlink" title="工作模式常量"></a>工作模式常量</h3><figure class="highlight rust"><table><tbody><tr><td class="code"><pre><code class="hljs rust"><span class="hljs-comment">/// USB 产品模式工作模式</span><br><span class="hljs-keyword">pub</span> <span class="hljs-keyword">const</span> WORK_MODE_USB_PRODUCT: <span class="hljs-type">u32</span> = <span class="hljs-number">0x10</span>;<br><br><span class="hljs-comment">/// Boot 文件模式常量</span><br><span class="hljs-keyword">pub</span> <span class="hljs-keyword">const</span> BOOT_FILE_MODE_NORMAL: <span class="hljs-type">u32</span> = <span class="hljs-number">0</span>;    <span class="hljs-comment">// 正常启动</span><br><span class="hljs-keyword">pub</span> <span class="hljs-keyword">const</span> BOOT_FILE_MODE_TOC: <span class="hljs-type">u32</span> = <span class="hljs-number">1</span>;       <span class="hljs-comment">// TOC 启动</span><br><span class="hljs-keyword">pub</span> <span class="hljs-keyword">const</span> BOOT_FILE_MODE_RESERVED0: <span class="hljs-type">u32</span> = <span class="hljs-number">2</span>; <span class="hljs-comment">// 保留</span><br><span class="hljs-keyword">pub</span> <span class="hljs-keyword">const</span> BOOT_FILE_MODE_RESERVED1: <span class="hljs-type">u32</span> = <span class="hljs-number">3</span>; <span class="hljs-comment">// 保留</span><br><span class="hljs-keyword">pub</span> <span class="hljs-keyword">const</span> BOOT_FILE_MODE_PKG: <span class="hljs-type">u32</span> = <span class="hljs-number">4</span>;       <span class="hljs-comment">// 包模式</span><br><br><span class="hljs-comment">/// 获取 Boot 文件模式字符串</span><br><span class="hljs-keyword">pub</span> <span class="hljs-keyword">fn</span> <span class="hljs-title function_">get_sunxi_boot_file_mode_string</span>(mode: <span class="hljs-type">u32</span>) <span class="hljs-punctuation">-&gt;</span> &amp;<span class="hljs-symbol">'static</span> <span class="hljs-type">str</span> {<br>    <span class="hljs-keyword">match</span> mode {<br>        BOOT_FILE_MODE_NORMAL =&gt; <span class="hljs-string">"Normal Boot File"</span>,<br>        BOOT_FILE_MODE_TOC =&gt; <span class="hljs-string">"TOC Boot File"</span>,<br>        BOOT_FILE_MODE_RESERVED0 =&gt; <span class="hljs-string">"Reserved Boot File 0"</span>,<br>        BOOT_FILE_MODE_RESERVED1 =&gt; <span class="hljs-string">"Reserved Boot File 1"</span>,<br>        BOOT_FILE_MODE_PKG =&gt; <span class="hljs-string">"Boot Package File"</span>,<br>        _ =&gt; <span class="hljs-string">"Unknown Boot File Type"</span>,<br>    }<br>}<br></code></pre></td></tr></tbody></table></figure><hr><h2 id="MBR-分区表解析" data-id="MBR-分区表解析" class="notion-h"><a href="#MBR-分区表解析" class="headerlink" title="MBR 分区表解析"></a>MBR 分区表解析</h2><h3 id="MBR-格式常量" data-id="MBR-格式常量" class="notion-h"><a href="#MBR-格式常量" class="headerlink" title="MBR 格式常量"></a>MBR 格式常量</h3><figure class="highlight rust"><table><tbody><tr><td class="code"><pre><code class="hljs rust"><span class="hljs-comment">/// MBR 魂数（全志特有）</span><br><span class="hljs-keyword">pub</span> <span class="hljs-keyword">const</span> MBR_MAGIC: &amp;<span class="hljs-type">str</span> = <span class="hljs-string">"softw411"</span>;<br><br><span class="hljs-comment">/// MBR 版本号</span><br><span class="hljs-keyword">pub</span> <span class="hljs-keyword">const</span> MBR_VERSION: <span class="hljs-type">u32</span> = <span class="hljs-number">0x00000200</span>;<br><br><span class="hljs-comment">/// MBR 大小：16KB（远大于标准 MBR 的 512B）</span><br><span class="hljs-keyword">pub</span> <span class="hljs-keyword">const</span> MBR_SIZE: <span class="hljs-type">usize</span> = <span class="hljs-number">16</span> * <span class="hljs-number">1024</span>;<br><br><span class="hljs-comment">/// 最大分区数量</span><br><span class="hljs-keyword">pub</span> <span class="hljs-keyword">const</span> MBR_MAX_PART_CNT: <span class="hljs-type">usize</span> = <span class="hljs-number">120</span>;<br><br><span class="hljs-comment">/// 分区名称最大长度</span><br><span class="hljs-keyword">pub</span> <span class="hljs-keyword">const</span> PART_NAME_MAX_LEN: <span class="hljs-type">usize</span> = <span class="hljs-number">16</span>;<br><br><span class="hljs-comment">/// CRC32 有效标志</span><br><span class="hljs-keyword">pub</span> <span class="hljs-keyword">const</span> EFEX_CRC32_VALID_FLAG: <span class="hljs-type">u32</span> = <span class="hljs-number">0x6a617603</span>;<br></code></pre></td></tr></tbody></table></figure><p><strong>全志 MBR 与标准 MBR 的区别：</strong></p><table><thead><tr><th>特性</th><th>标准 MBR</th><th>全志 MBR</th></tr></thead><tbody><tr><td>大小</td><td>512 bytes</td><td>16 KB</td></tr><tr><td>最大分区数</td><td>4（主分区）</td><td>120</td></tr><tr><td>魂数</td><td>无特定魔数</td><td>“softw411”</td></tr><tr><td>分区名称</td><td>无</td><td>16 字符</td></tr><tr><td>支持 64 位地址</td><td>否（CHS）</td><td>是（addrhi/addrlo）</td></tr></tbody></table><h3 id="SunxiPartitionRaw-结构" data-id="SunxiPartitionRaw-结构" class="notion-h"><a href="#SunxiPartitionRaw-结构" class="headerlink" title="SunxiPartitionRaw 结构"></a>SunxiPartitionRaw 结构</h3><figure class="highlight rust"><table><tbody><tr><td class="code"><pre><code class="hljs rust"><span class="hljs-comment">/// 原始分区条目结构</span><br><span class="hljs-meta">#[repr(C, packed)]</span><br><span class="hljs-meta">#[derive(Debug, Clone, Copy)]</span><br><span class="hljs-keyword">pub</span> <span class="hljs-keyword">struct</span> <span class="hljs-title class_">SunxiPartitionRaw</span> {<br>    <span class="hljs-comment">/// 分区起始地址（高 32 位）</span><br>    <span class="hljs-keyword">pub</span> addrhi: <span class="hljs-type">u32</span>,<br><br>    <span class="hljs-comment">/// 分区起始地址（低 32 位）</span><br>    <span class="hljs-keyword">pub</span> addrlo: <span class="hljs-type">u32</span>,<br><br>    <span class="hljs-comment">/// 分区长度（高 32 位）</span><br>    <span class="hljs-keyword">pub</span> lenhi: <span class="hljs-type">u32</span>,<br><br>    <span class="hljs-comment">/// 分区长度（低 32 位）</span><br>    <span class="hljs-keyword">pub</span> lenlo: <span class="hljs-type">u32</span>,<br><br>    <span class="hljs-comment">/// 分区类别名</span><br>    <span class="hljs-keyword">pub</span> classname: [<span class="hljs-type">u8</span>; PART_NAME_MAX_LEN],<br><br>    <span class="hljs-comment">/// 分区名称</span><br>    <span class="hljs-keyword">pub</span> name: [<span class="hljs-type">u8</span>; PART_NAME_MAX_LEN],<br><br>    <span class="hljs-comment">/// 用户类型</span><br>    <span class="hljs-keyword">pub</span> user_type: <span class="hljs-type">u32</span>,<br><br>    <span class="hljs-comment">/// 是否包含关键数据</span><br>    <span class="hljs-keyword">pub</span> keydata: <span class="hljs-type">u32</span>,<br><br>    <span class="hljs-comment">/// 是否只读</span><br>    <span class="hljs-keyword">pub</span> ro: <span class="hljs-type">u32</span>,<br><br>    <span class="hljs-comment">/// 保留字段</span><br>    <span class="hljs-keyword">pub</span> reserved: [<span class="hljs-type">u8</span>; PART_SIZE_RES_LEN],  <span class="hljs-comment">// 68 bytes</span><br>}<br></code></pre></td></tr></tbody></table></figure><p><strong>关键方法：</strong></p><figure class="highlight rust"><table><tbody><tr><td class="code"><pre><code class="hljs rust"><span class="hljs-keyword">impl</span> <span class="hljs-title class_">SunxiPartitionRaw</span> {<br>    <span class="hljs-comment">/// 获取分区起始地址（64 位）</span><br>    <span class="hljs-keyword">pub</span> <span class="hljs-keyword">fn</span> <span class="hljs-title function_">address</span>(&amp;<span class="hljs-keyword">self</span>) <span class="hljs-punctuation">-&gt;</span> <span class="hljs-type">u64</span> {<br>        ((<span class="hljs-keyword">self</span>.addrhi <span class="hljs-keyword">as</span> <span class="hljs-type">u64</span>) &lt;&lt; <span class="hljs-number">32</span>) | (<span class="hljs-keyword">self</span>.addrlo <span class="hljs-keyword">as</span> <span class="hljs-type">u64</span>)<br>    }<br><br>    <span class="hljs-comment">/// 获取分区长度（64 位）</span><br>    <span class="hljs-keyword">pub</span> <span class="hljs-keyword">fn</span> <span class="hljs-title function_">length</span>(&amp;<span class="hljs-keyword">self</span>) <span class="hljs-punctuation">-&gt;</span> <span class="hljs-type">u64</span> {<br>        ((<span class="hljs-keyword">self</span>.lenhi <span class="hljs-keyword">as</span> <span class="hljs-type">u64</span>) &lt;&lt; <span class="hljs-number">32</span>) | (<span class="hljs-keyword">self</span>.lenlo <span class="hljs-keyword">as</span> <span class="hljs-type">u64</span>)<br>    }<br><br>    <span class="hljs-comment">/// 检查是否只读</span><br>    <span class="hljs-keyword">pub</span> <span class="hljs-keyword">fn</span> <span class="hljs-title function_">readonly</span>(&amp;<span class="hljs-keyword">self</span>) <span class="hljs-punctuation">-&gt;</span> <span class="hljs-type">bool</span> {<br>        <span class="hljs-keyword">self</span>.ro != <span class="hljs-number">0</span><br>    }<br><br>    <span class="hljs-comment">/// 获取分区名称</span><br>    <span class="hljs-keyword">pub</span> <span class="hljs-keyword">fn</span> <span class="hljs-title function_">name_str</span>(&amp;<span class="hljs-keyword">self</span>) <span class="hljs-punctuation">-&gt;</span> <span class="hljs-type">String</span> {<br>        <span class="hljs-type">String</span>::<span class="hljs-title function_ invoke__">from_utf8_lossy</span>(&amp;<span class="hljs-keyword">self</span>.name)<br>            .<span class="hljs-title function_ invoke__">trim_end_matches</span>(<span class="hljs-string">'<span class="hljs-char escape_">\0</span>'</span>)<br>            .<span class="hljs-title function_ invoke__">to_string</span>()<br>    }<br>}<br></code></pre></td></tr></tbody></table></figure><h3 id="SunxiMbrRaw-结构" data-id="SunxiMbrRaw-结构" class="notion-h"><a href="#SunxiMbrRaw-结构" class="headerlink" title="SunxiMbrRaw 结构"></a>SunxiMbrRaw 结构</h3><figure class="highlight rust"><table><tbody><tr><td class="code"><pre><code class="hljs rust"><span class="hljs-comment">/// 原始 MBR 结构</span><br><span class="hljs-meta">#[repr(C, packed)]</span><br><span class="hljs-meta">#[derive(Debug, Clone, Copy)]</span><br><span class="hljs-keyword">pub</span> <span class="hljs-keyword">struct</span> <span class="hljs-title class_">SunxiMbrRaw</span> {<br>    <span class="hljs-comment">/// CRC32 校验</span><br>    <span class="hljs-keyword">pub</span> crc32: <span class="hljs-type">u32</span>,<br><br>    <span class="hljs-comment">/// MBR 版本</span><br>    <span class="hljs-keyword">pub</span> version: <span class="hljs-type">u32</span>,<br><br>    <span class="hljs-comment">/// 魂数标识：必须为 "softw411"</span><br>    <span class="hljs-keyword">pub</span> magic: [<span class="hljs-type">u8</span>; <span class="hljs-number">8</span>],<br><br>    <span class="hljs-comment">/// MBR 备份数量</span><br>    <span class="hljs-keyword">pub</span> copy: <span class="hljs-type">u32</span>,<br><br>    <span class="hljs-comment">/// MBR 索引号</span><br>    <span class="hljs-keyword">pub</span> index: <span class="hljs-type">u32</span>,<br><br>    <span class="hljs-comment">/// 分区数量</span><br>    <span class="hljs-keyword">pub</span> part_count: <span class="hljs-type">u32</span>,<br><br>    <span class="hljs-comment">/// 时间戳</span><br>    <span class="hljs-keyword">pub</span> stamp: <span class="hljs-type">u32</span>,<br><br>    <span class="hljs-comment">/// 分区条目数组（最多 120 个）</span><br>    <span class="hljs-keyword">pub</span> partitions: [SunxiPartitionRaw; MBR_MAX_PART_CNT],<br>}<br></code></pre></td></tr></tbody></table></figure><h3 id="MBR-内存布局" data-id="MBR-内存布局" class="notion-h"><a href="#MBR-内存布局" class="headerlink" title="MBR 内存布局"></a>MBR 内存布局</h3><figure class="highlight plaintext"><table><tbody><tr><td class="code"><pre><code class="hljs plaintext">+-------------------------------------------------------------+<br>|                 Sunxi MBR Structure (16KB)                  |<br>+-------------------------------------------------------------+<br>| Offset 0x00                                                 |<br>| +---------------------------------------------------------+ |<br>| | CRC32 (4 bytes)                                         | |<br>| | Version (4 bytes)                                       | |<br>| | Magic: "softw411" (8 bytes)                             | |<br>| | Copy (4 bytes)                                          | |<br>| | Index (4 bytes)                                         | |<br>| | Part Count (4 bytes)                                    | |<br>| | Stamp (4 bytes)                                         | |<br>| +---------------------------------------------------------+ |<br>| Offset 0x24                                                 |<br>| +---------------------------------------------------------+ |<br>| | Partition 0 (128 bytes)                                 | |<br>| |   - addrhi, addrlo (8 bytes)                            | |<br>| |   - lenhi, lenlo (8 bytes)                              | |<br>| |   - classname (16 bytes)                                | |<br>| |   - name (16 bytes)                                     | |<br>| |   - user_type, keydata, ro (12 bytes)                   | |<br>| |   - reserved (68 bytes)                                 | |<br>| +---------------------------------------------------------+ |<br>| Offset 0xA4                                                 |<br>| +---------------------------------------------------------+ |<br>| | Partition 1 (128 bytes)                                 | |<br>| +---------------------------------------------------------+ |<br>| ...                                                         |<br>| Offset 0xA4 + (119 * 128) = 0x3E44                          |<br>| +---------------------------------------------------------+ |<br>| | Partition 119 (128 bytes)                               | |<br>| +---------------------------------------------------------+ |<br>| Offset 0x3FC4 - 0x4000                                      |<br>| +---------------------------------------------------------+ |<br>| | Padding/Unused                                          | |<br>| +---------------------------------------------------------+ |<br>+-------------------------------------------------------------+<br></code></pre></td></tr></tbody></table></figure><h3 id="SunxiMbr-解析结构" data-id="SunxiMbr-解析结构" class="notion-h"><a href="#SunxiMbr-解析结构" class="headerlink" title="SunxiMbr 解析结构"></a>SunxiMbr 解析结构</h3><figure class="highlight rust"><table><tbody><tr><td class="code"><pre><code class="hljs rust"><span class="hljs-comment">/// 解析后的 MBR 结构（高层抽象）</span><br><span class="hljs-meta">#[derive(Debug, Clone)]</span><br><span class="hljs-keyword">pub</span> <span class="hljs-keyword">struct</span> <span class="hljs-title class_">SunxiMbr</span> {<br>    <span class="hljs-keyword">pub</span> crc32: <span class="hljs-type">u32</span>,<br>    <span class="hljs-keyword">pub</span> version: <span class="hljs-type">u32</span>,<br>    <span class="hljs-keyword">pub</span> magic: <span class="hljs-type">String</span>,<br>    <span class="hljs-keyword">pub</span> copy: <span class="hljs-type">u32</span>,<br>    <span class="hljs-keyword">pub</span> index: <span class="hljs-type">u32</span>,<br>    <span class="hljs-keyword">pub</span> part_count: <span class="hljs-type">u32</span>,<br>    <span class="hljs-keyword">pub</span> stamp: <span class="hljs-type">u32</span>,<br>    <span class="hljs-keyword">pub</span> partitions: <span class="hljs-type">Vec</span>&lt;SunxiPartition&gt;,<br>}<br><br><span class="hljs-keyword">impl</span> <span class="hljs-title class_">SunxiMbr</span> {<br>    <span class="hljs-comment">/// 从原始数据解析 MBR</span><br>    <span class="hljs-keyword">pub</span> <span class="hljs-keyword">fn</span> <span class="hljs-title function_">parse</span>(data: &amp;[<span class="hljs-type">u8</span>]) <span class="hljs-punctuation">-&gt;</span> <span class="hljs-type">Result</span>&lt;<span class="hljs-keyword">Self</span>, &amp;<span class="hljs-symbol">'static</span> <span class="hljs-type">str</span>&gt; {<br>        <span class="hljs-comment">// 1. 解析原始结构</span><br>        <span class="hljs-keyword">let</span> <span class="hljs-variable">raw</span> = SunxiMbrRaw::<span class="hljs-title function_ invoke__">parse</span>(data)?;<br><br>        <span class="hljs-comment">// 2. 提取有效分区</span><br>        <span class="hljs-keyword">let</span> <span class="hljs-keyword">mut </span><span class="hljs-variable">partitions</span> = <span class="hljs-type">Vec</span>::<span class="hljs-title function_ invoke__">with_capacity</span>(raw.part_count <span class="hljs-keyword">as</span> <span class="hljs-type">usize</span>);<br>        <span class="hljs-keyword">for</span> <span class="hljs-variable">i</span> <span class="hljs-keyword">in</span> <span class="hljs-number">0</span>..raw.part_count <span class="hljs-keyword">as</span> <span class="hljs-type">usize</span> {<br>            <span class="hljs-keyword">let</span> <span class="hljs-variable">partition</span> = SunxiPartition::<span class="hljs-title function_ invoke__">from_raw</span>(&amp;raw.partitions[i]);<br>            partitions.<span class="hljs-title function_ invoke__">push</span>(partition);<br>        }<br><br>        <span class="hljs-comment">// 3. 构建高层结构</span><br>        <span class="hljs-title function_ invoke__">Ok</span>(<span class="hljs-keyword">Self</span> {<br>            crc32: raw.crc32,<br>            version: raw.version,<br>            magic: raw.<span class="hljs-title function_ invoke__">magic_str</span>(),<br>            copy: raw.copy,<br>            index: raw.index,<br>            part_count: raw.part_count,<br>            stamp: raw.stamp,<br>            partitions,<br>        })<br>    }<br><br>    <span class="hljs-comment">/// 转换为 MbrInfo（用于刷写流程）</span><br>    <span class="hljs-keyword">pub</span> <span class="hljs-keyword">fn</span> <span class="hljs-title function_">to_mbr_info</span>(&amp;<span class="hljs-keyword">self</span>) <span class="hljs-punctuation">-&gt;</span> MbrInfo {<br>        MbrInfo {<br>            part_count: <span class="hljs-keyword">self</span>.part_count,<br>            partitions: <span class="hljs-keyword">self</span>.partitions.<span class="hljs-title function_ invoke__">clone</span>(),<br>        }<br>    }<br>}<br><br><span class="hljs-comment">/// MBR 信息容器</span><br><span class="hljs-meta">#[derive(Debug, Clone)]</span><br><span class="hljs-keyword">pub</span> <span class="hljs-keyword">struct</span> <span class="hljs-title class_">MbrInfo</span> {<br>    <span class="hljs-keyword">pub</span> part_count: <span class="hljs-type">u32</span>,<br>    <span class="hljs-keyword">pub</span> partitions: <span class="hljs-type">Vec</span>&lt;SunxiPartition&gt;,<br>}<br></code></pre></td></tr></tbody></table></figure><hr><h2 id="分区配置解析（INI-格式）" data-id="分区配置解析（INI-格式）" class="notion-h"><a href="#分区配置解析（INI-格式）" class="headerlink" title="分区配置解析（INI 格式）"></a>分区配置解析（INI 格式）</h2><h3 id="PartitionConfig-结构" data-id="PartitionConfig-结构" class="notion-h"><a href="#PartitionConfig-结构" class="headerlink" title="PartitionConfig 结构"></a>PartitionConfig 结构</h3><figure class="highlight rust"><table><tbody><tr><td class="code"><pre><code class="hljs rust"><span class="hljs-comment">/// 分区配置条目</span><br><span class="hljs-meta">#[derive(Debug, Clone, Default)]</span><br><span class="hljs-keyword">pub</span> <span class="hljs-keyword">struct</span> <span class="hljs-title class_">PartitionConfig</span> {<br>    <span class="hljs-comment">/// 分区名称</span><br>    <span class="hljs-keyword">pub</span> name: <span class="hljs-type">String</span>,<br><br>    <span class="hljs-comment">/// 分区大小（字节）</span><br>    <span class="hljs-keyword">pub</span> size: <span class="hljs-type">u64</span>,<br><br>    <span class="hljs-comment">/// 下载文件路径</span><br>    <span class="hljs-keyword">pub</span> downloadfile: <span class="hljs-type">String</span>,<br><br>    <span class="hljs-comment">/// 用户类型标识</span><br>    <span class="hljs-keyword">pub</span> user_type: <span class="hljs-type">u32</span>,<br><br>    <span class="hljs-comment">/// 是否包含关键数据</span><br>    <span class="hljs-keyword">pub</span> keydata: <span class="hljs-type">bool</span>,<br><br>    <span class="hljs-comment">/// 是否需要加密</span><br>    <span class="hljs-keyword">pub</span> encrypt: <span class="hljs-type">bool</span>,<br><br>    <span class="hljs-comment">/// 是否需要验证</span><br>    <span class="hljs-keyword">pub</span> verify: <span class="hljs-type">bool</span>,<br><br>    <span class="hljs-comment">/// 是否只读</span><br>    <span class="hljs-keyword">pub</span> readonly: <span class="hljs-type">bool</span>,<br>}<br></code></pre></td></tr></tbody></table></figure><h3 id="OpenixPartition-解析器" data-id="OpenixPartition-解析器" class="notion-h"><a href="#OpenixPartition-解析器" class="headerlink" title="OpenixPartition 解析器"></a>OpenixPartition 解析器</h3><figure class="highlight rust"><table><tbody><tr><td class="code"><pre><code class="hljs rust"><span class="hljs-comment">/// 分区配置容器</span><br><span class="hljs-keyword">pub</span> <span class="hljs-keyword">struct</span> <span class="hljs-title class_">OpenixPartition</span> {<br>    partitions: <span class="hljs-type">Vec</span>&lt;PartitionConfig&gt;,<br>}<br><br><span class="hljs-keyword">impl</span> <span class="hljs-title class_">OpenixPartition</span> {<br>    <span class="hljs-comment">/// 从二进制数据解析</span><br>    <span class="hljs-keyword">pub</span> <span class="hljs-keyword">fn</span> <span class="hljs-title function_">parse_from_data</span>(&amp;<span class="hljs-keyword">mut</span> <span class="hljs-keyword">self</span>, data: &amp;[<span class="hljs-type">u8</span>]) <span class="hljs-punctuation">-&gt;</span> <span class="hljs-type">bool</span> {<br>        <span class="hljs-keyword">let</span> <span class="hljs-variable">content</span> = <span class="hljs-type">String</span>::<span class="hljs-title function_ invoke__">from_utf8_lossy</span>(data);<br>        <span class="hljs-keyword">self</span>.<span class="hljs-title function_ invoke__">parse_from_content</span>(&amp;content)<br>    }<br><br>    <span class="hljs-comment">/// 从字符串内容解析</span><br>    <span class="hljs-keyword">fn</span> <span class="hljs-title function_">parse_from_content</span>(&amp;<span class="hljs-keyword">mut</span> <span class="hljs-keyword">self</span>, content: &amp;<span class="hljs-type">str</span>) <span class="hljs-punctuation">-&gt;</span> <span class="hljs-type">bool</span> {<br>        <span class="hljs-keyword">self</span>.partitions.<span class="hljs-title function_ invoke__">clear</span>();<br><br>        <span class="hljs-keyword">let</span> <span class="hljs-keyword">mut </span><span class="hljs-variable">in_partition_section</span> = <span class="hljs-literal">false</span>;<br>        <span class="hljs-keyword">let</span> <span class="hljs-keyword">mut </span><span class="hljs-variable">current_partition</span> = PartitionConfig::<span class="hljs-title function_ invoke__">default</span>();<br><br>        <span class="hljs-keyword">for</span> <span class="hljs-variable">line</span> <span class="hljs-keyword">in</span> content.<span class="hljs-title function_ invoke__">lines</span>() {<br>            <span class="hljs-keyword">let</span> <span class="hljs-variable">line</span> = line.<span class="hljs-title function_ invoke__">trim</span>();<br><br>            <span class="hljs-comment">// 跳过空行和注释</span><br>            <span class="hljs-keyword">if</span> line.<span class="hljs-title function_ invoke__">is_empty</span>() || line.<span class="hljs-title function_ invoke__">starts_with</span>(<span class="hljs-string">';'</span>) || line.<span class="hljs-title function_ invoke__">starts_with</span>(<span class="hljs-string">"//"</span>) {<br>                <span class="hljs-keyword">continue</span>;<br>            }<br><br>            <span class="hljs-comment">// 进入分区配置段</span><br>            <span class="hljs-keyword">if</span> line == <span class="hljs-string">"[partition_start]"</span> {<br>                in_partition_section = <span class="hljs-literal">true</span>;<br>                <span class="hljs-keyword">continue</span>;<br>            }<br><br>            <span class="hljs-comment">// 新分区定义</span><br>            <span class="hljs-keyword">if</span> line == <span class="hljs-string">"[partition]"</span> {<br>                <span class="hljs-comment">// 保存上一个分区</span><br>                <span class="hljs-keyword">if</span> !current_partition.name.<span class="hljs-title function_ invoke__">is_empty</span>() {<br>                    <span class="hljs-keyword">self</span>.partitions.<span class="hljs-title function_ invoke__">push</span>(current_partition.<span class="hljs-title function_ invoke__">clone</span>());<br>                }<br>                current_partition = PartitionConfig::<span class="hljs-title function_ invoke__">default</span>();<br>                in_partition_section = <span class="hljs-literal">true</span>;<br>                <span class="hljs-keyword">continue</span>;<br>            }<br><br>            <span class="hljs-comment">// 其他段标记，退出分区配置段</span><br>            <span class="hljs-keyword">if</span> line.<span class="hljs-title function_ invoke__">starts_with</span>(<span class="hljs-string">'['</span>) &amp;&amp; line.<span class="hljs-title function_ invoke__">ends_with</span>(<span class="hljs-string">']'</span>) {<br>                <span class="hljs-keyword">if</span> line != <span class="hljs-string">"[partition]"</span> &amp;&amp; line != <span class="hljs-string">"[partition_start]"</span> &amp;&amp; line != <span class="hljs-string">"[mbr]"</span> {<br>                    in_partition_section = <span class="hljs-literal">false</span>;<br>                }<br>                <span class="hljs-keyword">continue</span>;<br>            }<br><br>            <span class="hljs-comment">// 解析分区配置行</span><br>            <span class="hljs-keyword">if</span> in_partition_section {<br>                <span class="hljs-keyword">self</span>.<span class="hljs-title function_ invoke__">parse_partition_line</span>(line, &amp;<span class="hljs-keyword">mut</span> current_partition);<br>            }<br>        }<br><br>        <span class="hljs-comment">// 保存最后一个分区</span><br>        <span class="hljs-keyword">if</span> in_partition_section &amp;&amp; !current_partition.name.<span class="hljs-title function_ invoke__">is_empty</span>() {<br>            <span class="hljs-keyword">self</span>.partitions.<span class="hljs-title function_ invoke__">push</span>(current_partition);<br>        }<br><br>        <span class="hljs-literal">true</span><br>    }<br>}<br></code></pre></td></tr></tbody></table></figure><h3 id="配置行解析" data-id="配置行解析" class="notion-h"><a href="#配置行解析" class="headerlink" title="配置行解析"></a>配置行解析</h3><figure class="highlight rust"><table><tbody><tr><td class="code"><pre><code class="hljs rust"><span class="hljs-comment">/// 解析单行配置</span><br><span class="hljs-keyword">fn</span> <span class="hljs-title function_">parse_partition_line</span>(&amp;<span class="hljs-keyword">self</span>, line: &amp;<span class="hljs-type">str</span>, partition: &amp;<span class="hljs-keyword">mut</span> PartitionConfig) {<br>    <span class="hljs-keyword">let</span> <span class="hljs-variable">parts</span>: <span class="hljs-type">Vec</span>&lt;&amp;<span class="hljs-type">str</span>&gt; = line.<span class="hljs-title function_ invoke__">splitn</span>(<span class="hljs-number">2</span>, <span class="hljs-string">'='</span>).<span class="hljs-title function_ invoke__">collect</span>();<br>    <span class="hljs-keyword">if</span> parts.<span class="hljs-title function_ invoke__">len</span>() != <span class="hljs-number">2</span> {<br>        <span class="hljs-keyword">return</span>;<br>    }<br><br>    <span class="hljs-keyword">let</span> <span class="hljs-variable">key</span> = parts[<span class="hljs-number">0</span>].<span class="hljs-title function_ invoke__">trim</span>();<br>    <span class="hljs-keyword">let</span> <span class="hljs-variable">value</span> = parts[<span class="hljs-number">1</span>].<span class="hljs-title function_ invoke__">trim</span>();<br>    <span class="hljs-keyword">let</span> <span class="hljs-variable">value</span> = value.<span class="hljs-title function_ invoke__">trim_matches</span>(<span class="hljs-string">'"'</span>);  <span class="hljs-comment">// 移除引号</span><br><br>    <span class="hljs-keyword">match</span> key {<br>        <span class="hljs-string">"name"</span> =&gt; partition.name = value.<span class="hljs-title function_ invoke__">to_string</span>(),<br>        <span class="hljs-string">"size"</span> =&gt; {<br>            <span class="hljs-comment">// 支持十六进制和十进制</span><br>            partition.size = <span class="hljs-keyword">if</span> value.<span class="hljs-title function_ invoke__">starts_with</span>(<span class="hljs-string">"0x"</span>) || value.<span class="hljs-title function_ invoke__">starts_with</span>(<span class="hljs-string">"0X"</span>) {<br>                <span class="hljs-type">u64</span>::<span class="hljs-title function_ invoke__">from_str_radix</span>(&amp;value[<span class="hljs-number">2</span>..], <span class="hljs-number">16</span>).<span class="hljs-title function_ invoke__">unwrap_or</span>(<span class="hljs-number">0</span>)<br>            } <span class="hljs-keyword">else</span> {<br>                value.<span class="hljs-title function_ invoke__">parse</span>().<span class="hljs-title function_ invoke__">unwrap_or</span>(<span class="hljs-number">0</span>)<br>            }<br>        }<br>        <span class="hljs-string">"downloadfile"</span> =&gt; partition.downloadfile = value.<span class="hljs-title function_ invoke__">to_string</span>(),<br>        <span class="hljs-string">"user_type"</span> =&gt; {<br>            partition.user_type = <span class="hljs-keyword">if</span> value.<span class="hljs-title function_ invoke__">starts_with</span>(<span class="hljs-string">"0x"</span>) || value.<span class="hljs-title function_ invoke__">starts_with</span>(<span class="hljs-string">"0X"</span>) {<br>                <span class="hljs-type">u32</span>::<span class="hljs-title function_ invoke__">from_str_radix</span>(&amp;value[<span class="hljs-number">2</span>..], <span class="hljs-number">16</span>).<span class="hljs-title function_ invoke__">unwrap_or</span>(<span class="hljs-number">0</span>)<br>            } <span class="hljs-keyword">else</span> {<br>                value.<span class="hljs-title function_ invoke__">parse</span>().<span class="hljs-title function_ invoke__">unwrap_or</span>(<span class="hljs-number">0</span>)<br>            }<br>        }<br>        <span class="hljs-string">"keydata"</span> =&gt; partition.keydata = value != <span class="hljs-string">"0"</span>,<br>        <span class="hljs-string">"encrypt"</span> =&gt; partition.encrypt = value != <span class="hljs-string">"0"</span>,<br>        <span class="hljs-string">"verify"</span> =&gt; partition.verify = value != <span class="hljs-string">"0"</span>,<br>        <span class="hljs-string">"ro"</span> =&gt; partition.readonly = value != <span class="hljs-string">"0"</span>,<br>        _ =&gt; {}<br>    }<br>}<br></code></pre></td></tr></tbody></table></figure><h3 id="INI-配置示例" data-id="INI-配置示例" class="notion-h"><a href="#INI-配置示例" class="headerlink" title="INI 配置示例"></a>INI 配置示例</h3><figure class="highlight ini"><table><tbody><tr><td class="code"><pre><code class="hljs ini"><span class="hljs-comment">; 分区配置示例</span><br><br><span class="hljs-section">[partition_start]</span><br><span class="hljs-section">[partition]</span><br>    <span class="hljs-attr">name</span> = boot<br>    <span class="hljs-attr">size</span> = <span class="hljs-number">0</span>x100000<br>    <span class="hljs-attr">downloadfile</span> = <span class="hljs-string">"boot.fex"</span><br>    <span class="hljs-attr">user_type</span> = <span class="hljs-number">0</span>x8000<br>    <span class="hljs-attr">verify</span> = <span class="hljs-number">1</span><br><br><span class="hljs-section">[partition]</span><br>    <span class="hljs-attr">name</span> = rootfs<br>    <span class="hljs-attr">size</span> = <span class="hljs-number">0</span>x8000000<br>    <span class="hljs-attr">downloadfile</span> = <span class="hljs-string">"rootfs.fex"</span><br>    <span class="hljs-attr">user_type</span> = <span class="hljs-number">0</span>x8000<br>    <span class="hljs-attr">verify</span> = <span class="hljs-number">1</span><br><br><span class="hljs-section">[partition]</span><br>    <span class="hljs-attr">name</span> = userdata<br>    <span class="hljs-attr">size</span> = <span class="hljs-number">0</span>x10000000<br>    <span class="hljs-attr">downloadfile</span> = <span class="hljs-string">"userdata.fex"</span><br>    <span class="hljs-attr">user_type</span> = <span class="hljs-number">0</span>x8000<br>    <span class="hljs-attr">keydata</span> = <span class="hljs-number">0</span><br>    <span class="hljs-attr">encrypt</span> = <span class="hljs-number">0</span><br>    <span class="hljs-attr">verify</span> = <span class="hljs-number">1</span><br></code></pre></td></tr></tbody></table></figure><hr><h2 id="系统配置解析" data-id="系统配置解析" class="notion-h"><a href="#系统配置解析" class="headerlink" title="系统配置解析"></a>系统配置解析</h2><h3 id="DramParamInfo-结构" data-id="DramParamInfo-结构" class="notion-h"><a href="#DramParamInfo-结构" class="headerlink" title="DramParamInfo 结构"></a>DramParamInfo 结构</h3><figure class="highlight rust"><table><tbody><tr><td class="code"><pre><code class="hljs rust"><span class="hljs-comment">/// DRAM 参数信息</span><br><span class="hljs-comment">///</span><br><span class="hljs-comment">/// 包含 DRAM 初始化所需的 32 个参数</span><br><span class="hljs-meta">#[repr(C, packed)]</span><br><span class="hljs-meta">#[derive(Debug, Clone, Copy)]</span><br><span class="hljs-keyword">pub</span> <span class="hljs-keyword">struct</span> <span class="hljs-title class_">DramParamInfo</span> {<br>    <span class="hljs-comment">/// DRAM 初始化标志</span><br>    <span class="hljs-keyword">pub</span> dram_init_flag: <span class="hljs-type">u32</span>,<br><br>    <span class="hljs-comment">/// DRAM 更新标志</span><br>    <span class="hljs-keyword">pub</span> dram_update_flag: <span class="hljs-type">u32</span>,<br><br>    <span class="hljs-comment">/// DRAM 参数数组</span><br>    <span class="hljs-keyword">pub</span> dram_para: [<span class="hljs-type">u32</span>; <span class="hljs-number">32</span>],<br>}<br><br><span class="hljs-keyword">impl</span> <span class="hljs-title class_">DramParamInfo</span> {<br>    <span class="hljs-comment">/// 创建空参数</span><br>    <span class="hljs-keyword">pub</span> <span class="hljs-keyword">fn</span> <span class="hljs-title function_">create_empty</span>() <span class="hljs-punctuation">-&gt;</span> <span class="hljs-keyword">Self</span> {<br>        <span class="hljs-keyword">Self</span> {<br>            dram_init_flag: <span class="hljs-number">0</span>,<br>            dram_update_flag: <span class="hljs-number">0</span>,<br>            dram_para: [<span class="hljs-number">0u32</span>; <span class="hljs-number">32</span>],<br>        }<br>    }<br><br>    <span class="hljs-comment">/// 序列化为字节</span><br>    <span class="hljs-keyword">pub</span> <span class="hljs-keyword">fn</span> <span class="hljs-title function_">serialize</span>(&amp;<span class="hljs-keyword">self</span>) <span class="hljs-punctuation">-&gt;</span> <span class="hljs-type">Vec</span>&lt;<span class="hljs-type">u8</span>&gt; {<br>        <span class="hljs-keyword">let</span> <span class="hljs-variable">size</span> = std::mem::size_of::&lt;DramParamInfo&gt;();<br>        <span class="hljs-keyword">let</span> <span class="hljs-keyword">mut </span><span class="hljs-variable">data</span> = <span class="hljs-built_in">vec!</span>[<span class="hljs-number">0u8</span>; size];<br>        <span class="hljs-keyword">unsafe</span> {<br>            std::ptr::<span class="hljs-title function_ invoke__">copy_nonoverlapping</span>(<br>                <span class="hljs-keyword">self</span> <span class="hljs-keyword">as</span> *<span class="hljs-keyword">const</span> DramParamInfo <span class="hljs-keyword">as</span> *<span class="hljs-keyword">const</span> <span class="hljs-type">u8</span>,<br>                data.<span class="hljs-title function_ invoke__">as_mut_ptr</span>(),<br>                size,<br>            );<br>        }<br>        data<br>    }<br>}<br></code></pre></td></tr></tbody></table></figure><h3 id="SysConfigParser" data-id="SysConfigParser" class="notion-h"><a href="#SysConfigParser" class="headerlink" title="SysConfigParser"></a>SysConfigParser</h3><figure class="highlight rust"><table><tbody><tr><td class="code"><pre><code class="hljs rust"><span class="hljs-comment">/// 系统配置解析器</span><br><span class="hljs-keyword">pub</span> <span class="hljs-keyword">struct</span> <span class="hljs-title class_">SysConfigParser</span>;<br><br><span class="hljs-keyword">impl</span> <span class="hljs-title class_">SysConfigParser</span> {<br>    <span class="hljs-comment">/// 从原始数据解析</span><br>    <span class="hljs-keyword">pub</span> <span class="hljs-keyword">fn</span> <span class="hljs-title function_">parse</span>(data: &amp;[<span class="hljs-type">u8</span>]) <span class="hljs-punctuation">-&gt;</span> SysConfig {<br>        SysConfig {<br>            storage_type: <span class="hljs-keyword">Self</span>::<span class="hljs-title function_ invoke__">get_storage_type</span>(data),<br>        }<br>    }<br><br>    <span class="hljs-comment">/// 获取存储类型</span><br>    <span class="hljs-keyword">fn</span> <span class="hljs-title function_">get_storage_type</span>(data: &amp;[<span class="hljs-type">u8</span>]) <span class="hljs-punctuation">-&gt;</span> <span class="hljs-type">u32</span> {<br>        <span class="hljs-keyword">if</span> data.<span class="hljs-title function_ invoke__">len</span>() &lt; <span class="hljs-number">4</span> {<br>            <span class="hljs-keyword">return</span> <span class="hljs-number">0</span>;<br>        }<br>        <span class="hljs-keyword">let</span> <span class="hljs-variable">ptr</span> = data.<span class="hljs-title function_ invoke__">as_ptr</span>() <span class="hljs-keyword">as</span> *<span class="hljs-keyword">const</span> <span class="hljs-type">u32</span>;<br>        <span class="hljs-keyword">unsafe</span> { <span class="hljs-type">u32</span>::<span class="hljs-title function_ invoke__">from_le</span>(*ptr) }<br>    }<br><br>    <span class="hljs-comment">/// 从数值获取 StorageType</span><br>    <span class="hljs-keyword">pub</span> <span class="hljs-keyword">fn</span> <span class="hljs-title function_">get_storage_type_from_num</span>(num: <span class="hljs-type">u32</span>) <span class="hljs-punctuation">-&gt;</span> StorageType {<br>        StorageType::<span class="hljs-title function_ invoke__">from</span>(num)<br>    }<br>}<br><br><span class="hljs-comment">/// 系统配置数据</span><br><span class="hljs-meta">#[derive(Debug, Clone)]</span><br><span class="hljs-keyword">pub</span> <span class="hljs-keyword">struct</span> <span class="hljs-title class_">SysConfig</span> {<br>    <span class="hljs-comment">/// 存储类型（NAND, eMMC, SD 等）</span><br>    <span class="hljs-keyword">pub</span> storage_type: <span class="hljs-type">u32</span>,<br>}<br></code></pre></td></tr></tbody></table></figure><hr><h2 id="调用流程分析" data-id="调用流程分析" class="notion-h"><a href="#调用流程分析" class="headerlink" title="调用流程分析"></a>调用流程分析</h2><h3 id="MBR-解析流程" data-id="MBR-解析流程" class="notion-h"><a href="#MBR-解析流程" class="headerlink" title="MBR 解析流程"></a>MBR 解析流程</h3><figure class="highlight rust"><table><tbody><tr><td class="code"><pre><code class="hljs rust"><span class="hljs-comment">// 在刷写流程中解析 MBR</span><br><span class="hljs-keyword">fn</span> <span class="hljs-title function_">parse_mbr_for_flash</span>(packer: &amp;<span class="hljs-keyword">mut</span> OpenixPacker) <span class="hljs-punctuation">-&gt;</span> <span class="hljs-type">Result</span>&lt;MbrInfo, FlashError&gt; {<br>    <span class="hljs-comment">// 1. 从固件提取 MBR 数据</span><br>    <span class="hljs-keyword">let</span> <span class="hljs-variable">mbr_data</span> = packer.<span class="hljs-title function_ invoke__">get_mbr</span>()?;<br><br>    <span class="hljs-comment">// 2. 解析 MBR 结构</span><br>    <span class="hljs-keyword">let</span> <span class="hljs-variable">mbr</span> = SunxiMbr::<span class="hljs-title function_ invoke__">parse</span>(&amp;mbr_data)<br>        .<span class="hljs-title function_ invoke__">map_err</span>(|e| FlashError::MbrNotFound)?;<br><br>    <span class="hljs-comment">// 3. 转换为刷写信息</span><br>    <span class="hljs-keyword">let</span> <span class="hljs-variable">mbr_info</span> = mbr.<span class="hljs-title function_ invoke__">to_mbr_info</span>();<br><br>    <span class="hljs-comment">// 4. 验证分区数量</span><br>    <span class="hljs-keyword">if</span> mbr_info.part_count == <span class="hljs-number">0</span> {<br>        <span class="hljs-keyword">return</span> <span class="hljs-title function_ invoke__">Err</span>(FlashError::MbrNotFound);<br>    }<br><br>    <span class="hljs-comment">// 5. 打印分区信息</span><br>    <span class="hljs-keyword">for</span> <span class="hljs-variable">part</span> <span class="hljs-keyword">in</span> &amp;mbr_info.partitions {<br>        <span class="hljs-built_in">println!</span>(<br>            <span class="hljs-string">"分区: {} @ 0x{:08x} ({} bytes)"</span>,<br>            part.name,<br>            part.<span class="hljs-title function_ invoke__">address</span>(),<br>            part.<span class="hljs-title function_ invoke__">length</span>()<br>        );<br>    }<br><br>    <span class="hljs-title function_ invoke__">Ok</span>(mbr_info)<br>}<br></code></pre></td></tr></tbody></table></figure><h3 id="Boot-头解析流程" data-id="Boot-头解析流程" class="notion-h"><a href="#Boot-头解析流程" class="headerlink" title="Boot 头解析流程"></a>Boot 头解析流程</h3><figure class="highlight rust"><table><tbody><tr><td class="code"><pre><code class="hljs rust"><span class="hljs-comment">// 解析 U-Boot 头并修改工作模式</span><br><span class="hljs-keyword">fn</span> <span class="hljs-title function_">prepare_uboot_for_fel</span>(uboot_data: &amp;<span class="hljs-keyword">mut</span> [<span class="hljs-type">u8</span>]) <span class="hljs-punctuation">-&gt;</span> <span class="hljs-type">Result</span>&lt;(), FlashError&gt; {<br>    <span class="hljs-comment">// 1. 解析 U-Boot 头</span><br>    <span class="hljs-keyword">let</span> <span class="hljs-variable">header</span> = UBootHeader::<span class="hljs-title function_ invoke__">parse</span>(uboot_data)<br>        .<span class="hljs-title function_ invoke__">map_err</span>(|_| FlashError::UbootNotFound)?;<br><br>    <span class="hljs-comment">// 2. 验证魔数</span><br>    <span class="hljs-keyword">if</span> header.uboot_head.<span class="hljs-title function_ invoke__">magic_str</span>() != UBOOT_MAGIC {<br>        <span class="hljs-keyword">return</span> <span class="hljs-title function_ invoke__">Err</span>(FlashError::UbootNotFound);<br>    }<br><br>    <span class="hljs-comment">// 3. 设置工作模式为 USB 产品模式</span><br>    UBootHeader::<span class="hljs-title function_ invoke__">set_work_mode</span>(uboot_data, WORK_MODE_USB_PRODUCT);<br><br>    <span class="hljs-comment">// 4. 获取存储类型</span><br>    <span class="hljs-keyword">let</span> <span class="hljs-variable">storage_type</span> = StorageType::<span class="hljs-title function_ invoke__">from</span>(header.uboot_data.storage_<span class="hljs-keyword">type</span> <span class="hljs-title class_">as</span> <span class="hljs-type">u32</span>);<br>    <span class="hljs-built_in">println!</span>(<span class="hljs-string">"存储类型: {}"</span>, storage_type);<br><br>    <span class="hljs-title function_ invoke__">Ok</span>(())<br>}<br></code></pre></td></tr></tbody></table></figure><h3 id="分区配置解析流程" data-id="分区配置解析流程" class="notion-h"><a href="#分区配置解析流程" class="headerlink" title="分区配置解析流程"></a>分区配置解析流程</h3><figure class="highlight rust"><table><tbody><tr><td class="code"><pre><code class="hljs rust"><span class="hljs-comment">// 解析分区配置文件</span><br><span class="hljs-keyword">fn</span> <span class="hljs-title function_">parse_partition_config</span>(data: &amp;[<span class="hljs-type">u8</span>]) <span class="hljs-punctuation">-&gt;</span> <span class="hljs-type">Vec</span>&lt;PartitionConfig&gt; {<br>    <span class="hljs-keyword">let</span> <span class="hljs-keyword">mut </span><span class="hljs-variable">parser</span> = OpenixPartition::<span class="hljs-title function_ invoke__">new</span>();<br>    parser.<span class="hljs-title function_ invoke__">parse_from_data</span>(data);<br>    parser.<span class="hljs-title function_ invoke__">get_partitions</span>().<span class="hljs-title function_ invoke__">to_vec</span>()<br>}<br><br><span class="hljs-comment">// 示例输出</span><br><span class="hljs-keyword">fn</span> <span class="hljs-title function_">print_partitions</span>(partitions: &amp;[PartitionConfig]) {<br>    <span class="hljs-keyword">for</span> <span class="hljs-variable">part</span> <span class="hljs-keyword">in</span> partitions {<br>        <span class="hljs-built_in">println!</span>(<br>            <span class="hljs-string">"名称: {}, 大小: {} MB, 文件: {}"</span>,<br>            part.name,<br>            part.size / (<span class="hljs-number">1024</span> * <span class="hljs-number">1024</span>),<br>            part.downloadfile<br>        );<br>    }<br>}<br></code></pre></td></tr></tbody></table></figure><hr><h2 id="代码亮点" data-id="代码亮点" class="notion-h"><a href="#代码亮点" class="headerlink" title="代码亮点"></a>代码亮点</h2><h3 id="1-64-位地址的高低位拼接" data-id="1-64-位地址的高低位拼接" class="notion-h"><a href="#1-64-位地址的高低位拼接" class="headerlink" title="1. 64 位地址的高低位拼接"></a>1. 64 位地址的高低位拼接</h3><figure class="highlight rust"><table><tbody><tr><td class="code"><pre><code class="hljs rust"><span class="hljs-comment">/// 获取分区起始地址（64 位）</span><br><span class="hljs-keyword">pub</span> <span class="hljs-keyword">fn</span> <span class="hljs-title function_">address</span>(&amp;<span class="hljs-keyword">self</span>) <span class="hljs-punctuation">-&gt;</span> <span class="hljs-type">u64</span> {<br>    ((<span class="hljs-keyword">self</span>.addrhi <span class="hljs-keyword">as</span> <span class="hljs-type">u64</span>) &lt;&lt; <span class="hljs-number">32</span>) | (<span class="hljs-keyword">self</span>.addrlo <span class="hljs-keyword">as</span> <span class="hljs-type">u64</span>)<br>}<br></code></pre></td></tr></tbody></table></figure><p><strong>原理：</strong> 全志 MBR 使用 addrhi/addrlo 分离存储 64 位地址，需要手动拼接。</p><h3 id="2-段式-INI-解析" data-id="2-段式-INI-解析" class="notion-h"><a href="#2-段式-INI-解析" class="headerlink" title="2. 段式 INI 解析"></a>2. 段式 INI 解析</h3><figure class="highlight rust"><table><tbody><tr><td class="code"><pre><code class="hljs rust"><span class="hljs-comment">// 状态机式解析</span><br><span class="hljs-keyword">let</span> <span class="hljs-keyword">mut </span><span class="hljs-variable">in_partition_section</span> = <span class="hljs-literal">false</span>;<br><br><span class="hljs-keyword">for</span> <span class="hljs-variable">line</span> <span class="hljs-keyword">in</span> content.<span class="hljs-title function_ invoke__">lines</span>() {<br>    <span class="hljs-keyword">if</span> line == <span class="hljs-string">"[partition_start]"</span> {<br>        in_partition_section = <span class="hljs-literal">true</span>;  <span class="hljs-comment">// 进入分区段</span><br>        <span class="hljs-keyword">continue</span>;<br>    }<br><br>    <span class="hljs-keyword">if</span> line.<span class="hljs-title function_ invoke__">starts_with</span>(<span class="hljs-string">'['</span>) &amp;&amp; line != <span class="hljs-string">"[partition]"</span> {<br>        in_partition_section = <span class="hljs-literal">false</span>;  <span class="hljs-comment">// 退出分区段</span><br>        <span class="hljs-keyword">continue</span>;<br>    }<br><br>    <span class="hljs-keyword">if</span> in_partition_section {<br>        <span class="hljs-comment">// 只在分区段内解析配置行</span><br>        <span class="hljs-keyword">self</span>.<span class="hljs-title function_ invoke__">parse_partition_line</span>(line, &amp;<span class="hljs-keyword">mut</span> current_partition);<br>    }<br>}<br></code></pre></td></tr></tbody></table></figure><h3 id="3-动态工作模式设置" data-id="3-动态工作模式设置" class="notion-h"><a href="#3-动态工作模式设置" class="headerlink" title="3. 动态工作模式设置"></a>3. 动态工作模式设置</h3><figure class="highlight rust"><table><tbody><tr><td class="code"><pre><code class="hljs rust"><span class="hljs-comment">/// 在 U-Boot 数据中设置工作模式</span><br><span class="hljs-keyword">pub</span> <span class="hljs-keyword">fn</span> <span class="hljs-title function_">set_work_mode</span>(data: &amp;<span class="hljs-keyword">mut</span> [<span class="hljs-type">u8</span>], mode: <span class="hljs-type">u32</span>) {<br>    <span class="hljs-keyword">let</span> <span class="hljs-variable">data_offset</span> = std::mem::size_of::&lt;UBootBaseHeader&gt;();<br>    UBootDataHeader::<span class="hljs-title function_ invoke__">set_work_mode</span>(&amp;<span class="hljs-keyword">mut</span> data[data_offset..], mode);<br>}<br></code></pre></td></tr></tbody></table></figure><p><strong>用途：</strong> FEL 模式下需要设置 <code>work_mode = 0x10</code> 使 U-Boot 进入 USB 产品模式。</p><h3 id="4-序列化实现" data-id="4-序列化实现" class="notion-h"><a href="#4-序列化实现" class="headerlink" title="4. 序列化实现"></a>4. 序列化实现</h3><figure class="highlight rust"><table><tbody><tr><td class="code"><pre><code class="hljs rust"><span class="hljs-comment">/// 序列化结构体到字节</span><br><span class="hljs-keyword">pub</span> <span class="hljs-keyword">fn</span> <span class="hljs-title function_">serialize</span>(&amp;<span class="hljs-keyword">self</span>) <span class="hljs-punctuation">-&gt;</span> <span class="hljs-type">Vec</span>&lt;<span class="hljs-type">u8</span>&gt; {<br>    <span class="hljs-keyword">let</span> <span class="hljs-variable">size</span> = std::mem::size_of::&lt;DramParamInfo&gt;();<br>    <span class="hljs-keyword">let</span> <span class="hljs-keyword">mut </span><span class="hljs-variable">data</span> = <span class="hljs-built_in">vec!</span>[<span class="hljs-number">0u8</span>; size];<br>    <span class="hljs-keyword">unsafe</span> {<br>        std::ptr::<span class="hljs-title function_ invoke__">copy_nonoverlapping</span>(<br>            <span class="hljs-keyword">self</span> <span class="hljs-keyword">as</span> *<span class="hljs-keyword">const</span> DramParamInfo <span class="hljs-keyword">as</span> *<span class="hljs-keyword">const</span> <span class="hljs-type">u8</span>,<br>            data.<span class="hljs-title function_ invoke__">as_mut_ptr</span>(),<br>            size,<br>        );<br>    }<br>    data<br>}<br></code></pre></td></tr></tbody></table></figure><h3 id="5-十六进制数值解析" data-id="5-十六进制数值解析" class="notion-h"><a href="#5-十六进制数值解析" class="headerlink" title="5. 十六进制数值解析"></a>5. 十六进制数值解析</h3><figure class="highlight rust"><table><tbody><tr><td class="code"><pre><code class="hljs rust"><span class="hljs-comment">// 自动识别十六进制和十进制格式</span><br>partition.size = <span class="hljs-keyword">if</span> value.<span class="hljs-title function_ invoke__">starts_with</span>(<span class="hljs-string">"0x"</span>) || value.<span class="hljs-title function_ invoke__">starts_with</span>(<span class="hljs-string">"0X"</span>) {<br>    <span class="hljs-type">u64</span>::<span class="hljs-title function_ invoke__">from_str_radix</span>(&amp;value[<span class="hljs-number">2</span>..], <span class="hljs-number">16</span>).<span class="hljs-title function_ invoke__">unwrap_or</span>(<span class="hljs-number">0</span>)<br>} <span class="hljs-keyword">else</span> {<br>    value.<span class="hljs-title function_ invoke__">parse</span>().<span class="hljs-title function_ invoke__">unwrap_or</span>(<span class="hljs-number">0</span>)<br>};<br></code></pre></td></tr></tbody></table></figure><hr><h2 id="实践示例" data-id="实践示例" class="notion-h"><a href="#实践示例" class="headerlink" title="实践示例"></a>实践示例</h2><h3 id="解析固件中的-MBR" data-id="解析固件中的-MBR" class="notion-h"><a href="#解析固件中的-MBR" class="headerlink" title="解析固件中的 MBR"></a>解析固件中的 MBR</h3><figure class="highlight rust"><table><tbody><tr><td class="code"><pre><code class="hljs rust"><span class="hljs-keyword">use</span> openixcli::firmware::OpenixPacker;<br><span class="hljs-keyword">use</span> openixcli::config::mbr_parser::{SunxiMbr, MBR_SIZE};<br><br><span class="hljs-keyword">fn</span> <span class="hljs-title function_">main</span>() <span class="hljs-punctuation">-&gt;</span> <span class="hljs-type">Result</span>&lt;(), <span class="hljs-type">Box</span>&lt;<span class="hljs-keyword">dyn</span> std::error::Error&gt;&gt; {<br>    <span class="hljs-keyword">let</span> <span class="hljs-keyword">mut </span><span class="hljs-variable">packer</span> = OpenixPacker::<span class="hljs-title function_ invoke__">new</span>();<br>    packer.<span class="hljs-title function_ invoke__">load</span>(<span class="hljs-string">"firmware.fex"</span>)?;<br><br>    <span class="hljs-comment">// 获取 MBR 数据</span><br>    <span class="hljs-keyword">let</span> <span class="hljs-variable">mbr_data</span> = packer.<span class="hljs-title function_ invoke__">get_mbr</span>()?;<br>    <span class="hljs-built_in">println!</span>(<span class="hljs-string">"MBR 数据大小: {} bytes (预期 {} bytes)"</span>, mbr_data.<span class="hljs-title function_ invoke__">len</span>(), MBR_SIZE);<br><br>    <span class="hljs-comment">// 解析 MBR</span><br>    <span class="hljs-keyword">let</span> <span class="hljs-variable">mbr</span> = SunxiMbr::<span class="hljs-title function_ invoke__">parse</span>(&amp;mbr_data)?;<br><br>    <span class="hljs-built_in">println!</span>(<span class="hljs-string">"MBR 魂数: {}"</span>, mbr.magic);<br>    <span class="hljs-built_in">println!</span>(<span class="hljs-string">"MBR 版本: 0x{:08x}"</span>, mbr.version);<br>    <span class="hljs-built_in">println!</span>(<span class="hljs-string">"分区数量: {}"</span>, mbr.part_count);<br><br>    <span class="hljs-comment">// 打印所有分区</span><br>    <span class="hljs-built_in">println!</span>(<span class="hljs-string">"\n分区列表:"</span>);<br>    <span class="hljs-built_in">println!</span>(<span class="hljs-string">"{:-&lt;60}"</span>, <span class="hljs-string">""</span>);<br>    <span class="hljs-built_in">println!</span>(<span class="hljs-string">"{:16} {:12} {:12}"</span>, <span class="hljs-string">"名称"</span>, <span class="hljs-string">"起始地址"</span>, <span class="hljs-string">"大小"</span>);<br>    <span class="hljs-built_in">println!</span>(<span class="hljs-string">"{:-&lt;60}"</span>, <span class="hljs-string">""</span>);<br><br>    <span class="hljs-keyword">for</span> <span class="hljs-variable">part</span> <span class="hljs-keyword">in</span> &amp;mbr.partitions {<br>        <span class="hljs-built_in">println!</span>(<br>            <span class="hljs-string">"{:16} 0x{:010x}  {} MB"</span>,<br>            part.name,<br>            part.<span class="hljs-title function_ invoke__">address</span>(),<br>            part.<span class="hljs-title function_ invoke__">length</span>() / (<span class="hljs-number">1024</span> * <span class="hljs-number">1024</span>)<br>        );<br>    }<br><br>    <span class="hljs-title function_ invoke__">Ok</span>(())<br>}<br></code></pre></td></tr></tbody></table></figure><p><strong>输出示例：</strong></p><figure class="highlight plaintext"><table><tbody><tr><td class="code"><pre><code class="hljs plaintext">MBR 数据大小: 16384 bytes (预期 16384 bytes)<br>MBR 魂数: softw411<br>MBR 版本: 0x00000200<br>分区数量: 8<br><br>分区列表:<br>------------------------------------------------------------<br>名称             起始地址     大小<br>------------------------------------------------------------<br>boot             0x00000000   1 MB<br>env              0x00100000   128 KB<br>rootfs           0x00120000   128 MB<br>userdata         0x08120000   256 MB<br>misc             0x10120000   1 MB<br>recovery         0x10220000   32 MB<br>...<br></code></pre></td></tr></tbody></table></figure><h3 id="解析-Boot0-头" data-id="解析-Boot0-头" class="notion-h"><a href="#解析-Boot0-头" class="headerlink" title="解析 Boot0 头"></a>解析 Boot0 头</h3><figure class="highlight rust"><table><tbody><tr><td class="code"><pre><code class="hljs rust"><span class="hljs-keyword">use</span> openixcli::config::boot_header::{Boot0Header, BOOT0_MAGIC};<br><br><span class="hljs-keyword">fn</span> <span class="hljs-title function_">analyze_boot0</span>(boot0_data: &amp;[<span class="hljs-type">u8</span>]) {<br>    <span class="hljs-keyword">let</span> <span class="hljs-variable">header</span> = Boot0Header::<span class="hljs-title function_ invoke__">parse</span>(boot0_data).<span class="hljs-title function_ invoke__">unwrap</span>();<br><br>    <span class="hljs-built_in">println!</span>(<span class="hljs-string">"Boot0 分析:"</span>);<br>    <span class="hljs-built_in">println!</span>(<span class="hljs-string">"  魂数: {} ({})"</span>, header.<span class="hljs-title function_ invoke__">magic_str</span>(),<br>        <span class="hljs-keyword">if</span> header.<span class="hljs-title function_ invoke__">magic_str</span>() == BOOT0_MAGIC { <span class="hljs-string">"有效"</span> } <span class="hljs-keyword">else</span> { <span class="hljs-string">"无效"</span> });<br>    <span class="hljs-built_in">println!</span>(<span class="hljs-string">"  平台: {}"</span>, header.<span class="hljs-title function_ invoke__">platform_str</span>());<br>    <span class="hljs-built_in">println!</span>(<span class="hljs-string">"  镜像长度: {} bytes"</span>, header.length);<br>    <span class="hljs-built_in">println!</span>(<span class="hljs-string">"  运行地址: 0x{:08x}"</span>, header.run_addr);<br>    <span class="hljs-built_in">println!</span>(<span class="hljs-string">"  校验和: 0x{:08x}"</span>, header.check_sum);<br>}<br></code></pre></td></tr></tbody></table></figure><h3 id="解析分区配置" data-id="解析分区配置" class="notion-h"><a href="#解析分区配置" class="headerlink" title="解析分区配置"></a>解析分区配置</h3><figure class="highlight rust"><table><tbody><tr><td class="code"><pre><code class="hljs rust"><span class="hljs-keyword">use</span> openixcli::config::partition::OpenixPartition;<br><br><span class="hljs-keyword">fn</span> <span class="hljs-title function_">parse_config</span>(config_data: &amp;[<span class="hljs-type">u8</span>]) {<br>    <span class="hljs-keyword">let</span> <span class="hljs-keyword">mut </span><span class="hljs-variable">parser</span> = OpenixPartition::<span class="hljs-title function_ invoke__">new</span>();<br>    parser.<span class="hljs-title function_ invoke__">parse_from_data</span>(config_data);<br><br>    <span class="hljs-built_in">println!</span>(<span class="hljs-string">"分区配置:"</span>);<br>    <span class="hljs-keyword">for</span> <span class="hljs-variable">part</span> <span class="hljs-keyword">in</span> parser.<span class="hljs-title function_ invoke__">get_partitions</span>() {<br>        <span class="hljs-built_in">println!</span>(<span class="hljs-string">"\n  分区: {}"</span>, part.name);<br>        <span class="hljs-built_in">println!</span>(<span class="hljs-string">"    大小: {} bytes ({} KB)"</span>, part.size, part.size / <span class="hljs-number">1024</span>);<br>        <span class="hljs-built_in">println!</span>(<span class="hljs-string">"    文件: {}"</span>, part.downloadfile);<br>        <span class="hljs-built_in">println!</span>(<span class="hljs-string">"    验证: {}"</span>, part.verify);<br>        <span class="hljs-built_in">println!</span>(<span class="hljs-string">"    只读: {}"</span>, part.readonly);<br>        <span class="hljs-keyword">if</span> part.keydata {<br>            <span class="hljs-built_in">println!</span>(<span class="hljs-string">"    包含关键数据"</span>);<br>        }<br>    }<br>}<br></code></pre></td></tr></tbody></table></figure><hr><h2 id="数据结构关系图" data-id="数据结构关系图" class="notion-h"><a href="#数据结构关系图" class="headerlink" title="数据结构关系图"></a>数据结构关系图</h2><pre class="mermaid">classDiagram    class Boot0Header {        +jump_instruction: u32        +magic: [u8; 8]        +check_sum: u32        +length: u32        +run_addr: u32        +platform: [u8; 8]        +parse(data)        +magic_str() String    }    class UBootHeader {        +uboot_head: UBootBaseHeader        +uboot_data: UBootDataHeader        +parse(data)        +set_work_mode(data, mode)    }    class UBootDataHeader {        +dram_para: [u32; 32]        +run_clock: i32        +uart_port: i32        +twi_port: i32        +work_mode: i32        +storage_type: i32    }    class SunxiMbr {        +crc32: u32        +version: u32        +magic: String        +part_count: u32        +partitions: Vec~SunxiPartition~        +parse(data)        +to_mbr_info() MbrInfo    }    class SunxiPartition {        +addrhi: u32        +addrlo: u32        +lenhi: u32        +lenlo: u32        +name: String        +user_type: u32        +address() u64        +length() u64    }    class PartitionConfig {        +name: String        +size: u64        +downloadfile: String        +verify: bool        +readonly: bool    }    class OpenixPartition {        +partitions: Vec~PartitionConfig~        +parse_from_data(data)        +get_partitions()    }    UBootHeader --&gt; UBootBaseHeader    UBootHeader --&gt; UBootDataHeader    SunxiMbr --&gt; SunxiPartition    OpenixPartition --&gt; PartitionConfig</pre>]]>
    </content>
    <id>https://gloomyghost.com/posts/openixcli-config-module/</id>
    <link href="https://gloomyghost.com/posts/openixcli-config-module/"/>
    <published>2026-04-25T16:00:00.000Z</published>
    <summary>
      <![CDATA[<h2 id="概述" data-id="概述" class="notion-h"><a href="#概述" class="headerlink" title="概述"></a>概述</h2><p>嵌入式固件刷写涉及多种配置格式的解析：Boot 头（boot0/U-Boot）、]]>
    </summary>
    <title>OpenixCLI Config 模块深度解析：Boot 头、MBR 与配置解析</title>
    <updated>2026-05-29T20:33:31.219Z</updated>
  </entry>
  <entry>
    <author>
      <name>柚木 鉉</name>
    </author>
    <category term="Rust" scheme="https://gloomyghost.com/tags/Rust/"/>
    <category term="嵌入式" scheme="https://gloomyghost.com/tags/%E5%B5%8C%E5%85%A5%E5%BC%8F/"/>
    <category term="OpenixCLI" scheme="https://gloomyghost.com/tags/OpenixCLI/"/>
    <category term="进度追踪" scheme="https://gloomyghost.com/tags/%E8%BF%9B%E5%BA%A6%E8%BF%BD%E8%B8%AA/"/>
    <category term="多线程" scheme="https://gloomyghost.com/tags/%E5%A4%9A%E7%BA%BF%E7%A8%8B/"/>
    <content>
      <![CDATA[<h2 id="概述" data-id="概述" class="notion-h"><a href="#概述" class="headerlink" title="概述"></a>概述</h2><p>固件刷写是一个多阶段的复杂过程，涉及设备检测、DRAM 初始化、U-Boot 下载、分区刷写等步骤。OpenixCLI 的 Process 模块提供全局进度追踪系统，使用原子操作实现线程安全的状态管理，同时支持 CLI 进度条和 TUI 实时轮询两种模式。</p><h3 id="模块结构" data-id="模块结构" class="notion-h"><a href="#模块结构" class="headerlink" title="模块结构"></a>模块结构</h3><figure class="highlight plaintext"><table><tbody><tr><td class="code"><pre><code class="hljs plaintext">src/process/<br>├── mod.rs           # 模块导出<br>├── global_progress.rs # GlobalProgress 核心实现<br>├── stages.rs        # FlashStages 阶段定义<br>└── reporter.rs      # ProgressReporter 简化封装<br></code></pre></td></tr></tbody></table></figure><h3 id="核心导出" data-id="核心导出" class="notion-h"><a href="#核心导出" class="headerlink" title="核心导出"></a>核心导出</h3><figure class="highlight rust"><table><tbody><tr><td class="code"><pre><code class="hljs rust"><span class="hljs-keyword">pub</span> <span class="hljs-keyword">use</span> global_progress::{multi_progress, StageType};<br><span class="hljs-keyword">pub</span> <span class="hljs-keyword">use</span> reporter::ProgressReporter;<br><span class="hljs-keyword">pub</span> <span class="hljs-keyword">use</span> stages::FlashStages;<br></code></pre></td></tr></tbody></table></figure><hr><h2 id="架构与依赖" data-id="架构与依赖" class="notion-h"><a href="#架构与依赖" class="headerlink" title="架构与依赖"></a>架构与依赖</h2><h3 id="双模式进度显示" data-id="双模式进度显示" class="notion-h"><a href="#双模式进度显示" class="headerlink" title="双模式进度显示"></a>双模式进度显示</h3><table><thead><tr><th>模式</th><th>进度显示</th><th>状态同步</th><th>适用场景</th></tr></thead><tbody><tr><td>CLI</td><td>indicatif ProgressBar</td><td>实时更新</td><td>命令行刷写</td></tr><tr><td>TUI</td><td>ProgressSnapshot 轮询</td><td>定时获取</td><td>交互界面</td></tr></tbody></table><h3 id="模块依赖关系" data-id="模块依赖关系" class="notion-h"><a href="#模块依赖关系" class="headerlink" title="模块依赖关系"></a>模块依赖关系</h3><figure class="highlight plaintext"><table><tbody><tr><td class="code"><pre><code class="hljs plaintext">Process Module<br>├── indicatif (外部 crate) - 进度条<br>├── once_cell (外部 crate) - 全局状态延迟初始化<br>├── std::sync::atomic - AtomicU64/AtomicBool 线程安全计数器<br>├── std::sync::Mutex - 复合类型互斥保护<br>└── std::time::Instant - 速度计算时间戳<br></code></pre></td></tr></tbody></table></figure><hr><h2 id="核心数据结构" data-id="核心数据结构" class="notion-h"><a href="#核心数据结构" class="headerlink" title="核心数据结构"></a>核心数据结构</h2><h3 id="StageType-阶段枚举" data-id="StageType-阶段枚举" class="notion-h"><a href="#StageType-阶段枚举" class="headerlink" title="StageType 阶段枚举"></a>StageType 阶段枚举</h3><p>定义刷写过程中的所有阶段：</p><figure class="highlight rust"><table><tbody><tr><td class="code"><pre><code class="hljs rust"><span class="hljs-comment">/// 刷写操作阶段类型</span><br><span class="hljs-meta">#[derive(Debug, Clone, Copy, PartialEq, Eq)]</span><br><span class="hljs-keyword">pub</span> <span class="hljs-keyword">enum</span> <span class="hljs-title class_">StageType</span> {<br>    <span class="hljs-comment">/// 初始化阶段</span><br>    Init,<br><br>    <span class="hljs-comment">/// FEL 模式 DRAM 初始化</span><br>    FelDram,<br><br>    <span class="hljs-comment">/// FEL 模式 U-Boot 下载</span><br>    FelUboot,<br><br>    <span class="hljs-comment">/// FEL 后设备重连</span><br>    FelReconnect,<br><br>    <span class="hljs-comment">/// FES 模式设备查询</span><br>    FesQuery,<br><br>    <span class="hljs-comment">/// Flash 擦除</span><br>    FesErase,<br><br>    <span class="hljs-comment">/// MBR 写入</span><br>    FesMbr,<br><br>    <span class="hljs-comment">/// 分区刷写</span><br>    FesPartitions,<br><br>    <span class="hljs-comment">/// Boot 镜像写入</span><br>    FesBoot,<br><br>    <span class="hljs-comment">/// 设置设备模式（重启/关机）</span><br>    FesMode,<br>}<br><br><span class="hljs-keyword">impl</span> <span class="hljs-title class_">StageType</span> {<br>    <span class="hljs-comment">/// 获取阶段名称</span><br>    <span class="hljs-keyword">pub</span> <span class="hljs-keyword">fn</span> <span class="hljs-title function_">name</span>(&amp;<span class="hljs-keyword">self</span>) <span class="hljs-punctuation">-&gt;</span> &amp;<span class="hljs-symbol">'static</span> <span class="hljs-type">str</span> {<br>        <span class="hljs-keyword">match</span> <span class="hljs-keyword">self</span> {<br>            StageType::Init =&gt; <span class="hljs-string">"Initializing"</span>,<br>            StageType::FelDram =&gt; <span class="hljs-string">"DRAM Init"</span>,<br>            StageType::FelUboot =&gt; <span class="hljs-string">"U-Boot Download"</span>,<br>            StageType::FelReconnect =&gt; <span class="hljs-string">"Reconnecting"</span>,<br>            StageType::FesQuery =&gt; <span class="hljs-string">"Query Device"</span>,<br>            StageType::FesErase =&gt; <span class="hljs-string">"Erasing"</span>,<br>            StageType::FesMbr =&gt; <span class="hljs-string">"Writing MBR"</span>,<br>            StageType::FesPartitions =&gt; <span class="hljs-string">"Flashing Partitions"</span>,<br>            StageType::FesBoot =&gt; <span class="hljs-string">"Writing Boot"</span>,<br>            StageType::FesMode =&gt; <span class="hljs-string">"Setting Mode"</span>,<br>        }<br>    }<br>}<br></code></pre></td></tr></tbody></table></figure><h3 id="GlobalProgress-结构" data-id="GlobalProgress-结构" class="notion-h"><a href="#GlobalProgress-结构" class="headerlink" title="GlobalProgress 结构"></a>GlobalProgress 结构</h3><p>全局进度追踪器，使用原子类型实现线程安全：</p><figure class="highlight rust"><table><tbody><tr><td class="code"><pre><code class="hljs rust"><span class="hljs-comment">/// 全局进度追踪器</span><br><span class="hljs-keyword">pub</span> <span class="hljs-keyword">struct</span> <span class="hljs-title class_">GlobalProgress</span> {<br>    <span class="hljs-comment">/// indicatif 进度条（CLI 模式）</span><br>    progress_bar: Mutex&lt;<span class="hljs-type">Option</span>&lt;ProgressBar&gt;&gt;,<br><br>    <span class="hljs-comment">/// 总权重（百分比基准）</span><br>    total_weight: AtomicU64,<br><br>    <span class="hljs-comment">/// 已完成权重</span><br>    completed_weight: AtomicU64,<br><br>    <span class="hljs-comment">/// 当前阶段索引</span><br>    current_stage: AtomicUsize,<br><br>    <span class="hljs-comment">/// 阶段内进度（字节）</span><br>    stage_progress: AtomicU64,<br><br>    <span class="hljs-comment">/// 分区总字节数</span><br>    total_bytes: AtomicU64,<br><br>    <span class="hljs-comment">/// 全局已写入字节数</span><br>    global_written_bytes: AtomicU64,<br><br>    <span class="hljs-comment">/// 阶段信息列表</span><br>    stages: Mutex&lt;<span class="hljs-type">Vec</span>&lt;StageInfo&gt;&gt;,<br><br>    <span class="hljs-comment">/// 是否已启动（0=未启动，1=已启动）</span><br>    started: AtomicUsize,<br><br>    <span class="hljs-comment">/// 当前分区名称</span><br>    current_partition: Mutex&lt;<span class="hljs-type">String</span>&gt;,<br><br>    <span class="hljs-comment">/// 上次更新时间（速度计算）</span><br>    last_update_time: Mutex&lt;<span class="hljs-type">Option</span>&lt;Instant&gt;&gt;,<br><br>    <span class="hljs-comment">/// 上次更新的字节数</span><br>    last_update_bytes: AtomicU64,<br><br>    <span class="hljs-comment">/// 当前传输速度</span><br>    current_speed: Mutex&lt;<span class="hljs-type">f64</span>&gt;,<br><br>    <span class="hljs-comment">/// 精确进度百分比</span><br>    precise_progress: Mutex&lt;<span class="hljs-type">f64</span>&gt;,<br>}<br></code></pre></td></tr></tbody></table></figure><h3 id="StageInfo-结构" data-id="StageInfo-结构" class="notion-h"><a href="#StageInfo-结构" class="headerlink" title="StageInfo 结构"></a>StageInfo 结构</h3><figure class="highlight rust"><table><tbody><tr><td class="code"><pre><code class="hljs rust"><span class="hljs-comment">/// 阶段信息</span><br><span class="hljs-meta">#[derive(Debug, Clone)]</span><br><span class="hljs-keyword">pub</span> <span class="hljs-keyword">struct</span> <span class="hljs-title class_">StageInfo</span> {<br>    <span class="hljs-comment">/// 阶段类型</span><br>    <span class="hljs-keyword">pub</span> stage_type: StageType,<br><br>    <span class="hljs-comment">/// 阶段权重（百分比）</span><br>    <span class="hljs-keyword">pub</span> weight: <span class="hljs-type">u64</span>,<br><br>    <span class="hljs-comment">/// 是否已完成</span><br>    <span class="hljs-keyword">pub</span> completed: <span class="hljs-type">bool</span>,<br><br>    <span class="hljs-comment">/// 子任务总量（字节）</span><br>    <span class="hljs-keyword">pub</span> sub_total: <span class="hljs-type">u64</span>,<br>}<br></code></pre></td></tr></tbody></table></figure><h3 id="ProgressSnapshot-结构" data-id="ProgressSnapshot-结构" class="notion-h"><a href="#ProgressSnapshot-结构" class="headerlink" title="ProgressSnapshot 结构"></a>ProgressSnapshot 结构</h3><p>用于 TUI 模式定时轮询进度状态：</p><figure class="highlight rust"><table><tbody><tr><td class="code"><pre><code class="hljs rust"><span class="hljs-comment">/// 进度状态快照（供 TUI 轮询）</span><br><span class="hljs-keyword">pub</span> <span class="hljs-keyword">struct</span> <span class="hljs-title class_">ProgressSnapshot</span> {<br>    <span class="hljs-comment">/// 精确进度百分比</span><br>    <span class="hljs-keyword">pub</span> precise_progress: <span class="hljs-type">f64</span>,<br><br>    <span class="hljs-comment">/// 阶段内进度</span><br>    <span class="hljs-keyword">pub</span> stage_progress: <span class="hljs-type">u64</span>,<br><br>    <span class="hljs-comment">/// 总字节数</span><br>    <span class="hljs-keyword">pub</span> total_bytes: <span class="hljs-type">u64</span>,<br><br>    <span class="hljs-comment">/// 当前速度</span><br>    <span class="hljs-keyword">pub</span> speed: <span class="hljs-type">f64</span>,<br><br>    <span class="hljs-comment">/// 当前分区名称</span><br>    <span class="hljs-keyword">pub</span> current_partition: <span class="hljs-type">String</span>,<br><br>    <span class="hljs-comment">/// 当前阶段索引</span><br>    <span class="hljs-keyword">pub</span> current_stage_index: <span class="hljs-type">usize</span>,<br><br>    <span class="hljs-comment">/// 阶段信息列表</span><br>    <span class="hljs-keyword">pub</span> stages: <span class="hljs-type">Vec</span>&lt;StageInfo&gt;,<br>}<br></code></pre></td></tr></tbody></table></figure><hr><h2 id="全局状态管理" data-id="全局状态管理" class="notion-h"><a href="#全局状态管理" class="headerlink" title="全局状态管理"></a>全局状态管理</h2><h3 id="全局实例" data-id="全局实例" class="notion-h"><a href="#全局实例" class="headerlink" title="全局实例"></a>全局实例</h3><figure class="highlight rust"><table><tbody><tr><td class="code"><pre><code class="hljs rust"><span class="hljs-keyword">use</span> once_cell::sync::Lazy;<br><span class="hljs-keyword">use</span> std::sync::Arc;<br><br><span class="hljs-comment">/// 全局 MultiProgress 实例（indicatif）</span><br><span class="hljs-keyword">static</span> MULTI_PROGRESS: Lazy&lt;Arc&lt;MultiProgress&gt;&gt; =<br>    Lazy::<span class="hljs-title function_ invoke__">new</span>(|| Arc::<span class="hljs-title function_ invoke__">new</span>(MultiProgress::<span class="hljs-title function_ invoke__">new</span>()));<br><br><span class="hljs-comment">/// 获取 MultiProgress 实例</span><br><span class="hljs-keyword">pub</span> <span class="hljs-keyword">fn</span> <span class="hljs-title function_">multi_progress</span>() <span class="hljs-punctuation">-&gt;</span> Arc&lt;MultiProgress&gt; {<br>    Arc::<span class="hljs-title function_ invoke__">clone</span>(&amp;MULTI_PROGRESS)<br>}<br><br><span class="hljs-comment">/// 全局进度追踪器实例</span><br><span class="hljs-keyword">static</span> GLOBAL_PROGRESS: Lazy&lt;Arc&lt;GlobalProgress&gt;&gt; =<br>    Lazy::<span class="hljs-title function_ invoke__">new</span>(|| Arc::<span class="hljs-title function_ invoke__">new</span>(GlobalProgress::<span class="hljs-title function_ invoke__">new</span>()));<br><br><span class="hljs-comment">/// 获取全局进度追踪器</span><br><span class="hljs-keyword">pub</span> <span class="hljs-keyword">fn</span> <span class="hljs-title function_">global_progress</span>() <span class="hljs-punctuation">-&gt;</span> Arc&lt;GlobalProgress&gt; {<br>    Arc::<span class="hljs-title function_ invoke__">clone</span>(&amp;GLOBAL_PROGRESS)<br>}<br></code></pre></td></tr></tbody></table></figure><h3 id="TUI-模式控制" data-id="TUI-模式控制" class="notion-h"><a href="#TUI-模式控制" class="headerlink" title="TUI 模式控制"></a>TUI 模式控制</h3><figure class="highlight rust"><table><tbody><tr><td class="code"><pre><code class="hljs rust"><span class="hljs-comment">/// TUI 模式标志</span><br><span class="hljs-keyword">static</span> TUI_MODE: AtomicBool = AtomicBool::<span class="hljs-title function_ invoke__">new</span>(<span class="hljs-literal">false</span>);<br><br><span class="hljs-comment">/// 设置 TUI 模式</span><br><span class="hljs-keyword">pub</span> <span class="hljs-keyword">fn</span> <span class="hljs-title function_">set_tui_mode</span>(enabled: <span class="hljs-type">bool</span>) {<br>    TUI_MODE.<span class="hljs-title function_ invoke__">store</span>(enabled, Ordering::SeqCst);<br>}<br><br><span class="hljs-comment">/// 检查是否为 TUI 模式</span><br><span class="hljs-keyword">pub</span> <span class="hljs-keyword">fn</span> <span class="hljs-title function_">is_tui_mode</span>() <span class="hljs-punctuation">-&gt;</span> <span class="hljs-type">bool</span> {<br>    TUI_MODE.<span class="hljs-title function_ invoke__">load</span>(Ordering::SeqCst)<br>}<br></code></pre></td></tr></tbody></table></figure><hr><h2 id="FlashStages-阶段序列" data-id="FlashStages-阶段序列" class="notion-h"><a href="#FlashStages-阶段序列" class="headerlink" title="FlashStages 阶段序列"></a>FlashStages 阶段序列</h2><h3 id="FEL-模式阶段序列" data-id="FEL-模式阶段序列" class="notion-h"><a href="#FEL-模式阶段序列" class="headerlink" title="FEL 模式阶段序列"></a>FEL 模式阶段序列</h3><figure class="highlight rust"><table><tbody><tr><td class="code"><pre><code class="hljs rust"><span class="hljs-comment">/// FEL 模式阶段（USB 启动）</span><br><span class="hljs-keyword">pub</span> <span class="hljs-keyword">fn</span> <span class="hljs-title function_">for_fel_mode</span>() <span class="hljs-punctuation">-&gt;</span> <span class="hljs-keyword">Self</span> {<br>    <span class="hljs-keyword">let</span> <span class="hljs-keyword">mut </span><span class="hljs-variable">instance</span> = <span class="hljs-keyword">Self</span>::<span class="hljs-title function_ invoke__">new</span>();<br>    instance.stages = <span class="hljs-built_in">vec!</span>[<br>        StageType::Init,           <span class="hljs-comment">// 初始化</span><br>        StageType::FelDram,        <span class="hljs-comment">// DRAM 初始化</span><br>        StageType::FelUboot,       <span class="hljs-comment">// U-Boot 下载</span><br>        StageType::FelReconnect,   <span class="hljs-comment">// 设备重连</span><br>        StageType::FesQuery,       <span class="hljs-comment">// 设备查询</span><br>        StageType::FesErase,       <span class="hljs-comment">// Flash 擦除</span><br>        StageType::FesMbr,         <span class="hljs-comment">// MBR 写入</span><br>        StageType::FesPartitions,  <span class="hljs-comment">// 分区刷写</span><br>        StageType::FesBoot,        <span class="hljs-comment">// Boot 写入</span><br>        StageType::FesMode,        <span class="hljs-comment">// 设置模式</span><br>    ];<br>    instance<br>}<br></code></pre></td></tr></tbody></table></figure><h3 id="FES-模式阶段序列" data-id="FES-模式阶段序列" class="notion-h"><a href="#FES-模式阶段序列" class="headerlink" title="FES 模式阶段序列"></a>FES 模式阶段序列</h3><figure class="highlight rust"><table><tbody><tr><td class="code"><pre><code class="hljs rust"><span class="hljs-comment">/// FES 模式阶段（U-Boot）</span><br><span class="hljs-keyword">pub</span> <span class="hljs-keyword">fn</span> <span class="hljs-title function_">for_fes_mode</span>() <span class="hljs-punctuation">-&gt;</span> <span class="hljs-keyword">Self</span> {<br>    <span class="hljs-keyword">let</span> <span class="hljs-keyword">mut </span><span class="hljs-variable">instance</span> = <span class="hljs-keyword">Self</span>::<span class="hljs-title function_ invoke__">new</span>();<br>    instance.stages = <span class="hljs-built_in">vec!</span>[<br>        StageType::Init,           <span class="hljs-comment">// 初始化</span><br>        StageType::FesQuery,       <span class="hljs-comment">// 设备查询</span><br>        StageType::FesErase,       <span class="hljs-comment">// Flash 擦除</span><br>        StageType::FesMbr,         <span class="hljs-comment">// MBR 写入</span><br>        StageType::FesPartitions,  <span class="hljs-comment">// 分区刷写</span><br>        StageType::FesBoot,        <span class="hljs-comment">// Boot 写入</span><br>        StageType::FesMode,        <span class="hljs-comment">// 设置模式</span><br>    ];<br>    instance<br>}<br></code></pre></td></tr></tbody></table></figure><hr><h2 id="阶段进度计算" data-id="阶段进度计算" class="notion-h"><a href="#阶段进度计算" class="headerlink" title="阶段进度计算"></a>阶段进度计算</h2><h3 id="预定义权重分配" data-id="预定义权重分配" class="notion-h"><a href="#预定义权重分配" class="headerlink" title="预定义权重分配"></a>预定义权重分配</h3><figure class="highlight rust"><table><tbody><tr><td class="code"><pre><code class="hljs rust"><span class="hljs-comment">/// 定义阶段（设置权重百分比）</span><br><span class="hljs-keyword">pub</span> <span class="hljs-keyword">fn</span> <span class="hljs-title function_">define_stages</span>(&amp;<span class="hljs-keyword">self</span>, stage_types: &amp;[StageType]) {<br>    <span class="hljs-keyword">let</span> <span class="hljs-keyword">mut </span><span class="hljs-variable">stages</span> = <span class="hljs-keyword">self</span>.stages.<span class="hljs-title function_ invoke__">lock</span>().<span class="hljs-title function_ invoke__">unwrap</span>();<br>    stages.<span class="hljs-title function_ invoke__">clear</span>();<br><br>    <span class="hljs-keyword">let</span> <span class="hljs-keyword">mut </span><span class="hljs-variable">cumulative_percent</span> = <span class="hljs-number">0u64</span>;<br>    <span class="hljs-keyword">for</span> <span class="hljs-variable">stage_type</span> <span class="hljs-keyword">in</span> stage_types {<br>        <span class="hljs-comment">// 每个阶段占用的百分比</span><br>        <span class="hljs-keyword">let</span> <span class="hljs-variable">end_percent</span> = <span class="hljs-keyword">match</span> stage_type {<br>            StageType::Init =&gt; <span class="hljs-number">3</span>,            <span class="hljs-comment">// 0-3%</span><br>            StageType::FelDram =&gt; <span class="hljs-number">5</span>,         <span class="hljs-comment">// 3-5%</span><br>            StageType::FelUboot =&gt; <span class="hljs-number">8</span>,        <span class="hljs-comment">// 5-8%</span><br>            StageType::FelReconnect =&gt; <span class="hljs-number">10</span>,   <span class="hljs-comment">// 8-10%</span><br>            StageType::FesQuery =&gt; <span class="hljs-number">12</span>,       <span class="hljs-comment">// 10-12%</span><br>            StageType::FesErase =&gt; <span class="hljs-number">14</span>,       <span class="hljs-comment">// 12-14%</span><br>            StageType::FesMbr =&gt; <span class="hljs-number">20</span>,         <span class="hljs-comment">// 14-20%</span><br>            StageType::FesPartitions =&gt; <span class="hljs-number">100</span>, <span class="hljs-comment">// 20-100% (主要阶段)</span><br>            StageType::FesBoot =&gt; <span class="hljs-number">100</span>,       <span class="hljs-comment">// 同上</span><br>            StageType::FesMode =&gt; <span class="hljs-number">100</span>,       <span class="hljs-comment">// 同上</span><br>        };<br><br>        stages.<span class="hljs-title function_ invoke__">push</span>(StageInfo {<br>            stage_type: *stage_type,<br>            weight: end_percent - cumulative_percent,<br>            completed: <span class="hljs-literal">false</span>,<br>            sub_total: <span class="hljs-number">0</span>,<br>        });<br>        cumulative_percent = end_percent;<br>    }<br><br>    <span class="hljs-keyword">self</span>.total_weight.<span class="hljs-title function_ invoke__">store</span>(<span class="hljs-number">100</span>, Ordering::SeqCst);<br>    <span class="hljs-keyword">self</span>.completed_weight.<span class="hljs-title function_ invoke__">store</span>(<span class="hljs-number">0</span>, Ordering::SeqCst);<br>    <span class="hljs-keyword">self</span>.current_stage.<span class="hljs-title function_ invoke__">store</span>(<span class="hljs-number">0</span>, Ordering::SeqCst);<br>}<br></code></pre></td></tr></tbody></table></figure><h3 id="分区阶段权重动态设置" data-id="分区阶段权重动态设置" class="notion-h"><a href="#分区阶段权重动态设置" class="headerlink" title="分区阶段权重动态设置"></a>分区阶段权重动态设置</h3><figure class="highlight rust"><table><tbody><tr><td class="code"><pre><code class="hljs rust"><span class="hljs-comment">/// 设置分区刷写阶段的权重（基于总字节数）</span><br><span class="hljs-keyword">pub</span> <span class="hljs-keyword">fn</span> <span class="hljs-title function_">set_partition_stage_weight</span>(&amp;<span class="hljs-keyword">self</span>, total_bytes: <span class="hljs-type">u64</span>) {<br>    <span class="hljs-keyword">let</span> <span class="hljs-variable">current</span> = <span class="hljs-keyword">self</span>.current_stage.<span class="hljs-title function_ invoke__">load</span>(Ordering::SeqCst);<br>    <span class="hljs-keyword">let</span> <span class="hljs-keyword">mut </span><span class="hljs-variable">stages</span> = <span class="hljs-keyword">self</span>.stages.<span class="hljs-title function_ invoke__">lock</span>().<span class="hljs-title function_ invoke__">unwrap</span>();<br><br>    <span class="hljs-keyword">if</span> current &lt; stages.<span class="hljs-title function_ invoke__">len</span>() &amp;&amp; stages[current].stage_type == StageType::FesPartitions {<br>        <span class="hljs-comment">// 计算已完成的权重</span><br>        <span class="hljs-keyword">let</span> <span class="hljs-variable">completed_weight</span>: <span class="hljs-type">u64</span> = stages<br>            .<span class="hljs-title function_ invoke__">iter</span>()<br>            .<span class="hljs-title function_ invoke__">filter</span>(|s| s.completed)<br>            .<span class="hljs-title function_ invoke__">map</span>(|s| s.weight)<br>            .<span class="hljs-title function_ invoke__">sum</span>();<br><br>        <span class="hljs-comment">// 设置分区阶段权重为 80%（从 20% 到 100%）</span><br>        stages[current].weight = <span class="hljs-number">80</span>;<br>        stages[current].sub_total = total_bytes;<br><br>        <span class="hljs-keyword">self</span>.completed_weight.<span class="hljs-title function_ invoke__">store</span>(completed_weight, Ordering::SeqCst);<br>        <span class="hljs-keyword">self</span>.total_bytes.<span class="hljs-title function_ invoke__">store</span>(total_bytes, Ordering::SeqCst);<br>        <span class="hljs-keyword">self</span>.stage_progress.<span class="hljs-title function_ invoke__">store</span>(<span class="hljs-number">0</span>, Ordering::SeqCst);<br>        <span class="hljs-keyword">self</span>.global_written_bytes.<span class="hljs-title function_ invoke__">store</span>(<span class="hljs-number">0</span>, Ordering::SeqCst);<br>    }<br>}<br></code></pre></td></tr></tbody></table></figure><h3 id="进度计算公式" data-id="进度计算公式" class="notion-h"><a href="#进度计算公式" class="headerlink" title="进度计算公式"></a>进度计算公式</h3><figure class="highlight plaintext"><table><tbody><tr><td class="code"><pre><code class="hljs plaintext">总进度 = completed_weight + (stage_progress / sub_total) * stage_weight<br><br>其中：<br>- completed_weight: 已完成阶段的权重总和<br>- stage_progress: 当前阶段内进度（字节）<br>- sub_total: 当前阶段总字节数<br>- stage_weight: 当前阶段权重<br></code></pre></td></tr></tbody></table></figure><hr><h2 id="速度计算机制" data-id="速度计算机制" class="notion-h"><a href="#速度计算机制" class="headerlink" title="速度计算机制"></a>速度计算机制</h2><figure class="highlight rust"><table><tbody><tr><td class="code"><pre><code class="hljs rust"><span class="hljs-comment">/// 更新进度并计算速度</span><br><span class="hljs-keyword">pub</span> <span class="hljs-keyword">fn</span> <span class="hljs-title function_">update_stage_progress_with_speed</span>(&amp;<span class="hljs-keyword">self</span>, progress: <span class="hljs-type">u64</span>) {<br>    <span class="hljs-keyword">let</span> <span class="hljs-variable">now</span> = Instant::<span class="hljs-title function_ invoke__">now</span>();<br>    <span class="hljs-keyword">let</span> <span class="hljs-keyword">mut </span><span class="hljs-variable">last_time</span> = <span class="hljs-keyword">self</span>.last_update_time.<span class="hljs-title function_ invoke__">lock</span>().<span class="hljs-title function_ invoke__">unwrap</span>();<br>    <span class="hljs-keyword">let</span> <span class="hljs-variable">last_bytes</span> = <span class="hljs-keyword">self</span>.last_update_bytes.<span class="hljs-title function_ invoke__">load</span>(Ordering::SeqCst);<br><br>    <span class="hljs-keyword">if</span> <span class="hljs-keyword">let</span> <span class="hljs-variable">Some</span>(last) = *last_time {<br>        <span class="hljs-keyword">let</span> <span class="hljs-variable">elapsed</span> = now.<span class="hljs-title function_ invoke__">duration_since</span>(last).<span class="hljs-title function_ invoke__">as_secs_f64</span>();<br>        <span class="hljs-keyword">if</span> elapsed &gt; <span class="hljs-number">0.0</span> {<br>            <span class="hljs-comment">// 计算速度 = 字节差 / 时间差</span><br>            <span class="hljs-keyword">let</span> <span class="hljs-variable">bytes_diff</span> = progress.<span class="hljs-title function_ invoke__">saturating_sub</span>(last_bytes);<br>            <span class="hljs-keyword">let</span> <span class="hljs-variable">speed</span> = bytes_diff <span class="hljs-keyword">as</span> <span class="hljs-type">f64</span> / elapsed;<br>            *<span class="hljs-keyword">self</span>.current_speed.<span class="hljs-title function_ invoke__">lock</span>().<span class="hljs-title function_ invoke__">unwrap</span>() = speed;<br>        }<br>    }<br><br>    <span class="hljs-comment">// 更新时间戳和字节计数</span><br>    *last_time = <span class="hljs-title function_ invoke__">Some</span>(now);<br>    <span class="hljs-keyword">self</span>.last_update_bytes.<span class="hljs-title function_ invoke__">store</span>(progress, Ordering::SeqCst);<br><br>    <span class="hljs-comment">// 更新进度</span><br>    <span class="hljs-keyword">self</span>.<span class="hljs-title function_ invoke__">update_stage_progress</span>(progress);<br><br>    <span class="hljs-comment">// 更新显示消息</span><br>    <span class="hljs-keyword">self</span>.<span class="hljs-title function_ invoke__">update_progress_message</span>();<br>}<br></code></pre></td></tr></tbody></table></figure><h3 id="速度显示格式化" data-id="速度显示格式化" class="notion-h"><a href="#速度显示格式化" class="headerlink" title="速度显示格式化"></a>速度显示格式化</h3><figure class="highlight rust"><table><tbody><tr><td class="code"><pre><code class="hljs rust"><span class="hljs-comment">/// 更新进度消息</span><br><span class="hljs-keyword">pub</span> <span class="hljs-keyword">fn</span> <span class="hljs-title function_">update_progress_message</span>(&amp;<span class="hljs-keyword">self</span>) {<br>    <span class="hljs-keyword">let</span> <span class="hljs-variable">partition</span> = <span class="hljs-keyword">self</span>.current_partition.<span class="hljs-title function_ invoke__">lock</span>().<span class="hljs-title function_ invoke__">unwrap</span>();<br>    <span class="hljs-keyword">let</span> <span class="hljs-variable">speed</span> = *<span class="hljs-keyword">self</span>.current_speed.<span class="hljs-title function_ invoke__">lock</span>().<span class="hljs-title function_ invoke__">unwrap</span>();<br>    <span class="hljs-keyword">let</span> <span class="hljs-variable">progress</span> = <span class="hljs-keyword">self</span>.stage_progress.<span class="hljs-title function_ invoke__">load</span>(Ordering::SeqCst);<br>    <span class="hljs-keyword">let</span> <span class="hljs-variable">total</span> = <span class="hljs-keyword">self</span>.total_bytes.<span class="hljs-title function_ invoke__">load</span>(Ordering::SeqCst);<br><br>    <span class="hljs-comment">// 格式化速度</span><br>    <span class="hljs-keyword">let</span> <span class="hljs-variable">speed_str</span> = <span class="hljs-keyword">if</span> speed &gt; <span class="hljs-number">1024.0</span> * <span class="hljs-number">1024.0</span> {<br>        <span class="hljs-built_in">format!</span>(<span class="hljs-string">"{:.2} MB/s"</span>, speed / (<span class="hljs-number">1024.0</span> * <span class="hljs-number">1024.0</span>))<br>    } <span class="hljs-keyword">else</span> <span class="hljs-keyword">if</span> speed &gt; <span class="hljs-number">1024.0</span> {<br>        <span class="hljs-built_in">format!</span>(<span class="hljs-string">"{:.2} KB/s"</span>, speed / <span class="hljs-number">1024.0</span>)<br>    } <span class="hljs-keyword">else</span> {<br>        <span class="hljs-built_in">format!</span>(<span class="hljs-string">"{:.0} B/s"</span>, speed)<br>    };<br><br>    <span class="hljs-comment">// 格式化进度</span><br>    <span class="hljs-keyword">let</span> <span class="hljs-variable">progress_str</span> = <span class="hljs-keyword">if</span> total &gt; <span class="hljs-number">0</span> {<br>        <span class="hljs-keyword">let</span> <span class="hljs-variable">progress_mb</span> = progress <span class="hljs-keyword">as</span> <span class="hljs-type">f64</span> / (<span class="hljs-number">1024.0</span> * <span class="hljs-number">1024.0</span>);<br>        <span class="hljs-keyword">let</span> <span class="hljs-variable">total_mb</span> = total <span class="hljs-keyword">as</span> <span class="hljs-type">f64</span> / (<span class="hljs-number">1024.0</span> * <span class="hljs-number">1024.0</span>);<br>        <span class="hljs-built_in">format!</span>(<span class="hljs-string">"{:.1}/{:.1} MB"</span>, progress_mb, total_mb)<br>    } <span class="hljs-keyword">else</span> {<br>        <span class="hljs-type">String</span>::<span class="hljs-title function_ invoke__">new</span>()<br>    };<br><br>    <span class="hljs-comment">// 组合消息</span><br>    <span class="hljs-keyword">let</span> <span class="hljs-variable">message</span> = <span class="hljs-keyword">if</span> partition.<span class="hljs-title function_ invoke__">is_empty</span>() {<br>        <span class="hljs-built_in">format!</span>(<span class="hljs-string">"{} {}"</span>, speed_str, progress_str)<br>    } <span class="hljs-keyword">else</span> {<br>        <span class="hljs-built_in">format!</span>(<span class="hljs-string">"[{}] {} {}"</span>, partition, speed_str, progress_str)<br>    };<br><br>    <span class="hljs-keyword">if</span> <span class="hljs-keyword">let</span> <span class="hljs-variable">Some</span>(pb) = <span class="hljs-keyword">self</span>.progress_bar.<span class="hljs-title function_ invoke__">lock</span>().<span class="hljs-title function_ invoke__">unwrap</span>().<span class="hljs-title function_ invoke__">as_ref</span>() {<br>        pb.<span class="hljs-title function_ invoke__">set_message</span>(message);<br>    }<br>}<br></code></pre></td></tr></tbody></table></figure><hr><h2 id="TUI-模式快照机制" data-id="TUI-模式快照机制" class="notion-h"><a href="#TUI-模式快照机制" class="headerlink" title="TUI 模式快照机制"></a>TUI 模式快照机制</h2><figure class="highlight rust"><table><tbody><tr><td class="code"><pre><code class="hljs rust"><span class="hljs-comment">/// 获取进度状态快照（供 TUI 轮询）</span><br><span class="hljs-keyword">pub</span> <span class="hljs-keyword">fn</span> <span class="hljs-title function_">snapshot</span>(&amp;<span class="hljs-keyword">self</span>) <span class="hljs-punctuation">-&gt;</span> ProgressSnapshot {<br>    <span class="hljs-keyword">let</span> <span class="hljs-variable">stages</span> = <span class="hljs-keyword">self</span>.stages.<span class="hljs-title function_ invoke__">lock</span>().<span class="hljs-title function_ invoke__">unwrap</span>().<span class="hljs-title function_ invoke__">clone</span>();<br>    <span class="hljs-keyword">let</span> <span class="hljs-variable">partition</span> = <span class="hljs-keyword">self</span>.current_partition.<span class="hljs-title function_ invoke__">lock</span>().<span class="hljs-title function_ invoke__">unwrap</span>().<span class="hljs-title function_ invoke__">clone</span>();<br>    <span class="hljs-keyword">let</span> <span class="hljs-variable">speed</span> = *<span class="hljs-keyword">self</span>.current_speed.<span class="hljs-title function_ invoke__">lock</span>().<span class="hljs-title function_ invoke__">unwrap</span>();<br>    <span class="hljs-keyword">let</span> <span class="hljs-variable">precise</span> = *<span class="hljs-keyword">self</span>.precise_progress.<span class="hljs-title function_ invoke__">lock</span>().<span class="hljs-title function_ invoke__">unwrap</span>();<br><br>    ProgressSnapshot {<br>        precise_progress: precise,<br>        stage_progress: <span class="hljs-keyword">self</span>.stage_progress.<span class="hljs-title function_ invoke__">load</span>(Ordering::SeqCst),<br>        total_bytes: <span class="hljs-keyword">self</span>.total_bytes.<span class="hljs-title function_ invoke__">load</span>(Ordering::SeqCst),<br>        speed,<br>        current_partition: partition,<br>        current_stage_index: <span class="hljs-keyword">self</span>.current_stage.<span class="hljs-title function_ invoke__">load</span>(Ordering::SeqCst),<br>        stages,<br>    }<br>}<br></code></pre></td></tr></tbody></table></figure><p><strong>TUI 中的使用方式：</strong></p><figure class="highlight rust"><table><tbody><tr><td class="code"><pre><code class="hljs rust"><span class="hljs-comment">// TUI 每帧轮询进度</span><br><span class="hljs-keyword">let</span> <span class="hljs-variable">snapshot</span> = <span class="hljs-title function_ invoke__">global_progress</span>().<span class="hljs-title function_ invoke__">snapshot</span>();<br><br><span class="hljs-comment">// 使用快照数据渲染界面</span><br>app.progress = snapshot.precise_progress;<br>app.current_partition = snapshot.current_partition;<br>app.speed = format_speed(snapshot.speed);<br>app.stages = snapshot.stages;<br></code></pre></td></tr></tbody></table></figure><hr><h2 id="ProgressReporter-封装" data-id="ProgressReporter-封装" class="notion-h"><a href="#ProgressReporter-封装" class="headerlink" title="ProgressReporter 封装"></a>ProgressReporter 封装</h2><figure class="highlight rust"><table><tbody><tr><td class="code"><pre><code class="hljs rust"><span class="hljs-comment">/// ProgressReporter - GlobalProgress 的简化封装</span><br><span class="hljs-keyword">pub</span> <span class="hljs-keyword">struct</span> <span class="hljs-title class_">ProgressReporter</span> {<br>    progress: Arc&lt;GlobalProgress&gt;,<br>}<br><br><span class="hljs-keyword">impl</span> <span class="hljs-title class_">ProgressReporter</span> {<br>    <span class="hljs-keyword">pub</span> <span class="hljs-keyword">fn</span> <span class="hljs-title function_">new</span>() <span class="hljs-punctuation">-&gt;</span> <span class="hljs-keyword">Self</span> {<br>        <span class="hljs-keyword">Self</span> {<br>            progress: <span class="hljs-title function_ invoke__">global_progress</span>(),<br>        }<br>    }<br><br>    <span class="hljs-comment">/// 启动进度追踪</span><br>    <span class="hljs-keyword">pub</span> <span class="hljs-keyword">fn</span> <span class="hljs-title function_">start</span>(&amp;<span class="hljs-keyword">self</span>) {<br>        <span class="hljs-keyword">self</span>.progress.<span class="hljs-title function_ invoke__">start</span>();<br>    }<br><br>    <span class="hljs-comment">/// 定义阶段</span><br>    <span class="hljs-keyword">pub</span> <span class="hljs-keyword">fn</span> <span class="hljs-title function_">define_stages</span>(&amp;<span class="hljs-keyword">self</span>, stages: &amp;[StageType]) {<br>        <span class="hljs-keyword">self</span>.progress.<span class="hljs-title function_ invoke__">define_stages</span>(stages);<br>    }<br><br>    <span class="hljs-comment">/// 开始阶段</span><br>    <span class="hljs-keyword">pub</span> <span class="hljs-keyword">fn</span> <span class="hljs-title function_">begin_stage</span>(&amp;<span class="hljs-keyword">self</span>, stage_type: StageType) {<br>        <span class="hljs-keyword">self</span>.progress.<span class="hljs-title function_ invoke__">start_stage</span>(stage_type);<br>    }<br><br>    <span class="hljs-comment">/// 设置分区权重</span><br>    <span class="hljs-keyword">pub</span> <span class="hljs-keyword">fn</span> <span class="hljs-title function_">set_partition_stage_weight</span>(&amp;<span class="hljs-keyword">self</span>, total_bytes: <span class="hljs-type">u64</span>) {<br>        <span class="hljs-keyword">self</span>.progress.<span class="hljs-title function_ invoke__">set_partition_stage_weight</span>(total_bytes);<br>    }<br><br>    <span class="hljs-comment">/// 设置当前分区名称</span><br>    <span class="hljs-keyword">pub</span> <span class="hljs-keyword">fn</span> <span class="hljs-title function_">set_current_partition</span>(&amp;<span class="hljs-keyword">self</span>, partition_name: &amp;<span class="hljs-type">str</span>) {<br>        <span class="hljs-keyword">self</span>.progress.<span class="hljs-title function_ invoke__">set_current_partition</span>(partition_name);<br>    }<br><br>    <span class="hljs-comment">/// 更新进度（带速度计算）</span><br>    <span class="hljs-keyword">pub</span> <span class="hljs-keyword">fn</span> <span class="hljs-title function_">update_progress_with_speed</span>(&amp;<span class="hljs-keyword">self</span>, current: <span class="hljs-type">u64</span>) {<br>        <span class="hljs-keyword">self</span>.progress.<span class="hljs-title function_ invoke__">update_stage_progress_with_speed</span>(current);<br>    }<br><br>    <span class="hljs-comment">/// 完成当前阶段</span><br>    <span class="hljs-keyword">pub</span> <span class="hljs-keyword">fn</span> <span class="hljs-title function_">complete_stage</span>(&amp;<span class="hljs-keyword">self</span>) {<br>        <span class="hljs-keyword">self</span>.progress.<span class="hljs-title function_ invoke__">complete_stage</span>();<br>    }<br><br>    <span class="hljs-comment">/// 结束进度追踪</span><br>    <span class="hljs-keyword">pub</span> <span class="hljs-keyword">fn</span> <span class="hljs-title function_">finish</span>(&amp;<span class="hljs-keyword">self</span>) {<br>        <span class="hljs-keyword">self</span>.progress.<span class="hljs-title function_ invoke__">finish</span>();<br>    }<br>}<br></code></pre></td></tr></tbody></table></figure><hr><h2 id="调用流程分析" data-id="调用流程分析" class="notion-h"><a href="#调用流程分析" class="headerlink" title="调用流程分析"></a>调用流程分析</h2><h3 id="刷写流程中的进度更新" data-id="刷写流程中的进度更新" class="notion-h"><a href="#刷写流程中的进度更新" class="headerlink" title="刷写流程中的进度更新"></a>刷写流程中的进度更新</h3><figure class="highlight rust"><table><tbody><tr><td class="code"><pre><code class="hljs rust"><span class="hljs-keyword">impl</span> <span class="hljs-title class_">Flasher</span> {<br>    <span class="hljs-keyword">pub</span> <span class="hljs-keyword">async</span> <span class="hljs-keyword">fn</span> <span class="hljs-title function_">execute</span>(&amp;<span class="hljs-keyword">self</span>) <span class="hljs-punctuation">-&gt;</span> FlashResult&lt;()&gt; {<br>        <span class="hljs-keyword">let</span> <span class="hljs-variable">logger</span> = Logger::<span class="hljs-title function_ invoke__">new</span>();<br><br>        <span class="hljs-comment">// 1. 根据设备模式选择阶段序列</span><br>        <span class="hljs-keyword">let</span> <span class="hljs-variable">stages</span> = <span class="hljs-keyword">if</span> is_fel_mode {<br>            FlashStages::for_fel_mode()<br>        } <span class="hljs-keyword">else</span> {<br>            FlashStages::for_fes_mode()<br>        };<br><br>        <span class="hljs-comment">// 2. 定义阶段并启动进度</span><br>        logger.<span class="hljs-title function_ invoke__">define_stages</span>(stages.<span class="hljs-title function_ invoke__">stages</span>());<br>        logger.<span class="hljs-title function_ invoke__">start_global_progress</span>();<br><br>        <span class="hljs-comment">// 3. 初始化阶段</span><br>        logger.<span class="hljs-title function_ invoke__">begin_stage</span>(StageType::Init);<br>        <span class="hljs-comment">// ... 初始化操作 ...</span><br>        logger.<span class="hljs-title function_ invoke__">complete_stage</span>();<br><br>        <span class="hljs-comment">// 4. FEL DRAM 初始化（如果需要）</span><br>        <span class="hljs-keyword">if</span> is_fel_mode {<br>            logger.<span class="hljs-title function_ invoke__">begin_stage</span>(StageType::FelDram);<br>            <span class="hljs-comment">// ... DRAM 初始化 ...</span><br>            logger.<span class="hljs-title function_ invoke__">complete_stage</span>();<br><br>            logger.<span class="hljs-title function_ invoke__">begin_stage</span>(StageType::FelUboot);<br>            <span class="hljs-comment">// ... U-Boot 下载 ...</span><br>            logger.<span class="hljs-title function_ invoke__">complete_stage</span>();<br><br>            logger.<span class="hljs-title function_ invoke__">begin_stage</span>(StageType::FelReconnect);<br>            <span class="hljs-comment">// ... 等待重连 ...</span><br>            logger.<span class="hljs-title function_ invoke__">complete_stage</span>();<br>        }<br><br>        <span class="hljs-comment">// 5. FES 设备查询</span><br>        logger.<span class="hljs-title function_ invoke__">begin_stage</span>(StageType::FesQuery);<br>        <span class="hljs-comment">// ... 查询设备 ...</span><br>        logger.<span class="hljs-title function_ invoke__">complete_stage</span>();<br><br>        <span class="hljs-comment">// 6. Flash 擦除（如果需要）</span><br>        <span class="hljs-keyword">if</span> need_erase {<br>            logger.<span class="hljs-title function_ invoke__">begin_stage</span>(StageType::FesErase);<br>            <span class="hljs-comment">// ... 擦除操作 ...</span><br>            logger.<span class="hljs-title function_ invoke__">complete_stage</span>();<br>        }<br><br>        <span class="hljs-comment">// 7. MBR 写入</span><br>        logger.<span class="hljs-title function_ invoke__">begin_stage</span>(StageType::FesMbr);<br>        <span class="hljs-comment">// ... MBR 写入 ...</span><br>        logger.<span class="hljs-title function_ invoke__">complete_stage</span>();<br><br>        <span class="hljs-comment">// 8. 分区刷写（主要阶段）</span><br>        logger.<span class="hljs-title function_ invoke__">begin_stage</span>(StageType::FesPartitions);<br>        logger.<span class="hljs-title function_ invoke__">set_partition_stage_weight</span>(total_partition_bytes);<br><br>        <span class="hljs-keyword">for</span> <span class="hljs-variable">partition</span> <span class="hljs-keyword">in</span> partitions {<br>            logger.<span class="hljs-title function_ invoke__">set_current_partition</span>(&amp;partition.name);<br><br>            <span class="hljs-comment">// 刷写过程中更新进度</span><br>            <span class="hljs-keyword">for</span> <span class="hljs-variable">chunk</span> <span class="hljs-keyword">in</span> download_chunks {<br>                logger.<span class="hljs-title function_ invoke__">update_progress_with_speed</span>(bytes_written);<br>            }<br>        }<br>        logger.<span class="hljs-title function_ invoke__">complete_stage</span>();<br><br>        <span class="hljs-comment">// 9. Boot 写入</span><br>        logger.<span class="hljs-title function_ invoke__">begin_stage</span>(StageType::FesBoot);<br>        <span class="hljs-comment">// ... Boot 写入 ...</span><br>        logger.<span class="hljs-title function_ invoke__">complete_stage</span>();<br><br>        <span class="hljs-comment">// 10. 设置设备模式</span><br>        logger.<span class="hljs-title function_ invoke__">begin_stage</span>(StageType::FesMode);<br>        <span class="hljs-comment">// ... 重启/关机 ...</span><br>        logger.<span class="hljs-title function_ invoke__">complete_stage</span>();<br><br>        <span class="hljs-comment">// 11. 结束进度追踪</span><br>        logger.<span class="hljs-title function_ invoke__">finish_progress</span>();<br><br>        <span class="hljs-title function_ invoke__">Ok</span>(())<br>    }<br>}<br></code></pre></td></tr></tbody></table></figure><h3 id="CLI-进度条显示效果" data-id="CLI-进度条显示效果" class="notion-h"><a href="#CLI-进度条显示效果" class="headerlink" title="CLI 进度条显示效果"></a>CLI 进度条显示效果</h3><figure class="highlight plaintext"><table><tbody><tr><td class="code"><pre><code class="hljs plaintext">⠋ [00:00:05] [██████████░░░░░░░░░░░░░░░░░░░░░░] 42% [rootfs] 2.5 MB/s 45.2/128.0 MB<br></code></pre></td></tr></tbody></table></figure><hr><h2 id="代码亮点" data-id="代码亮点" class="notion-h"><a href="#代码亮点" class="headerlink" title="代码亮点"></a>代码亮点</h2><h3 id="1-AtomicU64-线程安全计数" data-id="1-AtomicU64-线程安全计数" class="notion-h"><a href="#1-AtomicU64-线程安全计数" class="headerlink" title="1. AtomicU64 线程安全计数"></a>1. AtomicU64 线程安全计数</h3><figure class="highlight rust"><table><tbody><tr><td class="code"><pre><code class="hljs rust">total_weight: AtomicU64,<br>completed_weight: AtomicU64,<br>stage_progress: AtomicU64,<br><br><span class="hljs-comment">// 使用 SeqCst 内存序保证严格顺序</span><br><span class="hljs-keyword">self</span>.total_weight.<span class="hljs-title function_ invoke__">store</span>(<span class="hljs-number">100</span>, Ordering::SeqCst);<br><span class="hljs-keyword">let</span> <span class="hljs-variable">weight</span> = <span class="hljs-keyword">self</span>.completed_weight.<span class="hljs-title function_ invoke__">fetch_add</span>(weight, Ordering::SeqCst);<br></code></pre></td></tr></tbody></table></figure><p><strong>原子操作类型：</strong></p><table><thead><tr><th>类型</th><th>作用</th></tr></thead><tbody><tr><td>AtomicU64</td><td>64 位整数原子操作</td></tr><tr><td>AtomicUsize</td><td>usize 原子操作（索引）</td></tr><tr><td>AtomicBool</td><td>布尔值原子操作</td></tr></tbody></table><h3 id="2-Ordering-SeqCst-内存序" data-id="2-Ordering-SeqCst-内存序" class="notion-h"><a href="#2-Ordering-SeqCst-内存序" class="headerlink" title="2. Ordering::SeqCst 内存序"></a>2. Ordering::SeqCst 内存序</h3><figure class="highlight rust"><table><tbody><tr><td class="code"><pre><code class="hljs rust"><span class="hljs-comment">// SeqCst = Sequentially Consistent</span><br><span class="hljs-comment">// 保证所有线程看到相同的操作顺序</span><br>TUI_MODE.<span class="hljs-title function_ invoke__">store</span>(enabled, Ordering::SeqCst);<br></code></pre></td></tr></tbody></table></figure><h3 id="3-Mutex-保护复合类型" data-id="3-Mutex-保护复合类型" class="notion-h"><a href="#3-Mutex-保护复合类型" class="headerlink" title="3. Mutex 保护复合类型"></a>3. Mutex 保护复合类型</h3><figure class="highlight rust"><table><tbody><tr><td class="code"><pre><code class="hljs rust"><span class="hljs-comment">// 对于 Vec、Option 等复合类型，使用 Mutex</span><br>stages: Mutex&lt;<span class="hljs-type">Vec</span>&lt;StageInfo&gt;&gt;,<br>progress_bar: Mutex&lt;<span class="hljs-type">Option</span>&lt;ProgressBar&gt;&gt;,<br>current_partition: Mutex&lt;<span class="hljs-type">String</span>&gt;,<br></code></pre></td></tr></tbody></table></figure><h3 id="4-原子操作的-swap-方法" data-id="4-原子操作的-swap-方法" class="notion-h"><a href="#4-原子操作的-swap-方法" class="headerlink" title="4. 原子操作的 swap 方法"></a>4. 原子操作的 swap 方法</h3><figure class="highlight rust"><table><tbody><tr><td class="code"><pre><code class="hljs rust"><span class="hljs-comment">/// 启动进度追踪（防止重复启动）</span><br><span class="hljs-keyword">pub</span> <span class="hljs-keyword">fn</span> <span class="hljs-title function_">start</span>(&amp;<span class="hljs-keyword">self</span>) {<br>    <span class="hljs-comment">// swap 返回旧值，如果旧值为 1 则表示已启动</span><br>    <span class="hljs-keyword">if</span> <span class="hljs-keyword">self</span>.started.<span class="hljs-title function_ invoke__">swap</span>(<span class="hljs-number">1</span>, Ordering::SeqCst) == <span class="hljs-number">1</span> {<br>        <span class="hljs-keyword">return</span>;  <span class="hljs-comment">// 已启动，直接返回</span><br>    }<br><br>    <span class="hljs-comment">// ... 创建进度条 ...</span><br>}<br></code></pre></td></tr></tbody></table></figure><h3 id="5-once-cell-Lazy-全局单例" data-id="5-once-cell-Lazy-全局单例" class="notion-h"><a href="#5-once-cell-Lazy-全局单例" class="headerlink" title="5. once_cell::Lazy 全局单例"></a>5. once_cell::Lazy 全局单例</h3><figure class="highlight rust"><table><tbody><tr><td class="code"><pre><code class="hljs rust"><span class="hljs-comment">// 延迟初始化，首次访问时创建</span><br><span class="hljs-keyword">static</span> GLOBAL_PROGRESS: Lazy&lt;Arc&lt;GlobalProgress&gt;&gt; =<br>    Lazy::<span class="hljs-title function_ invoke__">new</span>(|| Arc::<span class="hljs-title function_ invoke__">new</span>(GlobalProgress::<span class="hljs-title function_ invoke__">new</span>()));<br></code></pre></td></tr></tbody></table></figure><h3 id="6-indicatif-进度条样式" data-id="6-indicatif-进度条样式" class="notion-h"><a href="#6-indicatif-进度条样式" class="headerlink" title="6. indicatif 进度条样式"></a>6. indicatif 进度条样式</h3><figure class="highlight rust"><table><tbody><tr><td class="code"><pre><code class="hljs rust">pb.<span class="hljs-title function_ invoke__">set_style</span>(<br>    ProgressStyle::<span class="hljs-title function_ invoke__">default_bar</span>()<br>        .<span class="hljs-title function_ invoke__">template</span>(<br>            <span class="hljs-string">"{spinner:.green} [{elapsed_precise}] [{bar:40.cyan/blue}] {pos:&gt;3}% {msg}"</span>,<br>        )<br>        .<span class="hljs-title function_ invoke__">unwrap</span>()<br>        .<span class="hljs-title function_ invoke__">progress_chars</span>(<span class="hljs-string">"#&gt;-"</span>),<br>);<br>pb.<span class="hljs-title function_ invoke__">enable_steady_tick</span>(Duration::<span class="hljs-title function_ invoke__">from_millis</span>(<span class="hljs-number">100</span>));  <span class="hljs-comment">// 自动刷新</span><br></code></pre></td></tr></tbody></table></figure><hr><h2 id="阶段进度时线图" data-id="阶段进度时线图" class="notion-h"><a href="#阶段进度时线图" class="headerlink" title="阶段进度时线图"></a>阶段进度时线图</h2><pre class="mermaid">gantt    title FEL 模式刷写阶段进度    dateFormat X    axisFormat %s    section FEL 阶段    Init           :0, 3    DRAM Init      :3, 5    U-Boot Download :5, 8    Reconnecting   :8, 10    section FES 阶段    Query Device   :10, 12    Erasing        :12, 14    Writing MBR    :14, 20    Flashing Partitions :20, 100    Writing Boot   :crit, 95, 98    Setting Mode   :crit, 98, 100</pre><hr><h2 id="实践示例" data-id="实践示例" class="notion-h"><a href="#实践示例" class="headerlink" title="实践示例"></a>实践示例</h2><h3 id="CLI-模式使用" data-id="CLI-模式使用" class="notion-h"><a href="#CLI-模式使用" class="headerlink" title="CLI 模式使用"></a>CLI 模式使用</h3><figure class="highlight rust"><table><tbody><tr><td class="code"><pre><code class="hljs rust"><span class="hljs-keyword">use</span> openixcli::process::{FlashStages, global_progress, StageType};<br><span class="hljs-keyword">use</span> openixcli::utils::Logger;<br><br><span class="hljs-keyword">fn</span> <span class="hljs-title function_">main</span>() {<br>    <span class="hljs-keyword">let</span> <span class="hljs-variable">logger</span> = Logger::<span class="hljs-title function_ invoke__">new</span>();<br><br>    <span class="hljs-comment">// 定义阶段</span><br>    <span class="hljs-keyword">let</span> <span class="hljs-variable">stages</span> = FlashStages::for_fel_mode();<br>    logger.<span class="hljs-title function_ invoke__">define_stages</span>(stages.<span class="hljs-title function_ invoke__">stages</span>());<br><br>    <span class="hljs-comment">// 启动进度</span><br>    logger.<span class="hljs-title function_ invoke__">start_global_progress</span>();<br><br>    <span class="hljs-comment">// 模拟刷写过程</span><br>    <span class="hljs-keyword">for</span> <span class="hljs-variable">stage</span> <span class="hljs-keyword">in</span> stages.<span class="hljs-title function_ invoke__">stages</span>() {<br>        logger.<span class="hljs-title function_ invoke__">begin_stage</span>(*stage);<br>        <span class="hljs-built_in">println!</span>(<span class="hljs-string">"阶段: {}"</span>, stage.<span class="hljs-title function_ invoke__">name</span>());<br><br>        <span class="hljs-comment">// 模拟进度更新</span><br>        <span class="hljs-keyword">for</span> <span class="hljs-variable">i</span> <span class="hljs-keyword">in</span> <span class="hljs-number">0</span>..<span class="hljs-number">100</span> {<br>            logger.<span class="hljs-title function_ invoke__">update_progress_with_speed</span>(i);<br>            std::thread::<span class="hljs-title function_ invoke__">sleep</span>(std::time::Duration::<span class="hljs-title function_ invoke__">from_millis</span>(<span class="hljs-number">10</span>));<br>        }<br><br>        logger.<span class="hljs-title function_ invoke__">complete_stage</span>();<br>    }<br><br>    logger.<span class="hljs-title function_ invoke__">finish_progress</span>();<br>}<br></code></pre></td></tr></tbody></table></figure><h3 id="TUI-模式使用" data-id="TUI-模式使用" class="notion-h"><a href="#TUI-模式使用" class="headerlink" title="TUI 模式使用"></a>TUI 模式使用</h3><figure class="highlight rust"><table><tbody><tr><td class="code"><pre><code class="hljs rust"><span class="hljs-keyword">use</span> openixcli::process::{set_tui_mode, global_progress};<br><br><span class="hljs-keyword">fn</span> <span class="hljs-title function_">run_tui</span>() {<br>    <span class="hljs-comment">// 启用 TUI 模式（禁用 indicatif）</span><br>    <span class="hljs-title function_ invoke__">set_tui_mode</span>(<span class="hljs-literal">true</span>);<br><br>    <span class="hljs-comment">// 启动后台刷写任务</span><br>    <span class="hljs-title function_ invoke__">spawn_flash_task</span>();<br><br>    <span class="hljs-comment">// 主循环轮询进度</span><br>    <span class="hljs-keyword">loop</span> {<br>        <span class="hljs-keyword">let</span> <span class="hljs-variable">snapshot</span> = <span class="hljs-title function_ invoke__">global_progress</span>().<span class="hljs-title function_ invoke__">snapshot</span>();<br><br>        <span class="hljs-comment">// 渲染 TUI 界面</span><br>        <span class="hljs-title function_ invoke__">draw_progress_bar</span>(snapshot.precise_progress);<br>        <span class="hljs-title function_ invoke__">draw_speed</span>(snapshot.speed);<br>        <span class="hljs-title function_ invoke__">draw_partition</span>(snapshot.current_partition);<br>        <span class="hljs-title function_ invoke__">draw_stages</span>(snapshot.stages);<br><br>        <span class="hljs-comment">// 检查是否完成</span><br>        <span class="hljs-keyword">if</span> snapshot.precise_progress &gt;= <span class="hljs-number">100.0</span> {<br>            <span class="hljs-keyword">break</span>;<br>        }<br><br>        std::thread::<span class="hljs-title function_ invoke__">sleep</span>(std::time::Duration::<span class="hljs-title function_ invoke__">from_millis</span>(<span class="hljs-number">100</span>));<br>    }<br>}<br></code></pre></td></tr></tbody></table></figure><h3 id="获取当前进度" data-id="获取当前进度" class="notion-h"><a href="#获取当前进度" class="headerlink" title="获取当前进度"></a>获取当前进度</h3><figure class="highlight rust"><table><tbody><tr><td class="code"><pre><code class="hljs rust"><span class="hljs-keyword">let</span> <span class="hljs-variable">progress</span> = <span class="hljs-title function_ invoke__">global_progress</span>();<br><br><span class="hljs-comment">// 获取百分比</span><br><span class="hljs-keyword">let</span> <span class="hljs-variable">percent</span> = progress.<span class="hljs-title function_ invoke__">get_progress</span>();<br><span class="hljs-built_in">println!</span>(<span class="hljs-string">"当前进度: {}%"</span>, percent);<br><br><span class="hljs-comment">// 获取快照</span><br><span class="hljs-keyword">let</span> <span class="hljs-variable">snapshot</span> = progress.<span class="hljs-title function_ invoke__">snapshot</span>();<br><span class="hljs-built_in">println!</span>(<span class="hljs-string">"当前分区: {}"</span>, snapshot.current_partition);<br><span class="hljs-built_in">println!</span>(<span class="hljs-string">"传输速度: {:.2} MB/s"</span>, snapshot.speed / (<span class="hljs-number">1024.0</span> * <span class="hljs-number">1024.0</span>));<br><span class="hljs-built_in">println!</span>(<span class="hljs-string">"已完成阶段: {}"</span>, snapshot.stages.<span class="hljs-title function_ invoke__">iter</span>().<span class="hljs-title function_ invoke__">filter</span>(|s| s.completed).<span class="hljs-title function_ invoke__">count</span>());<br></code></pre></td></tr></tbody></table></figure><hr><h2 id="数据结构关系图" data-id="数据结构关系图" class="notion-h"><a href="#数据结构关系图" class="headerlink" title="数据结构关系图"></a>数据结构关系图</h2><pre class="mermaid">classDiagram    class GlobalProgress {        +progress_bar: Mutex~Option~ProgressBar~~        +total_weight: AtomicU64        +completed_weight: AtomicU64        +current_stage: AtomicUsize        +stages: Mutex~Vec~StageInfo~~        +current_speed: Mutex~f64~        +define_stages(stage_types)        +start()        +start_stage(stage_type)        +update_stage_progress(progress)        +complete_stage()        +snapshot() ProgressSnapshot        +finish()    }    class StageInfo {        +stage_type: StageType        +weight: u64        +completed: bool        +sub_total: u64    }    class ProgressSnapshot {        +precise_progress: f64        +stage_progress: u64        +total_bytes: u64        +speed: f64        +current_partition: String        +stages: Vec~StageInfo~    }    class StageType {        +Init        +FelDram        +FelUboot        +FelReconnect        +FesQuery        +FesErase        +FesMbr        +FesPartitions        +FesBoot        +FesMode        +name() String    }    class FlashStages {        +stages: Vec~StageType~        +for_fel_mode()        +for_fes_mode()    }    class ProgressReporter {        +progress: Arc~GlobalProgress~        +start()        +begin_stage()        +update_progress_with_speed()        +complete_stage()    }    GlobalProgress --&gt; StageInfo    GlobalProgress --&gt; ProgressSnapshot    FlashStages --&gt; StageType    ProgressReporter --&gt; GlobalProgress</pre>]]>
    </content>
    <id>https://gloomyghost.com/posts/openixcli-process-module/</id>
    <link href="https://gloomyghost.com/posts/openixcli-process-module/"/>
    <published>2026-04-25T16:00:00.000Z</published>
    <summary>
      <![CDATA[<h2 id="概述" data-id="概述" class="notion-h"><a href="#概述" class="headerlink" title="概述"></a>概述</h2><p>固件刷写是一个多阶段的复杂过程，涉及设备检测、DRAM 初始化、U-Boot 下]]>
    </summary>
    <title>OpenixCLI Process 模块深度解析：进度追踪与阶段管理</title>
    <updated>2026-05-29T20:33:31.219Z</updated>
  </entry>
  <entry>
    <author>
      <name>柚木 鉉</name>
    </author>
    <category term="Rust" scheme="https://gloomyghost.com/tags/Rust/"/>
    <category term="嵌入式" scheme="https://gloomyghost.com/tags/%E5%B5%8C%E5%85%A5%E5%BC%8F/"/>
    <category term="OpenixCLI" scheme="https://gloomyghost.com/tags/OpenixCLI/"/>
    <category term="TUI" scheme="https://gloomyghost.com/tags/TUI/"/>
    <category term="ratatui" scheme="https://gloomyghost.com/tags/ratatui/"/>
    <category term="事件驱动" scheme="https://gloomyghost.com/tags/%E4%BA%8B%E4%BB%B6%E9%A9%B1%E5%8A%A8/"/>
    <content>
      <![CDATA[<h2 id="概述" data-id="概述" class="notion-h"><a href="#概述" class="headerlink" title="概述"></a>概述</h2><p>终端用户界面（TUI）为固件刷写工具提供了直观的交互体验。OpenixCLI 使用 <code>ratatui</code> 框架构建事件驱动的 TUI 应用，通过 <code>mpsc</code> 通道实现异步事件通信，支持设备扫描、固件加载、选项配置和进度追踪的完整流程。</p><h3 id="模块结构" data-id="模块结构" class="notion-h"><a href="#模块结构" class="headerlink" title="模块结构"></a>模块结构</h3><figure class="highlight plaintext"><table><tbody><tr><td class="code"><pre><code class="hljs plaintext">src/tui/<br>├── mod.rs          # 模块导出，run() 入口<br>├── app.rs          # App 状态、事件循环、键盘处理<br>├── event.rs        # AppEvent 事件类型定义<br>├── bridge.rs       # scan_devices、run_flash 后台任务<br>├── ui.rs           # UI 渲染函数<br>└── widgets/        # Widget 组件<br>    ├── device_list.rs      # 设备列表渲染<br>    ├── firmware_info.rs    # 固件信息和选项<br>    ├── progress.rs         # 进度条显示<br>    └── log_view.rs         # 日志显示<br></code></pre></td></tr></tbody></table></figure><h3 id="核心导出" data-id="核心导出" class="notion-h"><a href="#核心导出" class="headerlink" title="核心导出"></a>核心导出</h3><figure class="highlight rust"><table><tbody><tr><td class="code"><pre><code class="hljs rust"><span class="hljs-keyword">pub</span> <span class="hljs-keyword">use</span> app::run;  <span class="hljs-comment">// TUI 入口函数</span><br></code></pre></td></tr></tbody></table></figure><hr><h2 id="核心数据结构" data-id="核心数据结构" class="notion-h"><a href="#核心数据结构" class="headerlink" title="核心数据结构"></a>核心数据结构</h2><h3 id="AppState-状态枚举" data-id="AppState-状态枚举" class="notion-h"><a href="#AppState-状态枚举" class="headerlink" title="AppState 状态枚举"></a>AppState 状态枚举</h3><figure class="highlight rust"><table><tbody><tr><td class="code"><pre><code class="hljs rust"><span class="hljs-comment">/// 应用状态</span><br><span class="hljs-meta">#[derive(Debug, Clone, Copy, PartialEq, Eq)]</span><br><span class="hljs-keyword">pub</span> <span class="hljs-keyword">enum</span> <span class="hljs-title class_">AppState</span> {<br>    <span class="hljs-comment">/// 初始状态（无设备或无固件）</span><br>    Idle,<br><br>    <span class="hljs-comment">/// 准备状态（有设备和固件，可刷写）</span><br>    Ready,<br><br>    <span class="hljs-comment">/// 正在刷写</span><br>    Flashing,<br><br>    <span class="hljs-comment">/// 刷写完成</span><br>    Done,<br><br>    <span class="hljs-comment">/// 刷写错误</span><br>    Error,<br>}<br></code></pre></td></tr></tbody></table></figure><h3 id="FocusPanel-焭点枚举" data-id="FocusPanel-焭点枚举" class="notion-h"><a href="#FocusPanel-焭点枚举" class="headerlink" title="FocusPanel 焭点枚举"></a>FocusPanel 焭点枚举</h3><figure class="highlight rust"><table><tbody><tr><td class="code"><pre><code class="hljs rust"><span class="hljs-comment">/// 当前焦点的面板</span><br><span class="hljs-meta">#[derive(Debug, Clone, Copy, PartialEq, Eq)]</span><br><span class="hljs-keyword">pub</span> <span class="hljs-keyword">enum</span> <span class="hljs-title class_">FocusPanel</span> {<br>    <span class="hljs-comment">/// 设备列表</span><br>    Devices,<br><br>    <span class="hljs-comment">/// 选项面板</span><br>    Options,<br>}<br><br><span class="hljs-keyword">impl</span> <span class="hljs-title class_">FocusPanel</span> {<br>    <span class="hljs-keyword">pub</span> <span class="hljs-keyword">fn</span> <span class="hljs-title function_">toggle</span>(&amp;<span class="hljs-keyword">self</span>) <span class="hljs-punctuation">-&gt;</span> <span class="hljs-keyword">Self</span> {<br>        <span class="hljs-keyword">match</span> <span class="hljs-keyword">self</span> {<br>            FocusPanel::Devices =&gt; FocusPanel::Options,<br>            FocusPanel::Options =&gt; FocusPanel::Devices,<br>        }<br>    }<br>}<br></code></pre></td></tr></tbody></table></figure><h3 id="AppEvent-事件类型" data-id="AppEvent-事件类型" class="notion-h"><a href="#AppEvent-事件类型" class="headerlink" title="AppEvent 事件类型"></a>AppEvent 事件类型</h3><figure class="highlight rust"><table><tbody><tr><td class="code"><pre><code class="hljs rust"><span class="hljs-comment">/// 应用事件</span><br><span class="hljs-meta">#[derive(Debug)]</span><br><span class="hljs-keyword">pub</span> <span class="hljs-keyword">enum</span> <span class="hljs-title class_">AppEvent</span> {<br>    <span class="hljs-comment">/// 键盘输入</span><br>    <span class="hljs-title function_ invoke__">Key</span>(KeyEvent),<br><br>    <span class="hljs-comment">/// 定时 Tick（UI 刷新）</span><br>    Tick,<br><br>    <span class="hljs-comment">/// 刷写阶段开始</span><br>    <span class="hljs-title function_ invoke__">FlashStageStart</span>(StageType),<br><br>    <span class="hljs-comment">/// 刷写进度更新</span><br>    FlashProgress {<br>        stage_progress: <span class="hljs-type">u64</span>,<br>        total: <span class="hljs-type">u64</span>,<br>        speed: <span class="hljs-type">f64</span>,<br>    },<br><br>    <span class="hljs-comment">/// 分区开始刷写</span><br>    <span class="hljs-title function_ invoke__">FlashPartitionStart</span>(<span class="hljs-type">String</span>),<br><br>    <span class="hljs-comment">/// 阶段完成</span><br>    FlashStageComplete,<br><br>    <span class="hljs-comment">/// 刷写完成</span><br>    FlashDone,<br><br>    <span class="hljs-comment">/// 刷写错误</span><br>    <span class="hljs-title function_ invoke__">FlashError</span>(<span class="hljs-type">String</span>),<br><br>    <span class="hljs-comment">/// 发现设备</span><br>    <span class="hljs-title function_ invoke__">DevicesFound</span>(<span class="hljs-type">Vec</span>&lt;DeviceInfo&gt;),<br><br>    <span class="hljs-comment">/// 日志消息</span><br>    <span class="hljs-title function_ invoke__">LogMessage</span>(LogLevel, <span class="hljs-type">String</span>),<br>}<br></code></pre></td></tr></tbody></table></figure><h3 id="DeviceInfo-设备信息" data-id="DeviceInfo-设备信息" class="notion-h"><a href="#DeviceInfo-设备信息" class="headerlink" title="DeviceInfo 设备信息"></a>DeviceInfo 设备信息</h3><figure class="highlight rust"><table><tbody><tr><td class="code"><pre><code class="hljs rust"><span class="hljs-comment">/// 设备信息</span><br><span class="hljs-meta">#[derive(Debug, Clone)]</span><br><span class="hljs-keyword">pub</span> <span class="hljs-keyword">struct</span> <span class="hljs-title class_">DeviceInfo</span> {<br>    <span class="hljs-comment">/// USB 总线号</span><br>    <span class="hljs-keyword">pub</span> bus: <span class="hljs-type">u8</span>,<br><br>    <span class="hljs-comment">/// USB 端口号</span><br>    <span class="hljs-keyword">pub</span> port: <span class="hljs-type">u8</span>,<br><br>    <span class="hljs-comment">/// 设备模式（FEL/FES）</span><br>    <span class="hljs-keyword">pub</span> mode: <span class="hljs-type">String</span>,<br><br>    <span class="hljs-comment">/// 芯片名称</span><br>    <span class="hljs-keyword">pub</span> chip: <span class="hljs-type">String</span>,<br><br>    <span class="hljs-comment">/// 芯片 ID</span><br>    <span class="hljs-keyword">pub</span> chip_id: <span class="hljs-type">u32</span>,<br><br>    <span class="hljs-comment">/// 是否为 FEL 模式</span><br>    <span class="hljs-keyword">pub</span> is_fel: <span class="hljs-type">bool</span>,<br>}<br></code></pre></td></tr></tbody></table></figure><h3 id="App-主结构" data-id="App-主结构" class="notion-h"><a href="#App-主结构" class="headerlink" title="App 主结构"></a>App 主结构</h3><figure class="highlight rust"><table><tbody><tr><td class="code"><pre><code class="hljs rust"><span class="hljs-comment">/// 主应用结构</span><br><span class="hljs-keyword">pub</span> <span class="hljs-keyword">struct</span> <span class="hljs-title class_">App</span> {<br>    <span class="hljs-comment">/// 应用状态</span><br>    <span class="hljs-keyword">pub</span> state: AppState,<br><br>    <span class="hljs-comment">/// 设备列表</span><br>    <span class="hljs-keyword">pub</span> devices: <span class="hljs-type">Vec</span>&lt;DeviceInfo&gt;,<br><br>    <span class="hljs-comment">/// 选中的设备索引</span><br>    <span class="hljs-keyword">pub</span> selected_device: <span class="hljs-type">usize</span>,<br><br>    <span class="hljs-comment">/// 设备列表滚动偏移</span><br>    <span class="hljs-keyword">pub</span> device_scroll_offset: <span class="hljs-type">usize</span>,<br><br>    <span class="hljs-comment">/// 固件状态</span><br>    <span class="hljs-keyword">pub</span> firmware: FirmwareState,<br><br>    <span class="hljs-comment">/// 进度状态</span><br>    <span class="hljs-keyword">pub</span> progress: ProgressState,<br><br>    <span class="hljs-comment">/// 日志状态</span><br>    <span class="hljs-keyword">pub</span> log: LogState,<br><br>    <span class="hljs-comment">/// 当前焦点</span><br>    <span class="hljs-keyword">pub</span> focus: FocusPanel,<br><br>    <span class="hljs-comment">/// 是否显示帮助</span><br>    <span class="hljs-keyword">pub</span> show_help: <span class="hljs-type">bool</span>,<br><br>    <span class="hljs-comment">/// 是否在输入模式</span><br>    <span class="hljs-keyword">pub</span> input_mode: <span class="hljs-type">bool</span>,<br><br>    <span class="hljs-comment">/// 输入缓冲区</span><br>    <span class="hljs-keyword">pub</span> input_buffer: <span class="hljs-type">String</span>,<br><br>    <span class="hljs-comment">/// 刷写开始时间</span><br>    flash_start_time: <span class="hljs-type">Option</span>&lt;Instant&gt;,<br><br>    <span class="hljs-comment">/// 固件解析器</span><br>    packer: <span class="hljs-type">Option</span>&lt;OpenixPacker&gt;,<br><br>    <span class="hljs-comment">/// 是否应退出</span><br>    should_quit: <span class="hljs-type">bool</span>,<br>}<br></code></pre></td></tr></tbody></table></figure><hr><h2 id="事件驱动架构" data-id="事件驱动架构" class="notion-h"><a href="#事件驱动架构" class="headerlink" title="事件驱动架构"></a>事件驱动架构</h2><h3 id="事件循环" data-id="事件循环" class="notion-h"><a href="#事件循环" class="headerlink" title="事件循环"></a>事件循环</h3><figure class="highlight rust"><table><tbody><tr><td class="code"><pre><code class="hljs rust"><span class="hljs-comment">/// 事件循环：轮询键盘事件和生成 Tick</span><br><span class="hljs-keyword">pub</span> <span class="hljs-keyword">async</span> <span class="hljs-keyword">fn</span> <span class="hljs-title function_">event_loop</span>(tx: mpsc::UnboundedSender&lt;AppEvent&gt;) {<br>    <span class="hljs-keyword">let</span> <span class="hljs-variable">tick_rate</span> = Duration::<span class="hljs-title function_ invoke__">from_millis</span>(<span class="hljs-number">100</span>);<br><br>    <span class="hljs-keyword">loop</span> {<br>        <span class="hljs-comment">// 使用 block_in_place 在异步上下文中阻塞轮询</span><br>        <span class="hljs-keyword">let</span> <span class="hljs-variable">has_event</span> = tokio::task::<span class="hljs-title function_ invoke__">block_in_place</span>(|| {<br>            event::<span class="hljs-title function_ invoke__">poll</span>(tick_rate).<span class="hljs-title function_ invoke__">unwrap_or</span>(<span class="hljs-literal">false</span>)<br>        });<br><br>        <span class="hljs-keyword">if</span> has_event {<br>            <span class="hljs-keyword">if</span> <span class="hljs-keyword">let</span> <span class="hljs-variable">Ok</span>(evt) = tokio::task::<span class="hljs-title function_ invoke__">block_in_place</span>(event::read) {<br>                <span class="hljs-keyword">match</span> evt {<br>                    Event::<span class="hljs-title function_ invoke__">Key</span>(key) =&gt; {<br>                        <span class="hljs-keyword">if</span> tx.<span class="hljs-title function_ invoke__">send</span>(AppEvent::<span class="hljs-title function_ invoke__">Key</span>(key)).<span class="hljs-title function_ invoke__">is_err</span>() {<br>                            <span class="hljs-keyword">return</span>;  <span class="hljs-comment">// 接收者关闭，退出循环</span><br>                        }<br>                    }<br>                    Event::<span class="hljs-title function_ invoke__">Resize</span>(_, _) =&gt; {<br>                        <span class="hljs-comment">// ratatui 自动处理终端大小变化</span><br>                    }<br>                    _ =&gt; {}<br>                }<br>            }<br>        } <span class="hljs-keyword">else</span> {<br>            <span class="hljs-comment">// 超时无事件，发送 Tick</span><br>            <span class="hljs-keyword">if</span> tx.<span class="hljs-title function_ invoke__">send</span>(AppEvent::Tick).<span class="hljs-title function_ invoke__">is_err</span>() {<br>                <span class="hljs-keyword">return</span>;<br>            }<br>        }<br>    }<br>}<br></code></pre></td></tr></tbody></table></figure><h3 id="主应用循环" data-id="主应用循环" class="notion-h"><a href="#主应用循环" class="headerlink" title="主应用循环"></a>主应用循环</h3><figure class="highlight rust"><table><tbody><tr><td class="code"><pre><code class="hljs rust"><span class="hljs-comment">/// 运行 TUI 应用</span><br><span class="hljs-keyword">pub</span> <span class="hljs-keyword">async</span> <span class="hljs-keyword">fn</span> <span class="hljs-title function_">run</span>() <span class="hljs-punctuation">-&gt;</span> anyhow::<span class="hljs-type">Result</span>&lt;()&gt; {<br>    <span class="hljs-comment">// 1. 启用 TUI 模式（禁用 indicatif 进度条）</span><br>    <span class="hljs-title function_ invoke__">set_tui_mode</span>(<span class="hljs-literal">true</span>);<br><br>    <span class="hljs-comment">// 2. 设置终端</span><br>    <span class="hljs-title function_ invoke__">enable_raw_mode</span>()?;<br>    io::<span class="hljs-title function_ invoke__">stdout</span>().<span class="hljs-title function_ invoke__">execute</span>(EnterAlternateScreen)?;<br><br>    <span class="hljs-comment">// 3. 创建 Terminal</span><br>    <span class="hljs-keyword">let</span> <span class="hljs-keyword">mut </span><span class="hljs-variable">terminal</span> = Terminal::<span class="hljs-title function_ invoke__">new</span>(CrosstermBackend::<span class="hljs-title function_ invoke__">new</span>(io::<span class="hljs-title function_ invoke__">stdout</span>()))?;<br><br>    <span class="hljs-comment">// 4. 创建事件通道</span><br>    <span class="hljs-keyword">let</span> (event_tx, event_rx) = mpsc::<span class="hljs-title function_ invoke__">unbounded_channel</span>();<br>    <span class="hljs-keyword">let</span> (log_tx, log_rx) = mpsc::<span class="hljs-title function_ invoke__">unbounded_channel</span>();<br><br>    <span class="hljs-comment">// 5. 设置 TUI 日志通道</span><br>    terminal::<span class="hljs-title function_ invoke__">set_tui_log_sender</span>(<span class="hljs-title function_ invoke__">Some</span>(log_tx.<span class="hljs-title function_ invoke__">clone</span>()));<br><br>    <span class="hljs-comment">// 6. 启动事件循环（后台任务）</span><br>    tokio::<span class="hljs-title function_ invoke__">spawn</span>(<span class="hljs-title function_ invoke__">event_loop</span>(event_tx.<span class="hljs-title function_ invoke__">clone</span>()));<br><br>    <span class="hljs-comment">// 7. 自动扫描设备</span><br>    tokio::<span class="hljs-title function_ invoke__">spawn</span>(bridge::<span class="hljs-title function_ invoke__">scan_devices</span>(event_tx.<span class="hljs-title function_ invoke__">clone</span>()));<br><br>    <span class="hljs-comment">// 8. 创建 App</span><br>    <span class="hljs-keyword">let</span> <span class="hljs-keyword">mut </span><span class="hljs-variable">app</span> = App::<span class="hljs-title function_ invoke__">new</span>();<br><br>    <span class="hljs-comment">// 9. 主循环</span><br>    <span class="hljs-keyword">while</span> !app.should_quit {<br>        <span class="hljs-comment">// 接收日志消息</span><br>        <span class="hljs-keyword">while</span> <span class="hljs-keyword">let</span> <span class="hljs-variable">Ok</span>(msg) = log_rx.<span class="hljs-title function_ invoke__">try_recv</span>() {<br>            app.log.<span class="hljs-title function_ invoke__">push</span>(msg.level, &amp;msg.message);<br>        }<br><br>        <span class="hljs-comment">// 刷写时轮询进度</span><br>        <span class="hljs-keyword">if</span> app.<span class="hljs-title function_ invoke__">is_flashing</span>() {<br>            app.<span class="hljs-title function_ invoke__">poll_progress</span>();<br>        }<br><br>        <span class="hljs-comment">// 绘制 UI</span><br>        terminal.<span class="hljs-title function_ invoke__">draw</span>(|frame| ui::<span class="hljs-title function_ invoke__">render</span>(frame, &amp;<span class="hljs-keyword">mut</span> app))?;<br><br>        <span class="hljs-comment">// 接收事件</span><br>        <span class="hljs-keyword">if</span> <span class="hljs-keyword">let</span> <span class="hljs-variable">Some</span>(event) = event_rx.<span class="hljs-title function_ invoke__">recv</span>().<span class="hljs-keyword">await</span> {<br>            <span class="hljs-keyword">match</span> event {<br>                AppEvent::<span class="hljs-title function_ invoke__">Key</span>(key) =&gt; app.<span class="hljs-title function_ invoke__">handle_key</span>(key),<br>                AppEvent::Tick =&gt; { <span class="hljs-comment">/* UI 刷新 */</span> }<br>                AppEvent::<span class="hljs-title function_ invoke__">DevicesFound</span>(devices) =&gt; {<br>                    app.devices = devices;<br>                    app.<span class="hljs-title function_ invoke__">update_state</span>();<br>                }<br>                AppEvent::<span class="hljs-title function_ invoke__">LogMessage</span>(level, msg) =&gt; {<br>                    app.log.<span class="hljs-title function_ invoke__">push</span>(level, &amp;msg);<br>                }<br>                AppEvent::FlashDone =&gt; {<br>                    app.state = AppState::Done;<br>                    app.<span class="hljs-title function_ invoke__">reload_firmware</span>();<br>                }<br>                AppEvent::<span class="hljs-title function_ invoke__">FlashError</span>(msg) =&gt; {<br>                    app.state = AppState::Error;<br>                    app.log.<span class="hljs-title function_ invoke__">push</span>(LogLevel::Error, &amp;msg);<br>                }<br>                <span class="hljs-comment">// ... 其他事件处理</span><br>            }<br>        }<br>    }<br><br>    <span class="hljs-comment">// 10. 清理</span><br>    terminal::<span class="hljs-title function_ invoke__">set_tui_log_sender</span>(<span class="hljs-literal">None</span>);<br>    <span class="hljs-title function_ invoke__">disable_raw_mode</span>()?;<br>    io::<span class="hljs-title function_ invoke__">stdout</span>().<span class="hljs-title function_ invoke__">execute</span>(LeaveAlternateScreen)?;<br><br>    <span class="hljs-title function_ invoke__">Ok</span>(())<br>}<br></code></pre></td></tr></tbody></table></figure><h3 id="事件流程图" data-id="事件流程图" class="notion-h"><a href="#事件流程图" class="headerlink" title="事件流程图"></a>事件流程图</h3><pre class="mermaid">sequenceDiagram    participant User as 用户    participant EventLoop as event_loop    participant MainLoop as 主循环    participant App as App 状态    participant Bridge as bridge    participant Flasher as Flasher    EventLoop-&gt;&gt;MainLoop: Key(KeyEvent)    EventLoop-&gt;&gt;MainLoop: Tick (每 100ms)    User-&gt;&gt;Bridge: 按下 's' 键    MainLoop-&gt;&gt;App: handle_key(KeyCode::Char('s'))    App-&gt;&gt;Bridge: spawn scan_devices()    Bridge-&gt;&gt;MainLoop: DevicesFound([DeviceInfo])    MainLoop-&gt;&gt;App: 更新设备列表    User-&gt;&gt;Bridge: 按下 'f' 键（刷写）    MainLoop-&gt;&gt;App: handle_key(KeyCode::Char('f'))    App-&gt;&gt;Bridge: spawn run_flash()    Bridge-&gt;&gt;Flasher: execute()    loop 刷写过程        Flasher-&gt;&gt;MainLoop: FlashProgress        MainLoop-&gt;&gt;App: poll_progress()        MainLoop-&gt;&gt;App: 更新进度显示    end    Flasher-&gt;&gt;MainLoop: FlashDone/FlashError    MainLoop-&gt;&gt;App: 更新状态</pre><hr><h2 id="Bridge-函数详解" data-id="Bridge-函数详解" class="notion-h"><a href="#Bridge-函数详解" class="headerlink" title="Bridge 函数详解"></a>Bridge 函数详解</h2><h3 id="scan-devices-设备扫描" data-id="scan-devices-设备扫描" class="notion-h"><a href="#scan-devices-设备扫描" class="headerlink" title="scan_devices 设备扫描"></a>scan_devices 设备扫描</h3><figure class="highlight rust"><table><tbody><tr><td class="code"><pre><code class="hljs rust"><span class="hljs-comment">/// 扫描 USB 设备并发送结果</span><br><span class="hljs-keyword">pub</span> <span class="hljs-keyword">async</span> <span class="hljs-keyword">fn</span> <span class="hljs-title function_">scan_devices</span>(tx: mpsc::UnboundedSender&lt;AppEvent&gt;) {<br>    <span class="hljs-comment">// 发送扫描开始消息</span><br>    <span class="hljs-keyword">let</span> <span class="hljs-variable">_</span> = tx.<span class="hljs-title function_ invoke__">send</span>(AppEvent::<span class="hljs-title function_ invoke__">LogMessage</span>(<br>        LogLevel::Info,<br>        <span class="hljs-string">"Scanning for devices..."</span>.<span class="hljs-title function_ invoke__">into</span>(),<br>    ));<br><br>    <span class="hljs-keyword">match</span> libefex::Context::<span class="hljs-title function_ invoke__">scan_usb_devices</span>() {<br>        <span class="hljs-title function_ invoke__">Ok</span>(devices) =&gt; {<br>            <span class="hljs-keyword">if</span> devices.<span class="hljs-title function_ invoke__">is_empty</span>() {<br>                <span class="hljs-keyword">let</span> <span class="hljs-variable">_</span> = tx.<span class="hljs-title function_ invoke__">send</span>(AppEvent::<span class="hljs-title function_ invoke__">LogMessage</span>(LogLevel::Warn, <span class="hljs-string">"No devices found"</span>.<span class="hljs-title function_ invoke__">into</span>()));<br>                <span class="hljs-keyword">let</span> <span class="hljs-variable">_</span> = tx.<span class="hljs-title function_ invoke__">send</span>(AppEvent::<span class="hljs-title function_ invoke__">DevicesFound</span>(<span class="hljs-built_in">vec!</span>[]));<br>                <span class="hljs-keyword">return</span>;<br>            }<br><br>            <span class="hljs-keyword">let</span> <span class="hljs-keyword">mut </span><span class="hljs-variable">infos</span> = <span class="hljs-type">Vec</span>::<span class="hljs-title function_ invoke__">new</span>();<br>            <span class="hljs-keyword">for</span> <span class="hljs-variable">dev</span> <span class="hljs-keyword">in</span> &amp;devices {<br>                <span class="hljs-keyword">let</span> <span class="hljs-keyword">mut </span><span class="hljs-variable">ctx</span> = libefex::Context::<span class="hljs-title function_ invoke__">new</span>();<br>                <span class="hljs-keyword">if</span> ctx.<span class="hljs-title function_ invoke__">scan_usb_device_at</span>(dev.bus, dev.port).<span class="hljs-title function_ invoke__">is_err</span>() {<br>                    <span class="hljs-keyword">continue</span>;<br>                }<br>                <span class="hljs-keyword">if</span> ctx.<span class="hljs-title function_ invoke__">usb_init</span>().<span class="hljs-title function_ invoke__">is_err</span>() {<br>                    <span class="hljs-keyword">continue</span>;<br>                }<br>                <span class="hljs-keyword">if</span> ctx.<span class="hljs-title function_ invoke__">efex_init</span>().<span class="hljs-title function_ invoke__">is_err</span>() {<br>                    <span class="hljs-keyword">continue</span>;<br>                }<br><br>                <span class="hljs-comment">// 获取设备信息</span><br>                <span class="hljs-keyword">let</span> <span class="hljs-variable">mode</span> = ctx.<span class="hljs-title function_ invoke__">get_device_mode</span>();<br>                <span class="hljs-keyword">let</span> <span class="hljs-variable">is_fel</span> = mode == libefex::DeviceMode::Fel;<br>                <span class="hljs-keyword">let</span> <span class="hljs-variable">mode_str</span> = <span class="hljs-keyword">match</span> mode {<br>                    libefex::DeviceMode::Fel =&gt; <span class="hljs-string">"FEL"</span>,<br>                    libefex::DeviceMode::Srv =&gt; <span class="hljs-string">"FES"</span>,<br>                    <span class="hljs-comment">// ...</span><br>                };<br><br>                <span class="hljs-keyword">let</span> <span class="hljs-variable">chip</span> = ctx.<span class="hljs-title function_ invoke__">get_device_mode_str</span>().<span class="hljs-title function_ invoke__">to_string</span>();<br>                <span class="hljs-keyword">let</span> <span class="hljs-variable">chip_id</span> = <span class="hljs-keyword">unsafe</span> { (*ctx.<span class="hljs-title function_ invoke__">as_ptr</span>()).resp.id };<br><br>                infos.<span class="hljs-title function_ invoke__">push</span>(DeviceInfo {<br>                    bus: dev.bus,<br>                    port: dev.port,<br>                    mode: mode_str.<span class="hljs-title function_ invoke__">into</span>(),<br>                    chip,<br>                    chip_id,<br>                    is_fel,<br>                });<br>            }<br><br>            <span class="hljs-keyword">let</span> <span class="hljs-variable">_</span> = tx.<span class="hljs-title function_ invoke__">send</span>(AppEvent::<span class="hljs-title function_ invoke__">DevicesFound</span>(infos));<br>        }<br>        <span class="hljs-title function_ invoke__">Err</span>(e) =&gt; {<br>            <span class="hljs-keyword">let</span> <span class="hljs-variable">_</span> = tx.<span class="hljs-title function_ invoke__">send</span>(AppEvent::<span class="hljs-title function_ invoke__">LogMessage</span>(LogLevel::Error, <span class="hljs-built_in">format!</span>(<span class="hljs-string">"Scan failed: {}"</span>, e)));<br>        }<br>    }<br>}<br></code></pre></td></tr></tbody></table></figure><h3 id="run-flash-后台刷写" data-id="run-flash-后台刷写" class="notion-h"><a href="#run-flash-后台刷写" class="headerlink" title="run_flash 后台刷写"></a>run_flash 后台刷写</h3><figure class="highlight rust"><table><tbody><tr><td class="code"><pre><code class="hljs rust"><span class="hljs-comment">/// 在后台线程运行刷写任务</span><br><span class="hljs-comment">///</span><br><span class="hljs-comment">/// 使用 spawn_blocking 因为 libefex::Context 包含 raw pointer 且不是 Send</span><br><span class="hljs-keyword">pub</span> <span class="hljs-keyword">async</span> <span class="hljs-keyword">fn</span> <span class="hljs-title function_">run_flash</span>(<br>    tx: mpsc::UnboundedSender&lt;AppEvent&gt;,<br>    packer: OpenixPacker,<br>    bus: <span class="hljs-type">Option</span>&lt;<span class="hljs-type">u8</span>&gt;,<br>    port: <span class="hljs-type">Option</span>&lt;<span class="hljs-type">u8</span>&gt;,<br>    mode: CmdFlashMode,<br>    verify: <span class="hljs-type">bool</span>,<br>    partitions: <span class="hljs-type">Option</span>&lt;<span class="hljs-type">Vec</span>&lt;<span class="hljs-type">String</span>&gt;&gt;,<br>    post_action: <span class="hljs-type">String</span>,<br>) {<br>    <span class="hljs-comment">// 构建刷写选项</span><br>    <span class="hljs-keyword">let</span> <span class="hljs-variable">flash_mode</span> = <span class="hljs-keyword">match</span> mode {<br>        CmdFlashMode::Partition =&gt; FlashMode::Partition,<br>        CmdFlashMode::KeepData =&gt; FlashMode::KeepData,<br>        CmdFlashMode::PartitionErase =&gt; FlashMode::PartitionErase,<br>        CmdFlashMode::FullErase =&gt; FlashMode::FullErase,<br>    };<br><br>    <span class="hljs-keyword">let</span> <span class="hljs-variable">options</span> = FlashOptions {<br>        bus,<br>        port,<br>        verify,<br>        mode: flash_mode,<br>        partitions,<br>        post_action: post_action.<span class="hljs-title function_ invoke__">clone</span>(),<br>    };<br><br>    <span class="hljs-comment">// 使用 spawn_blocking 运行刷写（libefex::Context 不是 Send）</span><br>    <span class="hljs-keyword">let</span> <span class="hljs-variable">result</span> = tokio::task::<span class="hljs-title function_ invoke__">spawn_blocking</span>(<span class="hljs-keyword">move</span> || {<br>        <span class="hljs-keyword">let</span> <span class="hljs-variable">rt</span> = tokio::runtime::Handle::<span class="hljs-title function_ invoke__">current</span>();<br>        rt.<span class="hljs-title function_ invoke__">block_on</span>(<span class="hljs-keyword">async</span> {<br>            <span class="hljs-keyword">let</span> <span class="hljs-keyword">mut </span><span class="hljs-variable">flasher</span> = Flasher::<span class="hljs-title function_ invoke__">new</span>(packer, options, cli_logger);<br>            flasher.<span class="hljs-title function_ invoke__">execute</span>().<span class="hljs-keyword">await</span><br>        })<br>    }).<span class="hljs-keyword">await</span>;<br><br>    <span class="hljs-keyword">match</span> result {<br>        <span class="hljs-title function_ invoke__">Ok</span>(<span class="hljs-title function_ invoke__">Ok</span>(())) =&gt; {<br>            <span class="hljs-keyword">let</span> <span class="hljs-variable">_</span> = tx.<span class="hljs-title function_ invoke__">send</span>(AppEvent::FlashDone);<br>        }<br>        <span class="hljs-title function_ invoke__">Ok</span>(<span class="hljs-title function_ invoke__">Err</span>(e)) =&gt; {<br>            <span class="hljs-keyword">let</span> <span class="hljs-variable">_</span> = tx.<span class="hljs-title function_ invoke__">send</span>(AppEvent::<span class="hljs-title function_ invoke__">FlashError</span>(<span class="hljs-built_in">format!</span>(<span class="hljs-string">"{}"</span>, e)));<br>        }<br>        <span class="hljs-title function_ invoke__">Err</span>(e) =&gt; {<br>            <span class="hljs-keyword">let</span> <span class="hljs-variable">_</span> = tx.<span class="hljs-title function_ invoke__">send</span>(AppEvent::<span class="hljs-title function_ invoke__">FlashError</span>(<span class="hljs-built_in">format!</span>(<span class="hljs-string">"Flash task panicked: {}"</span>, e)));<br>        }<br>    }<br>}<br></code></pre></td></tr></tbody></table></figure><hr><h2 id="键盘处理" data-id="键盘处理" class="notion-h"><a href="#键盘处理" class="headerlink" title="键盘处理"></a>键盘处理</h2><h3 id="handle-key-实现" data-id="handle-key-实现" class="notion-h"><a href="#handle-key-实现" class="headerlink" title="handle_key 实现"></a>handle_key 实现</h3><figure class="highlight rust"><table><tbody><tr><td class="code"><pre><code class="hljs rust"><span class="hljs-comment">/// 处理键盘输入</span><br><span class="hljs-keyword">fn</span> <span class="hljs-title function_">handle_key</span>(&amp;<span class="hljs-keyword">mut</span> <span class="hljs-keyword">self</span>, key: KeyEvent) {<br>    <span class="hljs-comment">// 仅处理 Press 事件</span><br>    <span class="hljs-keyword">if</span> key.kind != KeyEventKind::Press {<br>        <span class="hljs-keyword">return</span>;<br>    }<br><br>    <span class="hljs-comment">// 帮助界面</span><br>    <span class="hljs-keyword">if</span> <span class="hljs-keyword">self</span>.show_help {<br>        <span class="hljs-keyword">if</span> key.code == KeyCode::Esc || key.code == KeyCode::<span class="hljs-title function_ invoke__">Char</span>(<span class="hljs-string">'h'</span>) {<br>            <span class="hljs-keyword">self</span>.show_help = <span class="hljs-literal">false</span>;<br>        }<br>        <span class="hljs-keyword">return</span>;<br>    }<br><br>    <span class="hljs-comment">// 输入模式（路径输入）</span><br>    <span class="hljs-keyword">if</span> <span class="hljs-keyword">self</span>.input_mode {<br>        <span class="hljs-keyword">self</span>.<span class="hljs-title function_ invoke__">handle_input_key</span>(key);<br>        <span class="hljs-keyword">return</span>;<br>    }<br><br>    <span class="hljs-comment">// 刷写过程中仅允许 Ctrl+C 和 Esc</span><br>    <span class="hljs-keyword">if</span> <span class="hljs-keyword">self</span>.<span class="hljs-title function_ invoke__">is_flashing</span>() {<br>        <span class="hljs-keyword">if</span> key.code == KeyCode::Esc || key.modifiers.<span class="hljs-title function_ invoke__">contains</span>(KeyModifiers::CONTROL) &amp;&amp; key.code == KeyCode::<span class="hljs-title function_ invoke__">Char</span>(<span class="hljs-string">'c'</span>) {<br>            <span class="hljs-keyword">self</span>.should_quit = <span class="hljs-literal">true</span>;<br>        }<br>        <span class="hljs-keyword">return</span>;<br>    }<br><br>    <span class="hljs-keyword">match</span> key.code {<br>        KeyCode::<span class="hljs-title function_ invoke__">Char</span>(<span class="hljs-string">'q'</span>) | KeyCode::Esc =&gt; <span class="hljs-keyword">self</span>.should_quit = <span class="hljs-literal">true</span>,<br>        KeyCode::<span class="hljs-title function_ invoke__">Char</span>(<span class="hljs-string">'h'</span>) =&gt; <span class="hljs-keyword">self</span>.show_help = <span class="hljs-literal">true</span>,<br>        KeyCode::Tab =&gt; <span class="hljs-keyword">self</span>.focus = <span class="hljs-keyword">self</span>.focus.<span class="hljs-title function_ invoke__">toggle</span>(),<br>        KeyCode::<span class="hljs-title function_ invoke__">Char</span>(<span class="hljs-string">'s'</span>) =&gt; <span class="hljs-keyword">self</span>.<span class="hljs-title function_ invoke__">start_scan</span>(),<br>        KeyCode::<span class="hljs-title function_ invoke__">Char</span>(<span class="hljs-string">'r'</span>) =&gt; <span class="hljs-keyword">self</span>.<span class="hljs-title function_ invoke__">reload_firmware</span>(),<br>        KeyCode::<span class="hljs-title function_ invoke__">Char</span>(<span class="hljs-string">'f'</span>) =&gt; <span class="hljs-keyword">self</span>.<span class="hljs-title function_ invoke__">start_flash</span>(),<br>        KeyCode::<span class="hljs-title function_ invoke__">Char</span>(<span class="hljs-string">'p'</span>) =&gt; <span class="hljs-keyword">self</span>.firmware.<span class="hljs-title function_ invoke__">toggle_partition_selection</span>(),<br>        KeyCode::Up =&gt; <span class="hljs-keyword">self</span>.<span class="hljs-title function_ invoke__">handle_up</span>(),<br>        KeyCode::Down =&gt; <span class="hljs-keyword">self</span>.<span class="hljs-title function_ invoke__">handle_down</span>(),<br>        KeyCode::Enter =&gt; <span class="hljs-keyword">self</span>.<span class="hljs-title function_ invoke__">handle_enter</span>(),<br>        _ =&gt; {}<br>    }<br>}<br></code></pre></td></tr></tbody></table></figure><h3 id="快捷键列表" data-id="快捷键列表" class="notion-h"><a href="#快捷键列表" class="headerlink" title="快捷键列表"></a>快捷键列表</h3><table><thead><tr><th>键</th><th>功能</th></tr></thead><tbody><tr><td><code>q</code> / <code>Esc</code></td><td>退出</td></tr><tr><td><code>h</code></td><td>显示帮助</td></tr><tr><td><code>s</code></td><td>扫描设备</td></tr><tr><td><code>r</code></td><td>重新加载固件</td></tr><tr><td><code>f</code></td><td>开始刷写</td></tr><tr><td><code>p</code></td><td>切换分区选择</td></tr><tr><td><code>Tab</code></td><td>切换焦点面板</td></tr><tr><td><code>↑</code> / <code>↓</code></td><td>滚动选择</td></tr><tr><td><code>Enter</code></td><td>确认选择</td></tr><tr><td><code>Ctrl+C</code></td><td>强制退出</td></tr></tbody></table><hr><h2 id="进度轮询机制" data-id="进度轮询机制" class="notion-h"><a href="#进度轮询机制" class="headerlink" title="进度轮询机制"></a>进度轮询机制</h2><h3 id="poll-progress-实现" data-id="poll-progress-实现" class="notion-h"><a href="#poll-progress-实现" class="headerlink" title="poll_progress 实现"></a>poll_progress 实现</h3><figure class="highlight rust"><table><tbody><tr><td class="code"><pre><code class="hljs rust"><span class="hljs-comment">/// 轮询 GlobalProgress 并更新 UI 状态</span><br><span class="hljs-keyword">fn</span> <span class="hljs-title function_">poll_progress</span>(&amp;<span class="hljs-keyword">mut</span> <span class="hljs-keyword">self</span>) {<br>    <span class="hljs-keyword">let</span> <span class="hljs-variable">gp</span> = <span class="hljs-title function_ invoke__">global_progress</span>();<br>    <span class="hljs-keyword">let</span> <span class="hljs-variable">snap</span> = gp.<span class="hljs-title function_ invoke__">snapshot</span>();<br><br>    <span class="hljs-comment">// 更新进度百分比</span><br>    <span class="hljs-keyword">self</span>.progress.overall_percent = snap.precise_progress;<br>    <span class="hljs-keyword">self</span>.progress.stage_progress = snap.stage_progress;<br>    <span class="hljs-keyword">self</span>.progress.stage_total = snap.total_bytes;<br>    <span class="hljs-keyword">self</span>.progress.speed = snap.speed;<br><br>    <span class="hljs-comment">// 更新当前分区</span><br>    <span class="hljs-keyword">if</span> !snap.current_partition.<span class="hljs-title function_ invoke__">is_empty</span>() {<br>        <span class="hljs-keyword">self</span>.progress.current_partition = snap.current_partition;<br>    }<br><br>    <span class="hljs-comment">// 更新当前阶段</span><br>    <span class="hljs-keyword">if</span> snap.current_stage_index &lt; snap.stages.<span class="hljs-title function_ invoke__">len</span>() {<br>        <span class="hljs-keyword">let</span> <span class="hljs-variable">current</span> = snap.stages[snap.current_stage_index].stage_type;<br>        <span class="hljs-keyword">self</span>.progress.current_stage = <span class="hljs-title function_ invoke__">Some</span>(current);<br>        <span class="hljs-keyword">self</span>.progress.stage_index = snap.current_stage_index;<br>    }<br><br>    <span class="hljs-comment">// 更新已完成阶段</span><br>    <span class="hljs-keyword">self</span>.progress.completed_stages.<span class="hljs-title function_ invoke__">clear</span>();<br>    <span class="hljs-keyword">for</span> <span class="hljs-variable">stage_info</span> <span class="hljs-keyword">in</span> &amp;snap.stages {<br>        <span class="hljs-keyword">if</span> stage_info.completed {<br>            <span class="hljs-keyword">self</span>.progress.completed_stages.<span class="hljs-title function_ invoke__">push</span>(stage_info.stage_type);<br>        }<br>    }<br>}<br></code></pre></td></tr></tbody></table></figure><hr><h2 id="UI-渲染" data-id="UI-渲染" class="notion-h"><a href="#UI-渲染" class="headerlink" title="UI 渲染"></a>UI 渲染</h2><h3 id="布局结构" data-id="布局结构" class="notion-h"><a href="#布局结构" class="headerlink" title="布局结构"></a>布局结构</h3><figure class="highlight rust"><table><tbody><tr><td class="code"><pre><code class="hljs rust"><span class="hljs-comment">/// 渲染 UI</span><br><span class="hljs-keyword">pub</span> <span class="hljs-keyword">fn</span> <span class="hljs-title function_">render</span>(frame: &amp;<span class="hljs-keyword">mut</span> Frame, app: &amp;<span class="hljs-keyword">mut</span> App) {<br>    <span class="hljs-comment">// 创建主布局</span><br>    <span class="hljs-keyword">let</span> <span class="hljs-variable">layout</span> = <span class="hljs-keyword">if</span> frame.<span class="hljs-title function_ invoke__">area</span>().width &gt;= <span class="hljs-number">100</span> {<br>        <span class="hljs-comment">// 宽屏布局：左侧面板 + 右侧日志</span><br>        Layout::<span class="hljs-title function_ invoke__">default</span>()<br>            .<span class="hljs-title function_ invoke__">direction</span>(Direction::Horizontal)<br>            .<span class="hljs-title function_ invoke__">constraints</span>([Constraint::<span class="hljs-title function_ invoke__">Percentage</span>(<span class="hljs-number">50</span>), Constraint::<span class="hljs-title function_ invoke__">Percentage</span>(<span class="hljs-number">50</span>)])<br>            .<span class="hljs-title function_ invoke__">split</span>(frame.<span class="hljs-title function_ invoke__">area</span>())<br>    } <span class="hljs-keyword">else</span> {<br>        <span class="hljs-comment">// 窄屏布局：上下布局</span><br>        Layout::<span class="hljs-title function_ invoke__">default</span>()<br>            .<span class="hljs-title function_ invoke__">direction</span>(Direction::Vertical)<br>            .<span class="hljs-title function_ invoke__">constraints</span>([Constraint::<span class="hljs-title function_ invoke__">Percentage</span>(<span class="hljs-number">50</span>), Constraint::<span class="hljs-title function_ invoke__">Percentage</span>(<span class="hljs-number">50</span>)])<br>            .<span class="hljs-title function_ invoke__">split</span>(frame.<span class="hljs-title function_ invoke__">area</span>())<br>    };<br><br>    <span class="hljs-comment">// 左侧布局（设备 + 选项）</span><br>    <span class="hljs-keyword">let</span> <span class="hljs-variable">left_layout</span> = Layout::<span class="hljs-title function_ invoke__">default</span>()<br>        .<span class="hljs-title function_ invoke__">direction</span>(Direction::Vertical)<br>        .<span class="hljs-title function_ invoke__">constraints</span>([Constraint::<span class="hljs-title function_ invoke__">Percentage</span>(<span class="hljs-number">50</span>), Constraint::<span class="hljs-title function_ invoke__">Percentage</span>(<span class="hljs-number">50</span>)])<br>        .<span class="hljs-title function_ invoke__">split</span>(layout[<span class="hljs-number">0</span>]);<br><br>    <span class="hljs-comment">// 渲染设备列表</span><br>    <span class="hljs-title function_ invoke__">render_device_list</span>(frame, left_layout[<span class="hljs-number">0</span>], app);<br><br>    <span class="hljs-comment">// 渲染固件信息和选项</span><br>    <span class="hljs-title function_ invoke__">render_firmware_info</span>(frame, left_layout[<span class="hljs-number">1</span>], app);<br><br>    <span class="hljs-comment">// 渲染进度条</span><br>    <span class="hljs-title function_ invoke__">render_progress</span>(frame, layout[<span class="hljs-number">1</span>], app);<br><br>    <span class="hljs-comment">// 渲染日志</span><br>    <span class="hljs-title function_ invoke__">render_log_view</span>(frame, layout[<span class="hljs-number">2</span>], app);<br><br>    <span class="hljs-comment">// 渲染标题栏</span><br>    <span class="hljs-title function_ invoke__">render_title_bar</span>(frame, frame.<span class="hljs-title function_ invoke__">area</span>());<br><br>    <span class="hljs-comment">// 渲染帮助界面（如果显示）</span><br>    <span class="hljs-keyword">if</span> app.show_help {<br>        <span class="hljs-title function_ invoke__">render_help_overlay</span>(frame, frame.<span class="hljs-title function_ invoke__">area</span>());<br>    }<br>}<br></code></pre></td></tr></tbody></table></figure><h3 id="布局图" data-id="布局图" class="notion-h"><a href="#布局图" class="headerlink" title="布局图"></a>布局图</h3><figure class="highlight plaintext"><table><tbody><tr><td class="code"><pre><code class="hljs plaintext">┌──────────────────────────────────────────────────────────────────────────┐<br>│                          OpenixCLI - Firmware Flasher                    │<br>├─────────────────────────────────────────┬────────────────────────────────┤<br>│                                         │                                │<br>│  ┌─────────────────────────────────┐    │  ┌────────────────────────────┐│<br>│  │       Device List               │    │  │        Progress            ││<br>│  │  [1] Bus 001, Port 002          │    │  │  ████████████░░░░░░ 75%    ││<br>│  │      Chip: sun50iw10            │    │  │  [rootfs] 3.2 MB/s         ││<br>│  │      Mode: FEL                  │    │  │                            ││<br>│  │  [2] Bus 002, Port 001          │    │  │  Stages:                   ││<br>│  │      Mode: FES                  │    │  │  ✓ Init                    ││<br>│  │                                 │    │  │  ✓ DRAM Init               ││<br>│  │  Press 's' to scan              │    │  │  ✓ U-Boot                  ││<br>│  └─────────────────────────────────┘    │  │  → Flashing Partitions     ││<br>│                                         │  │                            ││<br>│  ┌─────────────────────────────────┐    │  └────────────────────────────┘│<br>│  │       Firmware Options          │    │                                │<br>│  │  Path: firmware.fex              │    │  ┌────────────────────────────┐│<br>│  │  Size: 128 MB                   │    │  │        Log View            ││<br>│  │  Files: 12                      │    │  │  [INFO] Loading firmware    ││<br>│  │  Mode: Full Erase               │    │  │  [INFO] Device connected   ││<br>│  │  Partitions: boot, rootfs       │    │  │  [OKAY] DRAM initialized   ││<br>│  │                                 │    │  │  [WARN] Partition skipped  ││<br>│  │  [Flash] Press 'f'              │    │  │  [ERRO] Transfer failed    ││<br>│  └─────────────────────────────────┘    │  └────────────────────────────┘│<br>│                                         │                                │<br>├─────────────────────────────────────────┴────────────────────────────────┤<br>│  Status: Ready | Help: 'h' | Scan: 's' | Flash: 'f' | Quit: 'q'          │<br>└──────────────────────────────────────────────────────────────────────────┘<br></code></pre></td></tr></tbody></table></figure><hr><h2 id="代码亮点" data-id="代码亮点" class="notion-h"><a href="#代码亮点" class="headerlink" title="代码亮点"></a>代码亮点</h2><h3 id="1-spawn-blocking-处理-Send-类型" data-id="1-spawn-blocking-处理-Send-类型" class="notion-h"><a href="#1-spawn-blocking-处理-Send-类型" class="headerlink" title="1. spawn_blocking 处理 !Send 类型"></a>1. spawn_blocking 处理 !Send 类型</h3><figure class="highlight rust"><table><tbody><tr><td class="code"><pre><code class="hljs rust"><span class="hljs-comment">// libefex::Context 包含 raw pointer，不是 Send</span><br><span class="hljs-comment">// 使用 spawn_blocking 在专用线程中运行</span><br><span class="hljs-keyword">let</span> <span class="hljs-variable">result</span> = tokio::task::<span class="hljs-title function_ invoke__">spawn_blocking</span>(<span class="hljs-keyword">move</span> || {<br>    <span class="hljs-keyword">let</span> <span class="hljs-variable">rt</span> = tokio::runtime::Handle::<span class="hljs-title function_ invoke__">current</span>();<br>    rt.<span class="hljs-title function_ invoke__">block_on</span>(<span class="hljs-keyword">async</span> {<br>        <span class="hljs-keyword">let</span> <span class="hljs-keyword">mut </span><span class="hljs-variable">flasher</span> = Flasher::<span class="hljs-title function_ invoke__">new</span>(packer, options, cli_logger);<br>        flasher.<span class="hljs-title function_ invoke__">execute</span>().<span class="hljs-keyword">await</span><br>    })<br>}).<span class="hljs-keyword">await</span>;<br></code></pre></td></tr></tbody></table></figure><h3 id="2-block-in-place-在异步中阻塞" data-id="2-block-in-place-在异步中阻塞" class="notion-h"><a href="#2-block-in-place-在异步中阻塞" class="headerlink" title="2. block_in_place 在异步中阻塞"></a>2. block_in_place 在异步中阻塞</h3><figure class="highlight rust"><table><tbody><tr><td class="code"><pre><code class="hljs rust"><span class="hljs-comment">// 在异步上下文中阻塞调用 crossterm 的 event::poll</span><br><span class="hljs-keyword">let</span> <span class="hljs-variable">has_event</span> = tokio::task::<span class="hljs-title function_ invoke__">block_in_place</span>(|| {<br>    event::<span class="hljs-title function_ invoke__">poll</span>(tick_rate).<span class="hljs-title function_ invoke__">unwrap_or</span>(<span class="hljs-literal">false</span>)<br>});<br></code></pre></td></tr></tbody></table></figure><h3 id="3-mpsc-unbounded-channel-无界通道" data-id="3-mpsc-unbounded-channel-无界通道" class="notion-h"><a href="#3-mpsc-unbounded-channel-无界通道" class="headerlink" title="3. mpsc::unbounded_channel 无界通道"></a>3. mpsc::unbounded_channel 无界通道</h3><figure class="highlight rust"><table><tbody><tr><td class="code"><pre><code class="hljs rust"><span class="hljs-comment">// 创建无界通道（适合事件流）</span><br><span class="hljs-keyword">let</span> (event_tx, event_rx) = mpsc::<span class="hljs-title function_ invoke__">unbounded_channel</span>();<br><br><span class="hljs-comment">// 发送事件（无阻塞）</span><br>event_tx.<span class="hljs-title function_ invoke__">send</span>(AppEvent::<span class="hljs-title function_ invoke__">Key</span>(key))?;<br><br><span class="hljs-comment">// 接收事件（异步）</span><br><span class="hljs-keyword">if</span> <span class="hljs-keyword">let</span> <span class="hljs-variable">Some</span>(event) = event_rx.<span class="hljs-title function_ invoke__">recv</span>().<span class="hljs-keyword">await</span> {<br>    <span class="hljs-comment">// 处理事件</span><br>}<br></code></pre></td></tr></tbody></table></figure><h3 id="4-ratatui-状态驱动渲染" data-id="4-ratatui-状态驱动渲染" class="notion-h"><a href="#4-ratatui-状态驱动渲染" class="headerlink" title="4. ratatui 状态驱动渲染"></a>4. ratatui 状态驱动渲染</h3><figure class="highlight rust"><table><tbody><tr><td class="code"><pre><code class="hljs rust"><span class="hljs-comment">// 每帧重新渲染整个 UI</span><br>terminal.<span class="hljs-title function_ invoke__">draw</span>(|frame| {<br>    ui::<span class="hljs-title function_ invoke__">render</span>(frame, &amp;<span class="hljs-keyword">mut</span> app);<br>})?;<br></code></pre></td></tr></tbody></table></figure><h3 id="5-GlobalProgress-快照轮询" data-id="5-GlobalProgress-快照轮询" class="notion-h"><a href="#5-GlobalProgress-快照轮询" class="headerlink" title="5. GlobalProgress 快照轮询"></a>5. GlobalProgress 快照轮询</h3><figure class="highlight rust"><table><tbody><tr><td class="code"><pre><code class="hljs rust"><span class="hljs-comment">// TUI 不直接更新进度条，而是轮询快照</span><br><span class="hljs-keyword">let</span> <span class="hljs-variable">snap</span> = <span class="hljs-title function_ invoke__">global_progress</span>().<span class="hljs-title function_ invoke__">snapshot</span>();<br><span class="hljs-keyword">self</span>.progress.overall_percent = snap.precise_progress;<br></code></pre></td></tr></tbody></table></figure><hr><h2 id="实践示例" data-id="实践示例" class="notion-h"><a href="#实践示例" class="headerlink" title="实践示例"></a>实践示例</h2><h3 id="启动-TUI" data-id="启动-TUI" class="notion-h"><a href="#启动-TUI" class="headerlink" title="启动 TUI"></a>启动 TUI</h3><figure class="highlight bash"><table><tbody><tr><td class="code"><pre><code class="hljs bash">$ openixcli<br><span class="hljs-comment"># 或</span><br>$ openixcli tui<br></code></pre></td></tr></tbody></table></figure><h3 id="交互流程" data-id="交互流程" class="notion-h"><a href="#交互流程" class="headerlink" title="交互流程"></a>交互流程</h3><ol><li><strong>扫描设备</strong>：按 <code>s</code> 扫描 USB 设备</li><li><strong>选择设备</strong>：使用 <code>↑</code>/<code>↓</code> 选择，<code>Enter</code> 确认</li><li><strong>加载固件</strong>：输入固件路径或拖拽文件</li><li><strong>配置选项</strong>：<code>Tab</code> 切换到选项面板，设置刷写模式</li><li><strong>开始刷写</strong>：按 <code>f</code> 开始刷写</li><li><strong>查看进度</strong>：实时显示刷写进度和日志</li><li><strong>完成</strong>：按 <code>q</code> 或 <code>Esc</code> 退出</li></ol><h3 id="状态转换图" data-id="状态转换图" class="notion-h"><a href="#状态转换图" class="headerlink" title="状态转换图"></a>状态转换图</h3><pre class="mermaid">stateDiagram-v2    [*] --&gt; Idle: 启动    Idle --&gt; Ready: 有设备 + 有固件    Ready --&gt; Idle: 无设备 或 无固件    Ready --&gt; Flashing: 按 'f'    Flashing --&gt; Done: 刷写成功    Flashing --&gt; Error: 刷写失败    Done --&gt; Ready: 可以再次刷写    Error --&gt; Ready: 修复后可再次刷写    Idle --&gt; [*]: 按 'q'    Ready --&gt; [*]: 按 'q'    Done --&gt; [*]: 按 'q'    Error --&gt; [*]: 按 'q'</pre><hr><h2 id="Widget-组件" data-id="Widget-组件" class="notion-h"><a href="#Widget-组件" class="headerlink" title="Widget 组件"></a>Widget 组件</h2><h3 id="ProgressState" data-id="ProgressState" class="notion-h"><a href="#ProgressState" class="headerlink" title="ProgressState"></a>ProgressState</h3><figure class="highlight rust"><table><tbody><tr><td class="code"><pre><code class="hljs rust"><span class="hljs-comment">/// 进度状态</span><br><span class="hljs-keyword">pub</span> <span class="hljs-keyword">struct</span> <span class="hljs-title class_">ProgressState</span> {<br>    <span class="hljs-comment">/// 总进度百分比</span><br>    <span class="hljs-keyword">pub</span> overall_percent: <span class="hljs-type">f64</span>,<br><br>    <span class="hljs-comment">/// 阶段内进度</span><br>    <span class="hljs-keyword">pub</span> stage_progress: <span class="hljs-type">u64</span>,<br><br>    <span class="hljs-comment">/// 阶段总字节数</span><br>    <span class="hljs-keyword">pub</span> stage_total: <span class="hljs-type">u64</span>,<br><br>    <span class="hljs-comment">/// 传输速度</span><br>    <span class="hljs-keyword">pub</span> speed: <span class="hljs-type">f64</span>,<br><br>    <span class="hljs-comment">/// 当前分区名称</span><br>    <span class="hljs-keyword">pub</span> current_partition: <span class="hljs-type">String</span>,<br><br>    <span class="hljs-comment">/// 当前阶段</span><br>    <span class="hljs-keyword">pub</span> current_stage: <span class="hljs-type">Option</span>&lt;StageType&gt;,<br><br>    <span class="hljs-comment">/// 已完成阶段列表</span><br>    <span class="hljs-keyword">pub</span> completed_stages: <span class="hljs-type">Vec</span>&lt;StageType&gt;,<br>}<br></code></pre></td></tr></tbody></table></figure><h3 id="LogState" data-id="LogState" class="notion-h"><a href="#LogState" class="headerlink" title="LogState"></a>LogState</h3><figure class="highlight rust"><table><tbody><tr><td class="code"><pre><code class="hljs rust"><span class="hljs-comment">/// 日志状态</span><br><span class="hljs-keyword">pub</span> <span class="hljs-keyword">struct</span> <span class="hljs-title class_">LogState</span> {<br>    <span class="hljs-comment">/// 日志条目列表</span><br>    <span class="hljs-keyword">pub</span> entries: <span class="hljs-type">Vec</span>&lt;LogEntry&gt;,<br><br>    <span class="hljs-comment">/// 滚动偏移</span><br>    <span class="hljs-keyword">pub</span> scroll_offset: <span class="hljs-type">usize</span>,<br>}<br><br><span class="hljs-comment">/// 日志条目</span><br><span class="hljs-keyword">pub</span> <span class="hljs-keyword">struct</span> <span class="hljs-title class_">LogEntry</span> {<br>    <span class="hljs-comment">/// 时间戳</span><br>    <span class="hljs-keyword">pub</span> timestamp: Instant,<br><br>    <span class="hljs-comment">/// 级别</span><br>    <span class="hljs-keyword">pub</span> level: LogLevel,<br><br>    <span class="hljs-comment">/// 消息内容</span><br>    <span class="hljs-keyword">pub</span> message: <span class="hljs-type">String</span>,<br>}<br></code></pre></td></tr></tbody></table></figure>]]>
    </content>
    <id>https://gloomyghost.com/posts/openixcli-tui-module/</id>
    <link href="https://gloomyghost.com/posts/openixcli-tui-module/"/>
    <published>2026-04-25T16:00:00.000Z</published>
    <summary>
      <![CDATA[<h2 id="概述" data-id="概述" class="notion-h"><a href="#概述" class="headerlink" title="概述"></a>概述</h2><p>终端用户界面（TUI）为固件刷写工具提供了直观的交互体验。OpenixCLI 使]]>
    </summary>
    <title>OpenixCLI TUI 模块深度解析：事件驱动终端界面</title>
    <updated>2026-05-29T20:33:31.219Z</updated>
  </entry>
  <entry>
    <author>
      <name>柚木 鉉</name>
    </author>
    <category term="Rust" scheme="https://gloomyghost.com/tags/Rust/"/>
    <category term="嵌入式" scheme="https://gloomyghost.com/tags/%E5%B5%8C%E5%85%A5%E5%BC%8F/"/>
    <category term="OpenixCLI" scheme="https://gloomyghost.com/tags/OpenixCLI/"/>
    <category term="错误处理" scheme="https://gloomyghost.com/tags/%E9%94%99%E8%AF%AF%E5%A4%84%E7%90%86/"/>
    <category term="日志系统" scheme="https://gloomyghost.com/tags/%E6%97%A5%E5%BF%97%E7%B3%BB%E7%BB%9F/"/>
    <content>
      <![CDATA[<h2 id="概述" data-id="概述" class="notion-h"><a href="#概述" class="headerlink" title="概述"></a>概述</h2><p>在嵌入式开发中，一个稳固的固件刷写工具必须具备完善的错误处理和日志系统。OpenixCLI 的 Utils 模块（<code>src/utils/</code>）正是为此而生，它提供了统一的错误类型定义和双模式日志架构，支持 CLI 和 TUI 两种交互模式。</p><h3 id="模块结构" data-id="模块结构" class="notion-h"><a href="#模块结构" class="headerlink" title="模块结构"></a>模块结构</h3><figure class="highlight plaintext"><table><tbody><tr><td class="code"><pre><code class="hljs plaintext">src/utils/<br>├── mod.rs       # 模块导出<br>├── error.rs     # FlashError 错误枚举<br>├── logger.rs    # Logger 进度报告封装<br>└── terminal.rs  # TermLogger 终端输出实现<br></code></pre></td></tr></tbody></table></figure><h3 id="核心导出" data-id="核心导出" class="notion-h"><a href="#核心导出" class="headerlink" title="核心导出"></a>核心导出</h3><figure class="highlight rust"><table><tbody><tr><td class="code"><pre><code class="hljs rust"><span class="hljs-keyword">pub</span> <span class="hljs-keyword">use</span> error::{FlashError, FlashResult};<br><span class="hljs-keyword">pub</span> <span class="hljs-keyword">use</span> logger::Logger;<br><span class="hljs-keyword">pub</span> <span class="hljs-keyword">use</span> terminal::TermLogger;<br></code></pre></td></tr></tbody></table></figure><hr><h2 id="架构与依赖" data-id="架构与依赖" class="notion-h"><a href="#架构与依赖" class="headerlink" title="架构与依赖"></a>架构与依赖</h2><h3 id="模块依赖关系" data-id="模块依赖关系" class="notion-h"><a href="#模块依赖关系" class="headerlink" title="模块依赖关系"></a>模块依赖关系</h3><figure class="highlight plaintext"><table><tbody><tr><td class="code"><pre><code class="hljs plaintext">utils 模块<br>├── thiserror (外部 crate) - 错误类型 derive<br>├── colored (外部 crate) - 终端彩色输出<br>├── indicatif (外部 crate) - 进度条<br>├── log (外部 crate) - 日志 facade<br>├── once_cell (外部 crate) - 全局状态<br>├── tokio::sync::mpsc (外部 crate) - TUI 日志通道<br>└── process 模块 - ProgressReporter 依赖<br></code></pre></td></tr></tbody></table></figure><h3 id="双模式日志架构" data-id="双模式日志架构" class="notion-h"><a href="#双模式日志架构" class="headerlink" title="双模式日志架构"></a>双模式日志架构</h3><p>OpenixCLI 支持两种运行模式：</p><table><thead><tr><th>模式</th><th>输出目标</th><th>进度显示</th><th>适用场景</th></tr></thead><tbody><tr><td>CLI</td><td>stdout/stderr</td><td>indicatif 进度条</td><td>命令行批量刷写</td></tr><tr><td>TUI</td><td>mpsc channel</td><td>ratatui 界面</td><td>交互式操作</td></tr></tbody></table><hr><h2 id="核心数据结构" data-id="核心数据结构" class="notion-h"><a href="#核心数据结构" class="headerlink" title="核心数据结构"></a>核心数据结构</h2><h3 id="FlashError-错误枚举" data-id="FlashError-错误枚举" class="notion-h"><a href="#FlashError-错误枚举" class="headerlink" title="FlashError 错误枚举"></a>FlashError 错误枚举</h3><p>FlashError 使用 <code>thiserror</code> crate 的 derive 宏定义，涵盖了固件刷写过程中可能出现的所有错误场景：</p><figure class="highlight rust"><table><tbody><tr><td class="code"><pre><code class="hljs rust"><span class="hljs-comment">/// Flash 操作错误类型</span><br><span class="hljs-comment">///</span><br><span class="hljs-comment">/// 使用 thiserror derive 自动实现 Error trait 和 Display trait</span><br><span class="hljs-meta">#[derive(Debug, Error)]</span><br><span class="hljs-keyword">pub</span> <span class="hljs-keyword">enum</span> <span class="hljs-title class_">FlashError</span> {<br>    <span class="hljs-comment">// 固件相关错误</span><br>    <span class="hljs-meta">#[error(<span class="hljs-string">"Firmware file not found: {0}"</span>)]</span><br>    <span class="hljs-title function_ invoke__">FirmwareNotFound</span>(<span class="hljs-type">String</span>),<br><br>    <span class="hljs-meta">#[error(<span class="hljs-string">"Invalid firmware format: {0}"</span>)]</span><br>    <span class="hljs-title function_ invoke__">InvalidFirmwareFormat</span>(<span class="hljs-type">String</span>),<br><br>    <span class="hljs-meta">#[error(<span class="hljs-string">"Encrypted firmware not supported"</span>)]</span><br>    EncryptedNotSupported,<br><br>    <span class="hljs-comment">// 设备相关错误</span><br>    <span class="hljs-meta">#[error(<span class="hljs-string">"Device not found"</span>)]</span><br>    DeviceNotFound,<br><br>    <span class="hljs-meta">#[error(<span class="hljs-string">"Failed to open device: {0}"</span>)]</span><br>    <span class="hljs-title function_ invoke__">DeviceOpenFailed</span>(<span class="hljs-type">String</span>),<br><br>    <span class="hljs-comment">// 刷写过程错误</span><br>    <span class="hljs-meta">#[error(<span class="hljs-string">"DRAM initialization failed"</span>)]</span><br>    DramInitFailed,<br><br>    <span class="hljs-meta">#[error(<span class="hljs-string">"U-Boot download failed"</span>)]</span><br>    UbootDownloadFailed,<br><br>    <span class="hljs-meta">#[error(<span class="hljs-string">"MBR download failed"</span>)]</span><br>    MbrDownloadFailed,<br><br>    <span class="hljs-meta">#[error(<span class="hljs-string">"Partition download failed: {0}"</span>)]</span><br>    <span class="hljs-title function_ invoke__">PartitionDownloadFailed</span>(<span class="hljs-type">String</span>),<br><br>    <span class="hljs-meta">#[error(<span class="hljs-string">"Device reconnect failed"</span>)]</span><br>    ReconnectFailed,<br><br>    <span class="hljs-meta">#[error(<span class="hljs-string">"Storage type mismatch: device={device}, firmware={firmware}"</span>)]</span><br>    StorageTypeMismatch { device: <span class="hljs-type">String</span>, firmware: <span class="hljs-type">String</span> },<br><br>    <span class="hljs-comment">// 固件组件缺失错误</span><br>    <span class="hljs-meta">#[error(<span class="hljs-string">"FES not found in firmware"</span>)]</span><br>    FesNotFound,<br><br>    <span class="hljs-meta">#[error(<span class="hljs-string">"U-Boot not found in firmware"</span>)]</span><br>    UbootNotFound,<br><br>    <span class="hljs-meta">#[error(<span class="hljs-string">"SysConfig not found in firmware"</span>)]</span><br>    SysConfigNotFound,<br><br>    <span class="hljs-meta">#[error(<span class="hljs-string">"MBR not found in firmware"</span>)]</span><br>    MbrNotFound,<br><br>    <span class="hljs-meta">#[error(<span class="hljs-string">"Boot0 not found in firmware"</span>)]</span><br>    Boot0NotFound,<br><br>    <span class="hljs-meta">#[error(<span class="hljs-string">"Boot1 not found in firmware"</span>)]</span><br>    Boot1NotFound,<br><br>    <span class="hljs-comment">// USB 和操作错误</span><br>    <span class="hljs-meta">#[error(<span class="hljs-string">"USB transfer error: {0}"</span>)]</span><br>    <span class="hljs-title function_ invoke__">UsbTransferError</span>(<span class="hljs-type">String</span>),<br><br>    <span class="hljs-meta">#[error(<span class="hljs-string">"Operation cancelled"</span>)]</span><br>    Cancelled,<br><br>    <span class="hljs-meta">#[error(<span class="hljs-string">"Timeout: {0}"</span>)]</span><br>    <span class="hljs-title function_ invoke__">Timeout</span>(<span class="hljs-type">String</span>),<br><br>    <span class="hljs-comment">// 外部错误转换</span><br>    <span class="hljs-meta">#[error(<span class="hljs-string">"IO error: {0}"</span>)]</span><br>    <span class="hljs-title function_ invoke__">Io</span>(<span class="hljs-meta">#[from]</span> std::io::Error),<br><br>    <span class="hljs-meta">#[error(<span class="hljs-string">"Packer error: {0}"</span>)]</span><br>    <span class="hljs-title function_ invoke__">Packer</span>(<span class="hljs-meta">#[from]</span> crate::firmware::PackerError),<br><br>    <span class="hljs-meta">#[error(<span class="hljs-string">"Libefex error: {0}"</span>)]</span><br>    <span class="hljs-title function_ invoke__">Libefex</span>(<span class="hljs-meta">#[from]</span> libefex::EfexError),<br><br>    <span class="hljs-meta">#[error(<span class="hljs-string">"Unknown error: {0}"</span>)]</span><br>    <span class="hljs-title function_ invoke__">Unknown</span>(<span class="hljs-type">String</span>),<br>}<br></code></pre></td></tr></tbody></table></figure><p><strong>关键特性分析：</strong></p><ol><li><strong><code>#[derive(Debug, Error)]</code></strong> - 自动实现 <code>std::error::Error</code> trait 和 <code>Debug</code> trait</li><li><strong><code>#[error("...")]</code></strong> - 自定义错误消息格式，支持字段插值</li><li><strong><code>#[from]</code></strong> - 自动实现 <code>From</code> trait，实现错误类型转换</li></ol><h3 id="FlashResult-类型别名" data-id="FlashResult-类型别名" class="notion-h"><a href="#FlashResult-类型别名" class="headerlink" title="FlashResult 类型别名"></a>FlashResult 类型别名</h3><figure class="highlight rust"><table><tbody><tr><td class="code"><pre><code class="hljs rust"><span class="hljs-comment">/// Flash 操作的 Result 类型别名</span><br><span class="hljs-keyword">pub</span> <span class="hljs-keyword">type</span> <span class="hljs-title class_">FlashResult</span>&lt;T&gt; = <span class="hljs-type">Result</span>&lt;T, FlashError&gt;;<br></code></pre></td></tr></tbody></table></figure><p>使用类型别名简化函数签名，避免重复书写完整的 Result 类型。</p><h3 id="Logger-结构" data-id="Logger-结构" class="notion-h"><a href="#Logger-结构" class="headerlink" title="Logger 结构"></a>Logger 结构</h3><p>Logger 是进度报告器的封装，提供统一的日志和进度接口：</p><figure class="highlight rust"><table><tbody><tr><td class="code"><pre><code class="hljs rust"><span class="hljs-comment">/// Logger - 统一的日志和进度报告接口</span><br><span class="hljs-meta">#[derive(Clone)]</span><br><span class="hljs-keyword">pub</span> <span class="hljs-keyword">struct</span> <span class="hljs-title class_">Logger</span> {<br>    verbose: <span class="hljs-type">bool</span>,<br>    reporter: Arc&lt;ProgressReporter&gt;,<br>}<br><br><span class="hljs-keyword">impl</span> <span class="hljs-title class_">Logger</span> {<br>    <span class="hljs-comment">/// 创建默认配置的 Logger</span><br>    <span class="hljs-keyword">pub</span> <span class="hljs-keyword">fn</span> <span class="hljs-title function_">new</span>() <span class="hljs-punctuation">-&gt;</span> <span class="hljs-keyword">Self</span> {<br>        <span class="hljs-keyword">Self</span> {<br>            verbose: <span class="hljs-literal">false</span>,<br>            reporter: Arc::<span class="hljs-title function_ invoke__">new</span>(ProgressReporter::<span class="hljs-title function_ invoke__">new</span>()),<br>        }<br>    }<br><br>    <span class="hljs-comment">/// 创建带 verbose 模式的 Logger</span><br>    <span class="hljs-keyword">pub</span> <span class="hljs-keyword">fn</span> <span class="hljs-title function_">with_verbose</span>(verbose: <span class="hljs-type">bool</span>) <span class="hljs-punctuation">-&gt;</span> <span class="hljs-keyword">Self</span> {<br>        <span class="hljs-keyword">Self</span> {<br>            verbose,<br>            reporter: Arc::<span class="hljs-title function_ invoke__">new</span>(ProgressReporter::<span class="hljs-title function_ invoke__">new</span>()),<br>        }<br>    }<br>}<br></code></pre></td></tr></tbody></table></figure><p><strong>设计要点：</strong></p><ul><li>使用 <code>Arc</code> 包装 <code>ProgressReporter</code>，支持跨线程共享</li><li><code>Clone</code> trait 实现，允许多个组件持有同一个 Logger 实例</li></ul><h3 id="TuiLogMessage-与日志级别" data-id="TuiLogMessage-与日志级别" class="notion-h"><a href="#TuiLogMessage-与日志级别" class="headerlink" title="TuiLogMessage 与日志级别"></a>TuiLogMessage 与日志级别</h3><figure class="highlight rust"><table><tbody><tr><td class="code"><pre><code class="hljs rust"><span class="hljs-comment">/// TUI 日志消息结构</span><br><span class="hljs-keyword">pub</span> <span class="hljs-keyword">struct</span> <span class="hljs-title class_">TuiLogMessage</span> {<br>    <span class="hljs-keyword">pub</span> level: TuiLogLevel,<br>    <span class="hljs-keyword">pub</span> message: <span class="hljs-type">String</span>,<br>}<br><br><span class="hljs-comment">/// TUI 日志级别枚举</span><br><span class="hljs-meta">#[derive(Debug, Clone, PartialEq, Eq)]</span><br><span class="hljs-keyword">pub</span> <span class="hljs-keyword">enum</span> <span class="hljs-title class_">TuiLogLevel</span> {<br>    Info,<br>    Success,<br>    Warn,<br>    Error,<br>    <span class="hljs-built_in">Debug</span>,<br>}<br></code></pre></td></tr></tbody></table></figure><h3 id="TermLogger-结构" data-id="TermLogger-结构" class="notion-h"><a href="#TermLogger-结构" class="headerlink" title="TermLogger 结构"></a>TermLogger 结构</h3><p>TermLogger 实现 <code>log::Log</code> trait，是全局日志 facade 的后端：</p><figure class="highlight rust"><table><tbody><tr><td class="code"><pre><code class="hljs rust"><span class="hljs-comment">/// Terminal Logger - 实现 log crate 的 Log trait</span><br><span class="hljs-keyword">pub</span> <span class="hljs-keyword">struct</span> <span class="hljs-title class_">TermLogger</span> {<br>    verbose: <span class="hljs-type">bool</span>,<br>}<br><br><span class="hljs-keyword">impl</span> <span class="hljs-title class_">TermLogger</span> {<br>    <span class="hljs-comment">/// 初始化终端日志器</span><br>    <span class="hljs-keyword">pub</span> <span class="hljs-keyword">fn</span> <span class="hljs-title function_">init</span>(verbose: <span class="hljs-type">bool</span>) <span class="hljs-punctuation">-&gt;</span> <span class="hljs-type">Result</span>&lt;(), log::SetLoggerError&gt; {<br>        <span class="hljs-title function_ invoke__">set_verbose</span>(verbose);<br>        <span class="hljs-keyword">let</span> <span class="hljs-variable">logger</span> = <span class="hljs-type">Box</span>::<span class="hljs-title function_ invoke__">new</span>(<span class="hljs-keyword">Self</span>::<span class="hljs-title function_ invoke__">new</span>(verbose));<br>        <span class="hljs-keyword">let</span> <span class="hljs-variable">level</span> = <span class="hljs-keyword">if</span> verbose {<br>            LevelFilter::<span class="hljs-built_in">Debug</span><br>        } <span class="hljs-keyword">else</span> {<br>            LevelFilter::Info<br>        };<br>        log::<span class="hljs-title function_ invoke__">set_boxed_logger</span>(logger)?;<br>        log::<span class="hljs-title function_ invoke__">set_max_level</span>(level);<br>        <span class="hljs-title function_ invoke__">Ok</span>(())<br>    }<br>}<br></code></pre></td></tr></tbody></table></figure><hr><h2 id="调用流程分析" data-id="调用流程分析" class="notion-h"><a href="#调用流程分析" class="headerlink" title="调用流程分析"></a>调用流程分析</h2><h3 id="全局日志初始化流程" data-id="全局日志初始化流程" class="notion-h"><a href="#全局日志初始化流程" class="headerlink" title="全局日志初始化流程"></a>全局日志初始化流程</h3><pre class="mermaid">sequenceDiagram    participant main as main.rs    participant TermLogger as TermLogger    participant log_crate as log crate    participant TUI_SENDER as TUI_LOG_SENDER    main-&gt;&gt;TermLogger: init(verbose)    TermLogger-&gt;&gt;TermLogger: set_verbose(verbose)    TermLogger-&gt;&gt;log_crate: set_boxed_logger(logger)    TermLogger-&gt;&gt;log_crate: set_max_level(level)    log_crate--&gt;&gt;main: Ok()</pre><p><strong>代码实现：</strong></p><figure class="highlight rust"><table><tbody><tr><td class="code"><pre><code class="hljs rust"><span class="hljs-comment">// 在 main.rs 中初始化</span><br><span class="hljs-keyword">fn</span> <span class="hljs-title function_">main</span>() <span class="hljs-punctuation">-&gt;</span> <span class="hljs-type">Result</span>&lt;()&gt; {<br>    <span class="hljs-comment">// 初始化日志系统</span><br>    TermLogger::<span class="hljs-title function_ invoke__">init</span>(cli.verbose)?;<br><br>    <span class="hljs-comment">// 后续所有 log::info!/log::error! 调用都会通过 TermLogger 处理</span><br>    log::info!(<span class="hljs-string">"OpenixCLI starting..."</span>);<br>}<br></code></pre></td></tr></tbody></table></figure><h3 id="Logger-使用流程" data-id="Logger-使用流程" class="notion-h"><a href="#Logger-使用流程" class="headerlink" title="Logger 使用流程"></a>Logger 使用流程</h3><figure class="highlight rust"><table><tbody><tr><td class="code"><pre><code class="hljs rust"><span class="hljs-comment">// 在 flash 模块中的使用示例</span><br><span class="hljs-keyword">impl</span> <span class="hljs-title class_">Flasher</span> {<br>    <span class="hljs-keyword">pub</span> <span class="hljs-keyword">async</span> <span class="hljs-keyword">fn</span> <span class="hljs-title function_">execute</span>(&amp;<span class="hljs-keyword">self</span>) <span class="hljs-punctuation">-&gt;</span> FlashResult&lt;()&gt; {<br>        <span class="hljs-keyword">let</span> <span class="hljs-variable">logger</span> = Logger::<span class="hljs-title function_ invoke__">with_verbose</span>(<span class="hljs-keyword">self</span>.options.verbose);<br><br>        <span class="hljs-comment">// 定义刷写阶段</span><br>        <span class="hljs-keyword">let</span> <span class="hljs-variable">stages</span> = FlashStages::for_fel_mode();<br>        logger.<span class="hljs-title function_ invoke__">define_stages</span>(&amp;stages);<br>        logger.<span class="hljs-title function_ invoke__">start_global_progress</span>();<br><br>        <span class="hljs-comment">// DRAM 初始化阶段</span><br>        logger.<span class="hljs-title function_ invoke__">begin_stage</span>(StageType::FelDram);<br>        logger.<span class="hljs-title function_ invoke__">info</span>(<span class="hljs-string">"Initializing DRAM..."</span>);<br><br>        <span class="hljs-comment">// ... 执行 DRAM 初始化 ...</span><br><br>        logger.<span class="hljs-title function_ invoke__">stage_complete</span>(<span class="hljs-string">"DRAM initialized"</span>);<br><br>        <span class="hljs-comment">// 完成当前阶段</span><br>        logger.<span class="hljs-title function_ invoke__">complete_stage</span>();<br><br>        <span class="hljs-comment">// U-Boot 下载阶段</span><br>        logger.<span class="hljs-title function_ invoke__">begin_stage</span>(StageType::FelUboot);<br>        logger.<span class="hljs-title function_ invoke__">set_current_partition</span>(<span class="hljs-string">"uboot"</span>);<br><br>        <span class="hljs-comment">// 更新进度</span><br>        logger.<span class="hljs-title function_ invoke__">update_progress_with_speed</span>(bytes_written);<br><br>        logger.<span class="hljs-title function_ invoke__">complete_stage</span>();<br><br>        <span class="hljs-comment">// 最终完成</span><br>        logger.<span class="hljs-title function_ invoke__">finish_progress</span>();<br>        <span class="hljs-title function_ invoke__">Ok</span>(())<br>    }<br>}<br></code></pre></td></tr></tbody></table></figure><h3 id="双模式日志切换" data-id="双模式日志切换" class="notion-h"><a href="#双模式日志切换" class="headerlink" title="双模式日志切换"></a>双模式日志切换</h3><p>关键机制是 <code>TUI_LOG_SENDER</code> 全局状态：</p><figure class="highlight rust"><table><tbody><tr><td class="code"><pre><code class="hljs rust"><span class="hljs-comment">/// 全局 TUI 日志发送器 - 使用 once_cell::Lazy 实现延迟初始化</span><br><span class="hljs-keyword">static</span> TUI_LOG_SENDER: Lazy&lt;Mutex&lt;<span class="hljs-type">Option</span>&lt;mpsc::UnboundedSender&lt;TuiLogMessage&gt;&gt;&gt;&gt; =<br>    Lazy::<span class="hljs-title function_ invoke__">new</span>(|| Mutex::<span class="hljs-title function_ invoke__">new</span>(<span class="hljs-literal">None</span>));<br><br><span class="hljs-comment">/// 设置 TUI 日志通道</span><br><span class="hljs-keyword">pub</span> <span class="hljs-keyword">fn</span> <span class="hljs-title function_">set_tui_log_sender</span>(tx: <span class="hljs-type">Option</span>&lt;mpsc::UnboundedSender&lt;TuiLogMessage&gt;&gt;) {<br>    <span class="hljs-keyword">let</span> <span class="hljs-keyword">mut </span><span class="hljs-variable">sender</span> = TUI_LOG_SENDER.<span class="hljs-title function_ invoke__">lock</span>().<span class="hljs-title function_ invoke__">unwrap</span>();<br>    *sender = tx;<br>}<br></code></pre></td></tr></tbody></table></figure><p><strong>日志输出流程：</strong></p><figure class="highlight rust"><table><tbody><tr><td class="code"><pre><code class="hljs rust"><span class="hljs-keyword">fn</span> <span class="hljs-title function_">log_info</span>(message: &amp;<span class="hljs-type">str</span>) {<br>    <span class="hljs-comment">// 优先尝试发送到 TUI channel</span><br>    <span class="hljs-keyword">if</span> <span class="hljs-title function_ invoke__">send_to_tui</span>(TuiLogLevel::Info, message) {<br>        <span class="hljs-keyword">return</span>;  <span class="hljs-comment">// TUI 模式，已发送，直接返回</span><br>    }<br><br>    <span class="hljs-comment">// CLI 模式，输出到 stdout</span><br>    MULTI_PROGRESS.<span class="hljs-title function_ invoke__">suspend</span>(|| {<br>        <span class="hljs-built_in">println!</span>(<span class="hljs-string">"[{}] {}"</span>, <span class="hljs-string">"INFO"</span>.<span class="hljs-title function_ invoke__">cyan</span>().<span class="hljs-title function_ invoke__">bold</span>(), message);<br>    });<br>}<br><br><span class="hljs-keyword">fn</span> <span class="hljs-title function_">send_to_tui</span>(level: TuiLogLevel, message: &amp;<span class="hljs-type">str</span>) <span class="hljs-punctuation">-&gt;</span> <span class="hljs-type">bool</span> {<br>    <span class="hljs-keyword">let</span> <span class="hljs-variable">sender</span> = TUI_LOG_SENDER.<span class="hljs-title function_ invoke__">lock</span>().<span class="hljs-title function_ invoke__">unwrap</span>();<br>    <span class="hljs-keyword">if</span> <span class="hljs-keyword">let</span> <span class="hljs-variable">Some</span>(<span class="hljs-keyword">ref</span> tx) = *sender {<br>        <span class="hljs-keyword">let</span> <span class="hljs-variable">_</span> = tx.<span class="hljs-title function_ invoke__">send</span>(TuiLogMessage {<br>            level,<br>            message: message.<span class="hljs-title function_ invoke__">to_string</span>(),<br>        });<br>        <span class="hljs-literal">true</span>  <span class="hljs-comment">// 发送成功，返回 true</span><br>    } <span class="hljs-keyword">else</span> {<br>        <span class="hljs-literal">false</span>  <span class="hljs-comment">// 无 TUI channel，返回 false</span><br>    }<br>}<br></code></pre></td></tr></tbody></table></figure><hr><h2 id="代码亮点" data-id="代码亮点" class="notion-h"><a href="#代码亮点" class="headerlink" title="代码亮点"></a>代码亮点</h2><h3 id="1-thiserror-的错误类型定义" data-id="1-thiserror-的错误类型定义" class="notion-h"><a href="#1-thiserror-的错误类型定义" class="headerlink" title="1. thiserror 的错误类型定义"></a>1. thiserror 的错误类型定义</h3><p>使用 <code>thiserror</code> crate 大幅简化错误类型定义：</p><figure class="highlight rust"><table><tbody><tr><td class="code"><pre><code class="hljs rust"><span class="hljs-comment">// 传统方式需要手动实现 Error 和 Display</span><br><span class="hljs-comment">// 使用 thiserror 只需：</span><br><span class="hljs-meta">#[derive(Debug, Error)]</span><br><span class="hljs-keyword">pub</span> <span class="hljs-keyword">enum</span> <span class="hljs-title class_">FlashError</span> {<br>    <span class="hljs-meta">#[error(<span class="hljs-string">"Firmware file not found: {0}"</span>)]</span><br>    <span class="hljs-title function_ invoke__">FirmwareNotFound</span>(<span class="hljs-type">String</span>),<br>}<br><br><span class="hljs-comment">// thiserror 自动生成：</span><br><span class="hljs-keyword">impl</span> <span class="hljs-title class_">std</span>::fmt::Display <span class="hljs-keyword">for</span> <span class="hljs-title class_">FlashError</span> {<br>    <span class="hljs-keyword">fn</span> <span class="hljs-title function_">fmt</span>(&amp;<span class="hljs-keyword">self</span>, f: &amp;<span class="hljs-keyword">mut</span> std::fmt::Formatter) <span class="hljs-punctuation">-&gt;</span> std::fmt::<span class="hljs-type">Result</span> {<br>        <span class="hljs-keyword">match</span> <span class="hljs-keyword">self</span> {<br>            FlashError::<span class="hljs-title function_ invoke__">FirmwareNotFound</span>(s) =&gt; <span class="hljs-built_in">write!</span>(f, <span class="hljs-string">"Firmware file not found: {}"</span>, s),<br>        }<br>    }<br>}<br><br><span class="hljs-keyword">impl</span> <span class="hljs-title class_">std</span>::error::Error <span class="hljs-keyword">for</span> <span class="hljs-title class_">FlashError</span> {}<br></code></pre></td></tr></tbody></table></figure><h3 id="2-from-属性实现错误转换" data-id="2-from-属性实现错误转换" class="notion-h"><a href="#2-from-属性实现错误转换" class="headerlink" title="2. #[from] 属性实现错误转换"></a>2. <code>#[from]</code> 属性实现错误转换</h3><figure class="highlight rust"><table><tbody><tr><td class="code"><pre><code class="hljs rust"><span class="hljs-meta">#[error(<span class="hljs-string">"IO error: {0}"</span>)]</span><br><span class="hljs-title function_ invoke__">Io</span>(<span class="hljs-meta">#[from]</span> std::io::Error),<br></code></pre></td></tr></tbody></table></figure><p>这会自动生成 <code>From&lt;std::io::Error&gt; for FlashError</code> 实现，允许使用 <code>?</code> 操作符自动转换：</p><figure class="highlight rust"><table><tbody><tr><td class="code"><pre><code class="hljs rust"><span class="hljs-keyword">fn</span> <span class="hljs-title function_">load_firmware</span>(path: &amp;Path) <span class="hljs-punctuation">-&gt;</span> FlashResult&lt;<span class="hljs-type">Vec</span>&lt;<span class="hljs-type">u8</span>&gt;&gt; {<br>    <span class="hljs-keyword">let</span> <span class="hljs-variable">data</span> = std::fs::<span class="hljs-title function_ invoke__">read</span>(path)?;  <span class="hljs-comment">// io::Error 自动转换为 FlashError::Io</span><br>    <span class="hljs-title function_ invoke__">Ok</span>(data)<br>}<br></code></pre></td></tr></tbody></table></figure><h3 id="3-结构化错误字段" data-id="3-结构化错误字段" class="notion-h"><a href="#3-结构化错误字段" class="headerlink" title="3. 结构化错误字段"></a>3. 结构化错误字段</h3><figure class="highlight rust"><table><tbody><tr><td class="code"><pre><code class="hljs rust"><span class="hljs-meta">#[error(<span class="hljs-string">"Storage type mismatch: device={device}, firmware={firmware}"</span>)]</span><br>StorageTypeMismatch { device: <span class="hljs-type">String</span>, firmware: <span class="hljs-type">String</span> },<br></code></pre></td></tr></tbody></table></figure><p>支持命名字段，错误消息更清晰：</p><figure class="highlight rust"><table><tbody><tr><td class="code"><pre><code class="hljs rust"><span class="hljs-keyword">return</span> <span class="hljs-title function_ invoke__">Err</span>(FlashError::StorageTypeMismatch {<br>    device: <span class="hljs-string">"eMMC"</span>.<span class="hljs-title function_ invoke__">to_string</span>(),<br>    firmware: <span class="hljs-string">"NAND"</span>.<span class="hljs-title function_ invoke__">to_string</span>(),<br>});<br><span class="hljs-comment">// 输出: Storage type mismatch: device=eMMC, firmware=NAND</span><br></code></pre></td></tr></tbody></table></figure><h3 id="4-once-cell-Lazy-全局状态" data-id="4-once-cell-Lazy-全局状态" class="notion-h"><a href="#4-once-cell-Lazy-全局状态" class="headerlink" title="4. once_cell::Lazy 全局状态"></a>4. once_cell::Lazy 全局状态</h3><figure class="highlight rust"><table><tbody><tr><td class="code"><pre><code class="hljs rust"><span class="hljs-keyword">static</span> TUI_LOG_SENDER: Lazy&lt;Mutex&lt;<span class="hljs-type">Option</span>&lt;mpsc::UnboundedSender&lt;TuiLogMessage&gt;&gt;&gt;&gt; =<br>    Lazy::<span class="hljs-title function_ invoke__">new</span>(|| Mutex::<span class="hljs-title function_ invoke__">new</span>(<span class="hljs-literal">None</span>));<br></code></pre></td></tr></tbody></table></figure><p><strong>优势：</strong></p><ul><li>延迟初始化，首次访问时才创建</li><li>线程安全，无需手动同步</li><li>比 <code>lazy_static!</code> 宏更现代化</li></ul><h3 id="5-indicatif-MultiProgress-悬挂机制" data-id="5-indicatif-MultiProgress-悬挂机制" class="notion-h"><a href="#5-indicatif-MultiProgress-悬挂机制" class="headerlink" title="5. indicatif MultiProgress 悬挂机制"></a>5. indicatif MultiProgress 悬挂机制</h3><figure class="highlight rust"><table><tbody><tr><td class="code"><pre><code class="hljs rust">MULTI_PROGRESS.<span class="hljs-title function_ invoke__">suspend</span>(|| {<br>    <span class="hljs-built_in">println!</span>(<span class="hljs-string">"[{}] {}"</span>, <span class="hljs-string">"INFO"</span>.<span class="hljs-title function_ invoke__">cyan</span>().<span class="hljs-title function_ invoke__">bold</span>(), message);<br>});<br></code></pre></td></tr></tbody></table></figure><p><code>suspend</code> 方法临时隐藏进度条，避免日志输出与进度条重叠。</p><h3 id="6-log-crate-facade-模式" data-id="6-log-crate-facade-模式" class="notion-h"><a href="#6-log-crate-facade-模式" class="headerlink" title="6. log crate facade 模式"></a>6. log crate facade 模式</h3><p>TermLogger 实现 <code>log::Log</code> trait：</p><figure class="highlight rust"><table><tbody><tr><td class="code"><pre><code class="hljs rust"><span class="hljs-keyword">impl</span> <span class="hljs-title class_">Log</span> <span class="hljs-keyword">for</span> <span class="hljs-title class_">TermLogger</span> {<br>    <span class="hljs-keyword">fn</span> <span class="hljs-title function_">enabled</span>(&amp;<span class="hljs-keyword">self</span>, metadata: &amp;Metadata) <span class="hljs-punctuation">-&gt;</span> <span class="hljs-type">bool</span> {<br>        <span class="hljs-comment">// 只处理 openixcli 和 libefex 的日志</span><br>        <span class="hljs-keyword">let</span> <span class="hljs-variable">target</span> = metadata.<span class="hljs-title function_ invoke__">target</span>();<br>        <span class="hljs-keyword">if</span> target.<span class="hljs-title function_ invoke__">starts_with</span>(<span class="hljs-string">"openixcli"</span>) || target.<span class="hljs-title function_ invoke__">starts_with</span>(<span class="hljs-string">"libefex"</span>) {<br>            <span class="hljs-keyword">return</span> <span class="hljs-literal">true</span>;<br>        }<br>        metadata.<span class="hljs-title function_ invoke__">level</span>() &lt;= Level::Info<br>    }<br><br>    <span class="hljs-keyword">fn</span> <span class="hljs-title function_">log</span>(&amp;<span class="hljs-keyword">self</span>, record: &amp;Record) {<br>        <span class="hljs-keyword">if</span> !<span class="hljs-keyword">self</span>.<span class="hljs-title function_ invoke__">enabled</span>(record.<span class="hljs-title function_ invoke__">metadata</span>()) {<br>            <span class="hljs-keyword">return</span>;<br>        }<br><br>        <span class="hljs-comment">// verbose 模式下过滤 Debug 级别</span><br>        <span class="hljs-keyword">if</span> record.<span class="hljs-title function_ invoke__">level</span>() == Level::<span class="hljs-built_in">Debug</span> &amp;&amp; !<span class="hljs-keyword">self</span>.verbose {<br>            <span class="hljs-keyword">return</span>;<br>        }<br><br>        <span class="hljs-comment">// 处理日志输出...</span><br>    }<br><br>    <span class="hljs-keyword">fn</span> <span class="hljs-title function_">flush</span>(&amp;<span class="hljs-keyword">self</span>) {}<br>}<br></code></pre></td></tr></tbody></table></figure><hr><h2 id="实践示例" data-id="实践示例" class="notion-h"><a href="#实践示例" class="headerlink" title="实践示例"></a>实践示例</h2><h3 id="CLI-模式下的日志使用" data-id="CLI-模式下的日志使用" class="notion-h"><a href="#CLI-模式下的日志使用" class="headerlink" title="CLI 模式下的日志使用"></a>CLI 模式下的日志使用</h3><figure class="highlight rust"><table><tbody><tr><td class="code"><pre><code class="hljs rust"><span class="hljs-keyword">use</span> openixcli::utils::{FlashError, FlashResult, Logger, TermLogger};<br><br><span class="hljs-keyword">fn</span> <span class="hljs-title function_">main</span>() <span class="hljs-punctuation">-&gt;</span> FlashResult&lt;()&gt; {<br>    <span class="hljs-comment">// 初始化日志系统</span><br>    TermLogger::<span class="hljs-title function_ invoke__">init</span>(<span class="hljs-literal">true</span>)?;  <span class="hljs-comment">// verbose 模式</span><br><br>    <span class="hljs-keyword">let</span> <span class="hljs-variable">logger</span> = Logger::<span class="hljs-title function_ invoke__">new</span>();<br><br>    <span class="hljs-comment">// 使用 log crate 宏</span><br>    log::info!(<span class="hljs-string">"Starting firmware flash process"</span>);<br><br>    <span class="hljs-comment">// 使用 Logger 方法</span><br>    logger.<span class="hljs-title function_ invoke__">info</span>(<span class="hljs-string">"Loading firmware..."</span>);<br>    logger.<span class="hljs-title function_ invoke__">warn</span>(<span class="hljs-string">"Firmware size exceeds recommended limit"</span>);<br><br>    <span class="hljs-comment">// 错误处理</span><br>    <span class="hljs-keyword">match</span> <span class="hljs-title function_ invoke__">load_firmware</span>(&amp;path) {<br>        <span class="hljs-title function_ invoke__">Ok</span>(data) =&gt; logger.<span class="hljs-title function_ invoke__">success</span>(<span class="hljs-string">"Firmware loaded"</span>),<br>        <span class="hljs-title function_ invoke__">Err</span>(e) =&gt; {<br>            log::error!(<span class="hljs-string">"Failed: {}"</span>, e);<br>            <span class="hljs-keyword">return</span> <span class="hljs-title function_ invoke__">Err</span>(e);<br>        }<br>    }<br><br>    <span class="hljs-title function_ invoke__">Ok</span>(())<br>}<br></code></pre></td></tr></tbody></table></figure><h3 id="TUI-模式下的日志集成" data-id="TUI-模式下的日志集成" class="notion-h"><a href="#TUI-模式下的日志集成" class="headerlink" title="TUI 模式下的日志集成"></a>TUI 模式下的日志集成</h3><figure class="highlight rust"><table><tbody><tr><td class="code"><pre><code class="hljs rust"><span class="hljs-comment">// TUI 启动时设置日志通道</span><br><span class="hljs-keyword">use</span> tokio::sync::mpsc;<br><br><span class="hljs-keyword">let</span> (log_tx, log_rx) = mpsc::<span class="hljs-title function_ invoke__">unbounded_channel</span>();<br><span class="hljs-title function_ invoke__">set_tui_log_sender</span>(<span class="hljs-title function_ invoke__">Some</span>(log_tx));<br><br><span class="hljs-comment">// 所有日志消息会发送到 log_rx</span><br><span class="hljs-comment">// TUI 从 log_rx 接收并渲染到界面</span><br><br><span class="hljs-comment">// TUI 退出时清理</span><br><span class="hljs-title function_ invoke__">set_tui_log_sender</span>(<span class="hljs-literal">None</span>);<br></code></pre></td></tr></tbody></table></figure><h3 id="错误处理的最佳实践" data-id="错误处理的最佳实践" class="notion-h"><a href="#错误处理的最佳实践" class="headerlink" title="错误处理的最佳实践"></a>错误处理的最佳实践</h3><figure class="highlight rust"><table><tbody><tr><td class="code"><pre><code class="hljs rust"><span class="hljs-keyword">fn</span> <span class="hljs-title function_">flash_device</span>(args: FlashArgs) <span class="hljs-punctuation">-&gt;</span> FlashResult&lt;()&gt; {<br>    <span class="hljs-comment">// 1. 使用具体的错误类型</span><br>    <span class="hljs-keyword">let</span> <span class="hljs-variable">firmware</span> = std::fs::<span class="hljs-title function_ invoke__">read</span>(&amp;args.firmware_path)<br>        .<span class="hljs-title function_ invoke__">map_err</span>(|e| {<br>            <span class="hljs-keyword">if</span> e.<span class="hljs-title function_ invoke__">kind</span>() == std::io::ErrorKind::NotFound {<br>                FlashError::<span class="hljs-title function_ invoke__">FirmwareNotFound</span>(args.firmware_path.<span class="hljs-title function_ invoke__">display</span>().<span class="hljs-title function_ invoke__">to_string</span>())<br>            } <span class="hljs-keyword">else</span> {<br>                FlashError::<span class="hljs-title function_ invoke__">Io</span>(e)  <span class="hljs-comment">// 自动转换</span><br>            }<br>        })?;<br><br>    <span class="hljs-comment">// 2. 验证固件格式</span><br>    <span class="hljs-keyword">if</span> !<span class="hljs-title function_ invoke__">validate_firmware</span>(&amp;firmware) {<br>        <span class="hljs-keyword">return</span> <span class="hljs-title function_ invoke__">Err</span>(FlashError::<span class="hljs-title function_ invoke__">InvalidFirmwareFormat</span>(<br>            <span class="hljs-string">"Missing IMAGEWTY magic"</span>.<span class="hljs-title function_ invoke__">to_string</span>()<br>        ));<br>    }<br><br>    <span class="hljs-comment">// 3. 检查加密固件</span><br>    <span class="hljs-keyword">if</span> <span class="hljs-title function_ invoke__">is_encrypted</span>(&amp;firmware) {<br>        <span class="hljs-keyword">return</span> <span class="hljs-title function_ invoke__">Err</span>(FlashError::EncryptedNotSupported);<br>    }<br><br>    <span class="hljs-comment">// 4. 查找设备</span><br>    <span class="hljs-keyword">let</span> <span class="hljs-variable">device</span> = <span class="hljs-title function_ invoke__">find_device</span>(args.bus, args.port)<br>        .<span class="hljs-title function_ invoke__">ok_or</span>(FlashError::DeviceNotFound)?;<br><br>    <span class="hljs-comment">// 5. 执行刷写</span><br>    <span class="hljs-title function_ invoke__">execute_flash</span>(device, firmware)?;<br><br>    <span class="hljs-title function_ invoke__">Ok</span>(())<br>}<br></code></pre></td></tr></tbody></table></figure><hr><h2 id="错误类型分类" data-id="错误类型分类" class="notion-h"><a href="#错误类型分类" class="headerlink" title="错误类型分类"></a>错误类型分类</h2><pre class="mermaid">graph TD    A[FlashError] --&gt; B[固件错误]    A --&gt; C[设备错误]    A --&gt; D[刷写过程错误]    A --&gt; E[组件缺失错误]    A --&gt; F[外部错误]    B --&gt; B1[FirmwareNotFound]    B --&gt; B2[InvalidFirmwareFormat]    B --&gt; B3[EncryptedNotSupported]    C --&gt; C1[DeviceNotFound]    C --&gt; C2[DeviceOpenFailed]    C --&gt; C3[StorageTypeMismatch]    D --&gt; D1[DramInitFailed]    D --&gt; D2[UbootDownloadFailed]    D --&gt; D3[MbrDownloadFailed]    D --&gt; D4[PartitionDownloadFailed]    D --&gt; D5[ReconnectFailed]    D --&gt; D6[UsbTransferError]    D --&gt; D7[Timeout]    E --&gt; E1[FesNotFound]    E --&gt; E2[UbootNotFound]    E --&gt; E3[SysConfigNotFound]    E --&gt; E4[MbrNotFound]    E --&gt; E5[Boot0NotFound]    E --&gt; E6[Boot1NotFound]    F --&gt; F1[Io]    F --&gt; F2[Packer]    F --&gt; F3[Libefex]    F --&gt; F4[Unknown]</pre>]]>
    </content>
    <id>https://gloomyghost.com/posts/openixcli-utils-module/</id>
    <link href="https://gloomyghost.com/posts/openixcli-utils-module/"/>
    <published>2026-04-25T16:00:00.000Z</published>
    <summary>
      <![CDATA[<h2 id="概述" data-id="概述" class="notion-h"><a href="#概述" class="headerlink" title="概述"></a>概述</h2><p>在嵌入式开发中，一个稳固的固件刷写工具必须具备完善的错误处理和日志系统。Open]]>
    </summary>
    <title>OpenixCLI Utils 模块深度解析：错误处理与日志系统</title>
    <updated>2026-05-29T20:33:31.219Z</updated>
  </entry>
  <entry>
    <author>
      <name>柚木 鉉</name>
    </author>
    <category term="Allwinner" scheme="https://gloomyghost.com/tags/Allwinner/"/>
    <category term="Rust" scheme="https://gloomyghost.com/tags/Rust/"/>
    <category term="嵌入式" scheme="https://gloomyghost.com/tags/%E5%B5%8C%E5%85%A5%E5%BC%8F/"/>
    <category term="OpenixCLI" scheme="https://gloomyghost.com/tags/OpenixCLI/"/>
    <category term="固件" scheme="https://gloomyghost.com/tags/%E5%9B%BA%E4%BB%B6/"/>
    <content>
      <![CDATA[<h2 id="概述" data-id="概述" class="notion-h"><a href="#概述" class="headerlink" title="概述"></a>概述</h2><p>固件解析是嵌入式刷写工具的核心功能。全志（Allwinner）芯片使用一种名为 IMAGEWTY 的专有固件格式，将多个组件（FES、U-Boot、MBR、DTB、分区数据等）打包成单一文件。OpenixCLI 的 Firmware 模块负责解析这种二进制格式，提取各组件数据供刷写流程使用。</p><h3 id="模块结构" data-id="模块结构" class="notion-h"><a href="#模块结构" class="headerlink" title="模块结构"></a>模块结构</h3><figure class="highlight plaintext"><table><tbody><tr><td class="code"><pre><code class="hljs plaintext">src/firmware/<br>├── mod.rs          # 模块导出<br>├── types.rs        # 二进制结构定义（#[repr(C, packed)]）<br>├── packer.rs       # OpenixPacker 固件解析器<br>├── image_data.rs   # 预定义组件查找表<br>└── sparse.rs       # Android sparse 格式解析<br></code></pre></td></tr></tbody></table></figure><h3 id="核心导出" data-id="核心导出" class="notion-h"><a href="#核心导出" class="headerlink" title="核心导出"></a>核心导出</h3><figure class="highlight rust"><table><tbody><tr><td class="code"><pre><code class="hljs rust"><span class="hljs-keyword">pub</span> <span class="hljs-keyword">use</span> packer::OpenixPacker;<br><span class="hljs-keyword">pub</span> <span class="hljs-keyword">use</span> packer::PackerError;<br><span class="hljs-keyword">pub</span> <span class="hljs-keyword">use</span> types::*;<br></code></pre></td></tr></tbody></table></figure><hr><h2 id="IMAGEWTY-文件格式" data-id="IMAGEWTY-文件格式" class="notion-h"><a href="#IMAGEWTY-文件格式" class="headerlink" title="IMAGEWTY 文件格式"></a>IMAGEWTY 文件格式</h2><h3 id="文件结构概览" data-id="文件结构概览" class="notion-h"><a href="#文件结构概览" class="headerlink" title="文件结构概览"></a>文件结构概览</h3><p>IMAGEWTY 固件文件采用分段式结构：</p><figure class="highlight plaintext"><table><tbody><tr><td class="code"><pre><code class="hljs plaintext">┌─────────────────────────────────────────────────────────────┐<br>│                    IMAGEWTY Firmware                        │<br>├─────────────────────────────────────────────────────────────┤<br>│ Offset 0x0000                                               │<br>│ ┌─────────────────────────────────────────────────────────┐ │<br>│ │              Image Header (1024 bytes)                  │ │<br>│ │  ┌───────────────────────────────────────────────────┐  │ │<br>│ │  │ Magic: "IMAGEWTY" (8 bytes)                       │  │ │<br>│ │  │ Header Version: 0x0100 or 0x0300                  │  │ │<br>│ │  │ Header Size, RAM Base, Version                    │  │ │<br>│ │  │ Image Size, Image Header Size                     │  │ │<br>│ │  │ Version-specific data (V1 or V3)                   │  │ │<br>│ │  │ - PID, VID, Hardware ID, Firmware ID              │  │ │<br>│ │  │ - Num Files                                       │  │ │<br>│ │  └───────────────────────────────────────────────────┘  │ │<br>│ └─────────────────────────────────────────────────────────┘ │<br>├─────────────────────────────────────────────────────────────┤<br>│ Offset 0x0400 (1024)                                        │<br>│ ┌─────────────────────────────────────────────────────────┐ │<br>│ │              File Header 0 (1024 bytes)                 │ │<br>│ │  ┌───────────────────────────────────────────────────┐  │ │<br>│ │  │ Filename Length, Total Header Size                │  │ │<br>│ │  │ Main Type: "FES" / "12345678" / "COMMON"          │  │ │<br>│ │  │ Sub Type: "FES_1-0000000000" / ...                │  │ │<br>│ │  │ Version-specific data (V1 or V3)                   │  │ │<br>│ │  │ - Stored Length, Original Length                  │  │ │<br>│ │  │ - Offset                                          │  │ │<br>│ │  │ - Filename                                        │  │ │<br>│ │  └───────────────────────────────────────────────────┘  │ │<br>│ └─────────────────────────────────────────────────────────┘ │<br>├─────────────────────────────────────────────────────────────┤<br>│ Offset 0x0800 (2048)                                        │<br>│ ┌─────────────────────────────────────────────────────────┐ │<br>│ │              File Header 1 (1024 bytes)                 │ │<br>│ └─────────────────────────────────────────────────────────┘ │<br>│ ...                                                         │<br>│ ┌─────────────────────────────────────────────────────────┐ │<br>│ │              File Header N (1024 bytes)                 │ │<br>│ └─────────────────────────────────────────────────────────┘ │<br>├─────────────────────────────────────────────────────────────┤<br>│ Offset = File Data                                          │<br>│ ┌─────────────────────────────────────────────────────────┐ │<br>│ │              File Data                                  │ │<br>│ └─────────────────────────────────────────────────────────┘ │<br>│ ...                                                         │<br>└─────────────────────────────────────────────────────────────┘<br></code></pre></td></tr></tbody></table></figure><h3 id="固件组件一览" data-id="固件组件一览" class="notion-h"><a href="#固件组件一览" class="headerlink" title="固件组件一览"></a>固件组件一览</h3><table><thead><tr><th>组件名称</th><th>Main Type</th><th>Sub Type</th><th>功能说明</th></tr></thead><tbody><tr><td>FES</td><td>FES</td><td>FES_1-0000000000</td><td>Flash Eraser Script，初始化 DRAM</td></tr><tr><td>U-Boot</td><td>12345678</td><td>UBOOT_0000000000</td><td>U-Boot bootloader</td></tr><tr><td>MBR</td><td>12345678</td><td>1234567890___MBR</td><td>Master Boot Record 分区表</td></tr><tr><td>GPT</td><td>12345678</td><td>1234567890___GPT</td><td>GUID Partition Table</td></tr><tr><td>DTB</td><td>COMMON</td><td>DTB_CONFIG000000</td><td>Device Tree Blob</td></tr><tr><td>sys_config</td><td>COMMON</td><td>SYS_CONFIG100000</td><td>系统配置文本</td></tr><tr><td>sys_config_bin</td><td>COMMON</td><td>SYS_CONFIG_BIN00</td><td>系统配置二进制</td></tr><tr><td>sys_partition</td><td>COMMON</td><td>SYS_CONFIG000000</td><td>分区配置</td></tr><tr><td>board_config</td><td>COMMON</td><td>BOARD_CONFIG_BIN</td><td>板级配置</td></tr><tr><td>boot0_card</td><td>12345678</td><td>1234567890BOOT_0</td><td>SD 卡 Boot0</td></tr><tr><td>bootpkg</td><td>BOOTPKG</td><td>BOOTPKG-00000000</td><td>启动包</td></tr></tbody></table><hr><h2 id="核心数据结构" data-id="核心数据结构" class="notion-h"><a href="#核心数据结构" class="headerlink" title="核心数据结构"></a>核心数据结构</h2><h3 id="常量定义" data-id="常量定义" class="notion-h"><a href="#常量定义" class="headerlink" title="常量定义"></a>常量定义</h3><figure class="highlight rust"><table><tbody><tr><td class="code"><pre><code class="hljs rust"><span class="hljs-comment">/// IMAGEWTY 格式魔数</span><br><span class="hljs-keyword">pub</span> <span class="hljs-keyword">const</span> IMAGEWTY_MAGIC: &amp;<span class="hljs-type">str</span> = <span class="hljs-string">"IMAGEWTY"</span>;<br><span class="hljs-keyword">pub</span> <span class="hljs-keyword">const</span> IMAGEWTY_MAGIC_LEN: <span class="hljs-type">usize</span> = <span class="hljs-number">8</span>;<br><br><span class="hljs-comment">/// 文件头长度（固定 1024 字节）</span><br><span class="hljs-keyword">pub</span> <span class="hljs-keyword">const</span> IMAGEWTY_FILEHDR_LEN: <span class="hljs-type">usize</span> = <span class="hljs-number">1024</span>;<br><br><span class="hljs-comment">/// Main Type 字段长度</span><br><span class="hljs-keyword">pub</span> <span class="hljs-keyword">const</span> IMAGEWTY_FHDR_MAINTYPE_LEN: <span class="hljs-type">usize</span> = <span class="hljs-number">8</span>;<br><br><span class="hljs-comment">/// Sub Type 字段长度</span><br><span class="hljs-keyword">pub</span> <span class="hljs-keyword">const</span> IMAGEWTY_FHDR_SUBTYPE_LEN: <span class="hljs-type">usize</span> = <span class="hljs-number">16</span>;<br><br><span class="hljs-comment">/// Filename 字段长度</span><br><span class="hljs-keyword">pub</span> <span class="hljs-keyword">const</span> IMAGEWTY_FHDR_FILENAME_LEN: <span class="hljs-type">usize</span> = <span class="hljs-number">256</span>;<br></code></pre></td></tr></tbody></table></figure><h3 id="ImageHeader-结构" data-id="ImageHeader-结构" class="notion-h"><a href="#ImageHeader-结构" class="headerlink" title="ImageHeader 结构"></a>ImageHeader 结构</h3><p>主头结构包含固件的元信息：</p><figure class="highlight rust"><table><tbody><tr><td class="code"><pre><code class="hljs rust"><span class="hljs-comment">/// IMAGEWTY 主头结构</span><br><span class="hljs-comment">///</span><br><span class="hljs-comment">/// 使用 #[repr(C, packed)] 确保与二进制格式完全匹配</span><br><span class="hljs-meta">#[repr(C, packed)]</span><br><span class="hljs-keyword">pub</span> <span class="hljs-keyword">struct</span> <span class="hljs-title class_">ImageHeader</span> {<br>    <span class="hljs-comment">/// 数标识：必须为 "IMAGEWTY"</span><br>    <span class="hljs-keyword">pub</span> magic: [<span class="hljs-type">u8</span>; IMAGEWTY_MAGIC_LEN],<br><br>    <span class="hljs-comment">/// 头版本：0x0100 (V1) 或 0x0300 (V3)</span><br>    <span class="hljs-keyword">pub</span> header_version: <span class="hljs-type">u32</span>,<br><br>    <span class="hljs-comment">/// 头大小</span><br>    <span class="hljs-keyword">pub</span> header_size: <span class="hljs-type">u32</span>,<br><br>    <span class="hljs-comment">/// RAM 基地址</span><br>    <span class="hljs-keyword">pub</span> ram_base: <span class="hljs-type">u32</span>,<br><br>    <span class="hljs-comment">/// 固件版本号</span><br>    <span class="hljs-keyword">pub</span> version: <span class="hljs-type">u32</span>,<br><br>    <span class="hljs-comment">/// 整个镜像大小</span><br>    <span class="hljs-keyword">pub</span> image_size: <span class="hljs-type">u32</span>,<br><br>    <span class="hljs-comment">/// 镜像头大小</span><br>    <span class="hljs-keyword">pub</span> image_header_size: <span class="hljs-type">u32</span>,<br><br>    <span class="hljs-comment">/// 版本特定数据（联合体）</span><br>    <span class="hljs-keyword">pub</span> data: ImageHeaderVersionData,<br>}<br></code></pre></td></tr></tbody></table></figure><p><strong>关键特性：</strong></p><ul><li><code>#[repr(C, packed)]</code> - 禁止内存对齐，直接映射到二进制布局</li><li>联合体 <code>ImageHeaderVersionData</code> - 支持不同版本的头部结构</li></ul><h3 id="ImageHeaderVersionData-联合体" data-id="ImageHeaderVersionData-联合体" class="notion-h"><a href="#ImageHeaderVersionData-联合体" class="headerlink" title="ImageHeaderVersionData 联合体"></a>ImageHeaderVersionData 联合体</h3><figure class="highlight rust"><table><tbody><tr><td class="code"><pre><code class="hljs rust"><span class="hljs-comment">/// 头版本数据联合体</span><br><span class="hljs-comment">///</span><br><span class="hljs-comment">/// 根据 header_version 选择 V1 或 V3 结构</span><br><span class="hljs-meta">#[repr(C, packed)]</span><br><span class="hljs-keyword">pub</span> <span class="hljs-keyword">union</span> <span class="hljs-title class_">ImageHeaderVersionData</span> {<br>    <span class="hljs-keyword">pub</span> v1: ImageHeaderV1,<br>    <span class="hljs-keyword">pub</span> v3: ImageHeaderV3,<br>}<br><br><span class="hljs-comment">/// V1 版本头结构</span><br><span class="hljs-meta">#[repr(C, packed)]</span><br><span class="hljs-meta">#[derive(Debug, Clone, Copy)]</span><br><span class="hljs-keyword">pub</span> <span class="hljs-keyword">struct</span> <span class="hljs-title class_">ImageHeaderV1</span> {<br>    <span class="hljs-keyword">pub</span> pid: <span class="hljs-type">u32</span>,              <span class="hljs-comment">// Product ID</span><br>    <span class="hljs-keyword">pub</span> vid: <span class="hljs-type">u32</span>,              <span class="hljs-comment">// Vendor ID</span><br>    <span class="hljs-keyword">pub</span> hardware_id: <span class="hljs-type">u32</span>,      <span class="hljs-comment">// 硬件 ID</span><br>    <span class="hljs-keyword">pub</span> firmware_id: <span class="hljs-type">u32</span>,      <span class="hljs-comment">// 固件 ID</span><br>    <span class="hljs-keyword">pub</span> val1: <span class="hljs-type">u32</span>,<br>    <span class="hljs-keyword">pub</span> val1024: <span class="hljs-type">u32</span>,<br>    <span class="hljs-keyword">pub</span> num_files: <span class="hljs-type">u32</span>,        <span class="hljs-comment">// 文件数量</span><br>    <span class="hljs-keyword">pub</span> val1024_2: <span class="hljs-type">u32</span>,<br>    <span class="hljs-keyword">pub</span> val0: <span class="hljs-type">u32</span>,<br>    <span class="hljs-keyword">pub</span> val0_2: <span class="hljs-type">u32</span>,<br>    <span class="hljs-keyword">pub</span> val0_3: <span class="hljs-type">u32</span>,<br>    <span class="hljs-keyword">pub</span> val0_4: <span class="hljs-type">u32</span>,<br>}<br><br><span class="hljs-comment">/// V3 版本头结构</span><br><span class="hljs-meta">#[repr(C, packed)]</span><br><span class="hljs-meta">#[derive(Debug, Clone, Copy)]</span><br><span class="hljs-keyword">pub</span> <span class="hljs-keyword">struct</span> <span class="hljs-title class_">ImageHeaderV3</span> {<br>    <span class="hljs-keyword">pub</span> unknown: <span class="hljs-type">u32</span>,          <span class="hljs-comment">// V3 新增字段</span><br>    <span class="hljs-keyword">pub</span> pid: <span class="hljs-type">u32</span>,<br>    <span class="hljs-keyword">pub</span> vid: <span class="hljs-type">u32</span>,<br>    <span class="hljs-keyword">pub</span> hardware_id: <span class="hljs-type">u32</span>,<br>    <span class="hljs-keyword">pub</span> firmware_id: <span class="hljs-type">u32</span>,<br>    <span class="hljs-keyword">pub</span> val1: <span class="hljs-type">u32</span>,<br>    <span class="hljs-keyword">pub</span> val1024: <span class="hljs-type">u32</span>,<br>    <span class="hljs-keyword">pub</span> num_files: <span class="hljs-type">u32</span>,<br>    <span class="hljs-keyword">pub</span> val1024_2: <span class="hljs-type">u32</span>,<br>    <span class="hljs-keyword">pub</span> val0: <span class="hljs-type">u32</span>,<br>    <span class="hljs-keyword">pub</span> val0_2: <span class="hljs-type">u32</span>,<br>    <span class="hljs-keyword">pub</span> val0_3: <span class="hljs-type">u32</span>,<br>    <span class="hljs-keyword">pub</span> val0_4: <span class="hljs-type">u32</span>,<br>}<br></code></pre></td></tr></tbody></table></figure><h3 id="FileHeader-结构" data-id="FileHeader-结构" class="notion-h"><a href="#FileHeader-结构" class="headerlink" title="FileHeader 结构"></a>FileHeader 结构</h3><p>每个文件的头结构：</p><figure class="highlight rust"><table><tbody><tr><td class="code"><pre><code class="hljs rust"><span class="hljs-comment">/// 文件头结构</span><br><span class="hljs-meta">#[repr(C, packed)]</span><br><span class="hljs-keyword">pub</span> <span class="hljs-keyword">struct</span> <span class="hljs-title class_">FileHeader</span> {<br>    <span class="hljs-comment">/// 文件名长度</span><br>    <span class="hljs-keyword">pub</span> filename_len: <span class="hljs-type">u32</span>,<br><br>    <span class="hljs-comment">/// 总头大小</span><br>    <span class="hljs-keyword">pub</span> total_header_size: <span class="hljs-type">u32</span>,<br><br>    <span class="hljs-comment">/// 主类型标识（如 "FES", "12345678"）</span><br>    <span class="hljs-keyword">pub</span> maintype: [<span class="hljs-type">u8</span>; IMAGEWTY_FHDR_MAINTYPE_LEN],<br><br>    <span class="hljs-comment">/// 子类型标识</span><br>    <span class="hljs-keyword">pub</span> subtype: [<span class="hljs-type">u8</span>; IMAGEWTY_FHDR_SUBTYPE_LEN],<br><br>    <span class="hljs-comment">/// 版本特定数据</span><br>    <span class="hljs-keyword">pub</span> data: FileHeaderVersionData,<br>}<br><br><span class="hljs-comment">/// 文件头版本数据联合体</span><br><span class="hljs-meta">#[repr(C, packed)]</span><br><span class="hljs-keyword">pub</span> <span class="hljs-keyword">union</span> <span class="hljs-title class_">FileHeaderVersionData</span> {<br>    <span class="hljs-keyword">pub</span> v1: FileHeaderV1,<br>    <span class="hljs-keyword">pub</span> v3: FileHeaderV3,<br>}<br><br><span class="hljs-comment">/// V1 版本文件头</span><br><span class="hljs-meta">#[repr(C, packed)]</span><br><span class="hljs-meta">#[derive(Debug, Clone, Copy)]</span><br><span class="hljs-keyword">pub</span> <span class="hljs-keyword">struct</span> <span class="hljs-title class_">FileHeaderV1</span> {<br>    <span class="hljs-keyword">pub</span> unknown_3: <span class="hljs-type">u32</span>,<br>    <span class="hljs-keyword">pub</span> stored_length: <span class="hljs-type">u32</span>,    <span class="hljs-comment">// 存储长度（压缩后）</span><br>    <span class="hljs-keyword">pub</span> original_length: <span class="hljs-type">u32</span>,  <span class="hljs-comment">// 原始长度</span><br>    <span class="hljs-keyword">pub</span> offset: <span class="hljs-type">u32</span>,           <span class="hljs-comment">// 数据偏移</span><br>    <span class="hljs-keyword">pub</span> unknown: <span class="hljs-type">u32</span>,<br>    <span class="hljs-keyword">pub</span> filename: [<span class="hljs-type">u8</span>; IMAGEWTY_FHDR_FILENAME_LEN],<br>}<br><br><span class="hljs-comment">/// V3 版本文件头</span><br><span class="hljs-meta">#[repr(C, packed)]</span><br><span class="hljs-meta">#[derive(Debug, Clone, Copy)]</span><br><span class="hljs-keyword">pub</span> <span class="hljs-keyword">struct</span> <span class="hljs-title class_">FileHeaderV3</span> {<br>    <span class="hljs-keyword">pub</span> unknown_0: <span class="hljs-type">u32</span>,<br>    <span class="hljs-keyword">pub</span> filename: [<span class="hljs-type">u8</span>; IMAGEWTY_FHDR_FILENAME_LEN],  <span class="hljs-comment">// V3 中 filename 位置不同</span><br>    <span class="hljs-keyword">pub</span> stored_length: <span class="hljs-type">u32</span>,<br>    <span class="hljs-keyword">pub</span> pad1: <span class="hljs-type">u32</span>,<br>    <span class="hljs-keyword">pub</span> original_length: <span class="hljs-type">u32</span>,<br>    <span class="hljs-keyword">pub</span> pad2: <span class="hljs-type">u32</span>,<br>    <span class="hljs-keyword">pub</span> offset: <span class="hljs-type">u32</span>,<br>}<br></code></pre></td></tr></tbody></table></figure><p><strong>注意：</strong> V1 和 V3 结构中 <code>filename</code> 字段的位置不同，这是解析时需要根据版本号处理的关键点。</p><h3 id="ImageInfo-和-FileInfo" data-id="ImageInfo-和-FileInfo" class="notion-h"><a href="#ImageInfo-和-FileInfo" class="headerlink" title="ImageInfo 和 FileInfo"></a>ImageInfo 和 FileInfo</h3><p>解析后的高层抽象结构：</p><figure class="highlight rust"><table><tbody><tr><td class="code"><pre><code class="hljs rust"><span class="hljs-comment">/// 镜像信息容器</span><br><span class="hljs-meta">#[derive(Debug, Clone)]</span><br><span class="hljs-keyword">pub</span> <span class="hljs-keyword">struct</span> <span class="hljs-title class_">ImageInfo</span> {<br>    <span class="hljs-keyword">pub</span> header: ImageHeader,<br>    <span class="hljs-keyword">pub</span> files: <span class="hljs-type">Vec</span>&lt;FileInfo&gt;,<br>    <span class="hljs-keyword">pub</span> is_encrypted: <span class="hljs-type">bool</span>,<br>    <span class="hljs-keyword">pub</span> image_size: <span class="hljs-type">u32</span>,<br>    <span class="hljs-keyword">pub</span> num_files: <span class="hljs-type">u32</span>,<br>}<br><br><span class="hljs-comment">/// 文件信息结构</span><br><span class="hljs-meta">#[derive(Debug, Clone)]</span><br><span class="hljs-keyword">pub</span> <span class="hljs-keyword">struct</span> <span class="hljs-title class_">FileInfo</span> {<br>    <span class="hljs-keyword">pub</span> filename: <span class="hljs-type">String</span>,<br>    <span class="hljs-keyword">pub</span> maintype: <span class="hljs-type">String</span>,<br>    <span class="hljs-keyword">pub</span> subtype: <span class="hljs-type">String</span>,<br>    <span class="hljs-keyword">pub</span> stored_length: <span class="hljs-type">u32</span>,<br>    <span class="hljs-keyword">pub</span> original_length: <span class="hljs-type">u32</span>,<br>    <span class="hljs-keyword">pub</span> offset: <span class="hljs-type">u32</span>,<br>}<br></code></pre></td></tr></tbody></table></figure><h3 id="StorageType-枚举" data-id="StorageType-枚举" class="notion-h"><a href="#StorageType-枚举" class="headerlink" title="StorageType 枚举"></a>StorageType 枚举</h3><figure class="highlight rust"><table><tbody><tr><td class="code"><pre><code class="hljs rust"><span class="hljs-comment">/// 存储类型枚举</span><br><span class="hljs-meta">#[derive(Debug, Clone, Copy, PartialEq, Eq)]</span><br><span class="hljs-keyword">pub</span> <span class="hljs-keyword">enum</span> <span class="hljs-title class_">StorageType</span> {<br>    Nand = <span class="hljs-number">0</span>,      <span class="hljs-comment">// NAND Flash</span><br>    Sdcard = <span class="hljs-number">1</span>,    <span class="hljs-comment">// SD 卡</span><br>    Emmc = <span class="hljs-number">2</span>,      <span class="hljs-comment">// eMMC</span><br>    Spinor = <span class="hljs-number">3</span>,    <span class="hljs-comment">// SPI NOR Flash</span><br>    Emmc3 = <span class="hljs-number">4</span>,     <span class="hljs-comment">// eMMC v3</span><br>    Spinand = <span class="hljs-number">5</span>,   <span class="hljs-comment">// SPI NAND Flash</span><br>    Sd1 = <span class="hljs-number">6</span>,       <span class="hljs-comment">// SD 卡槽 1</span><br>    Emmc0 = <span class="hljs-number">7</span>,     <span class="hljs-comment">// eMMC 槽 0</span><br>    Ufs = <span class="hljs-number">8</span>,       <span class="hljs-comment">// UFS</span><br>    Auto = -<span class="hljs-number">1</span>,     <span class="hljs-comment">// 自动检测</span><br>}<br></code></pre></td></tr></tbody></table></figure><hr><h2 id="OpenixPacker-解析器" data-id="OpenixPacker-解析器" class="notion-h"><a href="#OpenixPacker-解析器" class="headerlink" title="OpenixPacker 解析器"></a>OpenixPacker 解析器</h2><h3 id="结构定义" data-id="结构定义" class="notion-h"><a href="#结构定义" class="headerlink" title="结构定义"></a>结构定义</h3><figure class="highlight rust"><table><tbody><tr><td class="code"><pre><code class="hljs rust"><span class="hljs-comment">/// IMAGEWTY 固件解析器</span><br><span class="hljs-comment">///</span><br><span class="hljs-comment">/// 提供固件加载和文件提取功能</span><br><span class="hljs-keyword">pub</span> <span class="hljs-keyword">struct</span> <span class="hljs-title class_">OpenixPacker</span> {<br>    file: <span class="hljs-type">Option</span>&lt;File&gt;,             <span class="hljs-comment">// 文件句柄</span><br>    image_header: <span class="hljs-type">Option</span>&lt;ImageHeader&gt;,  <span class="hljs-comment">// 镜像头</span><br>    file_headers: <span class="hljs-type">Vec</span>&lt;FileHeader&gt;,  <span class="hljs-comment">// 所有文件头</span><br>    is_encrypted: <span class="hljs-type">bool</span>,             <span class="hljs-comment">// 是否加密</span><br>    image_loaded: <span class="hljs-type">bool</span>,             <span class="hljs-comment">// 是否已加载</span><br>}<br></code></pre></td></tr></tbody></table></figure><h3 id="加载流程" data-id="加载流程" class="notion-h"><a href="#加载流程" class="headerlink" title="加载流程"></a>加载流程</h3><pre class="mermaid">sequenceDiagram    participant User as 用户代码    participant Packer as OpenixPacker    participant File as std::fs::File    participant Header as ImageHeader/FileHeader    User-&gt;&gt;Packer: load(path)    Packer-&gt;&gt;File: open(path)    File--&gt;&gt;Packer: File handle    Packer-&gt;&gt;File: read_exact(8 bytes)    File--&gt;&gt;Packer: Magic buffer    Packer-&gt;&gt;Packer: 验证 Magic = "IMAGEWTY"    alt Magic != IMAGEWTY        Packer--&gt;&gt;User: PackerError::EncryptedNotSupported    else Magic == IMAGEWTY        Packer-&gt;&gt;File: seek(Start(0))        Packer-&gt;&gt;File: read_exact(1024 bytes)        File--&gt;&gt;Packer: Header buffer        Packer-&gt;&gt;Header: ImageHeader::parse()        Header--&gt;&gt;Packer: ImageHeader        loop i = 0..num_files            Packer-&gt;&gt;File: seek(1024 + i * 1024)            Packer-&gt;&gt;File: read_exact(1024 bytes)            File--&gt;&gt;Packer: FileHeader buffer            Packer-&gt;&gt;Header: FileHeader::parse()            Header--&gt;&gt;Packer: FileHeader        end        Packer-&gt;&gt;Packer: 设置 image_loaded = true        Packer--&gt;&gt;User: Ok(())    end</pre><h3 id="load-方法实现" data-id="load-方法实现" class="notion-h"><a href="#load-方法实现" class="headerlink" title="load 方法实现"></a>load 方法实现</h3><figure class="highlight rust"><table><tbody><tr><td class="code"><pre><code class="hljs rust"><span class="hljs-comment">/// 加载固件文件</span><br><span class="hljs-keyword">pub</span> <span class="hljs-keyword">fn</span> <span class="hljs-title function_">load</span>&lt;P: <span class="hljs-built_in">AsRef</span>&lt;Path&gt;&gt;(&amp;<span class="hljs-keyword">mut</span> <span class="hljs-keyword">self</span>, path: P) <span class="hljs-punctuation">-&gt;</span> <span class="hljs-type">Result</span>&lt;(), PackerError&gt; {<br>    <span class="hljs-keyword">let</span> <span class="hljs-keyword">mut </span><span class="hljs-variable">file</span> = File::<span class="hljs-title function_ invoke__">open</span>(path)?;<br><br>    <span class="hljs-comment">// 1. 读取并验证魔数</span><br>    <span class="hljs-keyword">let</span> <span class="hljs-keyword">mut </span><span class="hljs-variable">magic_buf</span> = [<span class="hljs-number">0u8</span>; IMAGEWTY_MAGIC_LEN];<br>    file.<span class="hljs-title function_ invoke__">read_exact</span>(&amp;<span class="hljs-keyword">mut</span> magic_buf)?;<br>    <span class="hljs-keyword">let</span> <span class="hljs-variable">magic</span> = <span class="hljs-type">String</span>::<span class="hljs-title function_ invoke__">from_utf8_lossy</span>(&amp;magic_buf).<span class="hljs-title function_ invoke__">to_string</span>();<br><br>    <span class="hljs-keyword">if</span> magic != IMAGEWTY_MAGIC {<br>        <span class="hljs-keyword">self</span>.is_encrypted = <span class="hljs-literal">true</span>;<br>        <span class="hljs-keyword">return</span> <span class="hljs-title function_ invoke__">Err</span>(PackerError::EncryptedNotSupported);<br>    }<br><br>    <span class="hljs-comment">// 2. 回到文件开头，读取完整 ImageHeader</span><br>    file.<span class="hljs-title function_ invoke__">seek</span>(SeekFrom::<span class="hljs-title function_ invoke__">Start</span>(<span class="hljs-number">0</span>))?;<br><br>    <span class="hljs-keyword">let</span> <span class="hljs-keyword">mut </span><span class="hljs-variable">header_buf</span> = [<span class="hljs-number">0u8</span>; IMAGEWTY_FILEHDR_LEN];<br>    file.<span class="hljs-title function_ invoke__">read_exact</span>(&amp;<span class="hljs-keyword">mut</span> header_buf)?;<br><br>    <span class="hljs-keyword">let</span> <span class="hljs-variable">image_header</span> = ImageHeader::<span class="hljs-title function_ invoke__">parse</span>(&amp;header_buf)<br>        .<span class="hljs-title function_ invoke__">map_err</span>(PackerError::ParseError)?;<br><br>    <span class="hljs-comment">// 3. 获取文件数量</span><br>    <span class="hljs-keyword">let</span> <span class="hljs-variable">num_files</span> = image_header.<span class="hljs-title function_ invoke__">num_files</span>();<br><br>    <span class="hljs-comment">// 4. 逐个读取 FileHeader</span><br>    <span class="hljs-keyword">let</span> <span class="hljs-keyword">mut </span><span class="hljs-variable">file_headers</span> = <span class="hljs-type">Vec</span>::<span class="hljs-title function_ invoke__">with_capacity</span>(num_files <span class="hljs-keyword">as</span> <span class="hljs-type">usize</span>);<br>    <span class="hljs-keyword">for</span> <span class="hljs-variable">i</span> <span class="hljs-keyword">in</span> <span class="hljs-number">0</span>..num_files {<br>        <span class="hljs-keyword">let</span> <span class="hljs-variable">offset</span> = IMAGEWTY_FILEHDR_LEN + (i <span class="hljs-keyword">as</span> <span class="hljs-type">usize</span>) * IMAGEWTY_FILEHDR_LEN;<br>        file.<span class="hljs-title function_ invoke__">seek</span>(SeekFrom::<span class="hljs-title function_ invoke__">Start</span>(offset <span class="hljs-keyword">as</span> <span class="hljs-type">u64</span>))?;<br><br>        <span class="hljs-keyword">let</span> <span class="hljs-keyword">mut </span><span class="hljs-variable">file_header_buf</span> = [<span class="hljs-number">0u8</span>; IMAGEWTY_FILEHDR_LEN];<br>        file.<span class="hljs-title function_ invoke__">read_exact</span>(&amp;<span class="hljs-keyword">mut</span> file_header_buf)?;<br><br>        <span class="hljs-keyword">let</span> <span class="hljs-variable">file_header</span> = FileHeader::<span class="hljs-title function_ invoke__">parse</span>(&amp;file_header_buf)<br>            .<span class="hljs-title function_ invoke__">map_err</span>(PackerError::ParseError)?;<br>        file_headers.<span class="hljs-title function_ invoke__">push</span>(*file_header);<br>    }<br><br>    <span class="hljs-comment">// 5. 更新内部状态</span><br>    <span class="hljs-keyword">self</span>.file = <span class="hljs-title function_ invoke__">Some</span>(file);<br>    <span class="hljs-keyword">self</span>.image_header = <span class="hljs-title function_ invoke__">Some</span>(*image_header);<br>    <span class="hljs-keyword">self</span>.file_headers = file_headers;<br>    <span class="hljs-keyword">self</span>.image_loaded = <span class="hljs-literal">true</span>;<br><br>    <span class="hljs-title function_ invoke__">Ok</span>(())<br>}<br></code></pre></td></tr></tbody></table></figure><h3 id="文件提取方法" data-id="文件提取方法" class="notion-h"><a href="#文件提取方法" class="headerlink" title="文件提取方法"></a>文件提取方法</h3><figure class="highlight rust"><table><tbody><tr><td class="code"><pre><code class="hljs rust"><span class="hljs-comment">/// 按文件名提取数据</span><br><span class="hljs-keyword">pub</span> <span class="hljs-keyword">fn</span> <span class="hljs-title function_">get_file_data_by_filename</span>(&amp;<span class="hljs-keyword">mut</span> <span class="hljs-keyword">self</span>, filename: &amp;<span class="hljs-type">str</span>) <span class="hljs-punctuation">-&gt;</span> <span class="hljs-type">Result</span>&lt;<span class="hljs-type">Vec</span>&lt;<span class="hljs-type">u8</span>&gt;, PackerError&gt; {<br>    <span class="hljs-keyword">if</span> !<span class="hljs-keyword">self</span>.image_loaded {<br>        <span class="hljs-keyword">return</span> <span class="hljs-title function_ invoke__">Err</span>(PackerError::ImageNotLoaded);<br>    }<br><br>    <span class="hljs-keyword">let</span> <span class="hljs-variable">header_version</span> = <span class="hljs-keyword">self</span>.<span class="hljs-title function_ invoke__">get_header_version</span>();<br>    <span class="hljs-keyword">let</span> <span class="hljs-variable">file_header</span> = <span class="hljs-keyword">self</span>.<span class="hljs-title function_ invoke__">get_file_header_by_filename</span>(filename)<br>        .<span class="hljs-title function_ invoke__">ok_or_else</span>(|| PackerError::<span class="hljs-title function_ invoke__">FileNotFound</span>(filename.<span class="hljs-title function_ invoke__">to_string</span>()))?;<br><br>    <span class="hljs-keyword">self</span>.<span class="hljs-title function_ invoke__">read_data_at_offset</span>(<br>        file_header.<span class="hljs-title function_ invoke__">offset</span>(header_version),<br>        file_header.<span class="hljs-title function_ invoke__">original_length</span>(header_version),<br>    )<br>}<br><br><span class="hljs-comment">/// 按 maintype/subtype 提取数据</span><br><span class="hljs-keyword">pub</span> <span class="hljs-keyword">fn</span> <span class="hljs-title function_">get_file_data_by_maintype_subtype</span>(<br>    &amp;<span class="hljs-keyword">mut</span> <span class="hljs-keyword">self</span>,<br>    maintype: &amp;<span class="hljs-type">str</span>,<br>    subtype: &amp;<span class="hljs-type">str</span>,<br>) <span class="hljs-punctuation">-&gt;</span> <span class="hljs-type">Result</span>&lt;<span class="hljs-type">Vec</span>&lt;<span class="hljs-type">u8</span>&gt;, PackerError&gt; {<br>    <span class="hljs-keyword">if</span> !<span class="hljs-keyword">self</span>.image_loaded {<br>        <span class="hljs-keyword">return</span> <span class="hljs-title function_ invoke__">Err</span>(PackerError::ImageNotLoaded);<br>    }<br><br>    <span class="hljs-keyword">let</span> <span class="hljs-variable">header_version</span> = <span class="hljs-keyword">self</span>.<span class="hljs-title function_ invoke__">get_header_version</span>();<br>    <span class="hljs-keyword">let</span> <span class="hljs-variable">file_header</span> = <span class="hljs-keyword">self</span>.<span class="hljs-title function_ invoke__">get_file_header_by_maintype_subtype</span>(maintype, subtype)<br>        .<span class="hljs-title function_ invoke__">ok_or_else</span>(|| PackerError::<span class="hljs-title function_ invoke__">FileNotFound</span>(<span class="hljs-built_in">format!</span>(<span class="hljs-string">"{}/{}"</span>, maintype, subtype)))?;<br><br>    <span class="hljs-keyword">self</span>.<span class="hljs-title function_ invoke__">read_data_at_offset</span>(<br>        file_header.<span class="hljs-title function_ invoke__">offset</span>(header_version),<br>        file_header.<span class="hljs-title function_ invoke__">original_length</span>(header_version),<br>    )<br>}<br><br><span class="hljs-comment">/// 在指定偏移读取数据</span><br><span class="hljs-keyword">fn</span> <span class="hljs-title function_">read_data_at_offset</span>(&amp;<span class="hljs-keyword">mut</span> <span class="hljs-keyword">self</span>, offset: <span class="hljs-type">u32</span>, length: <span class="hljs-type">u32</span>) <span class="hljs-punctuation">-&gt;</span> <span class="hljs-type">Result</span>&lt;<span class="hljs-type">Vec</span>&lt;<span class="hljs-type">u8</span>&gt;, PackerError&gt; {<br>    <span class="hljs-keyword">let</span> <span class="hljs-variable">file</span> = <span class="hljs-keyword">self</span>.file.<span class="hljs-title function_ invoke__">as_mut</span>().<span class="hljs-title function_ invoke__">ok_or</span>(PackerError::ImageNotLoaded)?;<br><br>    file.<span class="hljs-title function_ invoke__">seek</span>(SeekFrom::<span class="hljs-title function_ invoke__">Start</span>(offset <span class="hljs-keyword">as</span> <span class="hljs-type">u64</span>))?;<br><br>    <span class="hljs-keyword">let</span> <span class="hljs-keyword">mut </span><span class="hljs-variable">buffer</span> = <span class="hljs-built_in">vec!</span>[<span class="hljs-number">0u8</span>; length <span class="hljs-keyword">as</span> <span class="hljs-type">usize</span>];<br>    file.<span class="hljs-title function_ invoke__">read_exact</span>(&amp;<span class="hljs-keyword">mut</span> buffer)?;<br><br>    <span class="hljs-title function_ invoke__">Ok</span>(buffer)<br>}<br></code></pre></td></tr></tbody></table></figure><h3 id="预定义组件快捷方法" data-id="预定义组件快捷方法" class="notion-h"><a href="#预定义组件快捷方法" class="headerlink" title="预定义组件快捷方法"></a>预定义组件快捷方法</h3><figure class="highlight rust"><table><tbody><tr><td class="code"><pre><code class="hljs rust"><span class="hljs-comment">/// 通过预定义名称获取组件数据</span><br><span class="hljs-keyword">pub</span> <span class="hljs-keyword">fn</span> <span class="hljs-title function_">get_image_data_by_name</span>(&amp;<span class="hljs-keyword">mut</span> <span class="hljs-keyword">self</span>, name: &amp;<span class="hljs-type">str</span>) <span class="hljs-punctuation">-&gt;</span> <span class="hljs-type">Result</span>&lt;<span class="hljs-type">Vec</span>&lt;<span class="hljs-type">u8</span>&gt;, PackerError&gt; {<br>    <span class="hljs-keyword">if</span> <span class="hljs-keyword">let</span> <span class="hljs-variable">Some</span>(entry) = crate::firmware::image_data::<span class="hljs-title function_ invoke__">get_image_data_entry</span>(name) {<br>        <span class="hljs-keyword">self</span>.<span class="hljs-title function_ invoke__">get_file_data_by_maintype_subtype</span>(entry.maintype, entry.subtype)<br>    } <span class="hljs-keyword">else</span> {<br>        <span class="hljs-title function_ invoke__">Err</span>(PackerError::<span class="hljs-title function_ invoke__">FileNotFound</span>(name.<span class="hljs-title function_ invoke__">to_string</span>()))<br>    }<br>}<br><br><span class="hljs-comment">/// 获取 FES 数据</span><br><span class="hljs-keyword">pub</span> <span class="hljs-keyword">fn</span> <span class="hljs-title function_">get_fes</span>(&amp;<span class="hljs-keyword">mut</span> <span class="hljs-keyword">self</span>) <span class="hljs-punctuation">-&gt;</span> <span class="hljs-type">Result</span>&lt;<span class="hljs-type">Vec</span>&lt;<span class="hljs-type">u8</span>&gt;, PackerError&gt; {<br>    <span class="hljs-keyword">self</span>.<span class="hljs-title function_ invoke__">get_image_data_by_name</span>(<span class="hljs-string">"fes"</span>)<br>}<br><br><span class="hljs-comment">/// 获取 U-Boot 数据</span><br><span class="hljs-keyword">pub</span> <span class="hljs-keyword">fn</span> <span class="hljs-title function_">get_uboot</span>(&amp;<span class="hljs-keyword">mut</span> <span class="hljs-keyword">self</span>) <span class="hljs-punctuation">-&gt;</span> <span class="hljs-type">Result</span>&lt;<span class="hljs-type">Vec</span>&lt;<span class="hljs-type">u8</span>&gt;, PackerError&gt; {<br>    <span class="hljs-keyword">self</span>.<span class="hljs-title function_ invoke__">get_image_data_by_name</span>(<span class="hljs-string">"uboot"</span>)<br>}<br><br><span class="hljs-comment">/// 获取 MBR 数据</span><br><span class="hljs-keyword">pub</span> <span class="hljs-keyword">fn</span> <span class="hljs-title function_">get_mbr</span>(&amp;<span class="hljs-keyword">mut</span> <span class="hljs-keyword">self</span>) <span class="hljs-punctuation">-&gt;</span> <span class="hljs-type">Result</span>&lt;<span class="hljs-type">Vec</span>&lt;<span class="hljs-type">u8</span>&gt;, PackerError&gt; {<br>    <span class="hljs-keyword">self</span>.<span class="hljs-title function_ invoke__">get_image_data_by_name</span>(<span class="hljs-string">"mbr"</span>)<br>}<br><br><span class="hljs-comment">/// 获取 DTB 数据</span><br><span class="hljs-keyword">pub</span> <span class="hljs-keyword">fn</span> <span class="hljs-title function_">get_dtb</span>(&amp;<span class="hljs-keyword">mut</span> <span class="hljs-keyword">self</span>) <span class="hljs-punctuation">-&gt;</span> <span class="hljs-type">Result</span>&lt;<span class="hljs-type">Vec</span>&lt;<span class="hljs-type">u8</span>&gt;, PackerError&gt; {<br>    <span class="hljs-keyword">self</span>.<span class="hljs-title function_ invoke__">get_image_data_by_name</span>(<span class="hljs-string">"dtb"</span>)<br>}<br><br><span class="hljs-comment">/// 获取 sys_config 二进制数据</span><br><span class="hljs-keyword">pub</span> <span class="hljs-keyword">fn</span> <span class="hljs-title function_">get_sys_config_bin</span>(&amp;<span class="hljs-keyword">mut</span> <span class="hljs-keyword">self</span>) <span class="hljs-punctuation">-&gt;</span> <span class="hljs-type">Result</span>&lt;<span class="hljs-type">Vec</span>&lt;<span class="hljs-type">u8</span>&gt;, PackerError&gt; {<br>    <span class="hljs-keyword">self</span>.<span class="hljs-title function_ invoke__">get_image_data_by_name</span>(<span class="hljs-string">"sys_config_bin"</span>)<br>}<br></code></pre></td></tr></tbody></table></figure><hr><h2 id="预定义组件查找表" data-id="预定义组件查找表" class="notion-h"><a href="#预定义组件查找表" class="headerlink" title="预定义组件查找表"></a>预定义组件查找表</h2><h3 id="ImageDataEntry-结构" data-id="ImageDataEntry-结构" class="notion-h"><a href="#ImageDataEntry-结构" class="headerlink" title="ImageDataEntry 结构"></a>ImageDataEntry 结构</h3><figure class="highlight rust"><table><tbody><tr><td class="code"><pre><code class="hljs rust"><span class="hljs-comment">/// 预定义镜像数据条目</span><br><span class="hljs-keyword">pub</span> <span class="hljs-keyword">struct</span> <span class="hljs-title class_">ImageDataEntry</span> {<br>    <span class="hljs-keyword">pub</span> name: &amp;<span class="hljs-symbol">'static</span> <span class="hljs-type">str</span>,       <span class="hljs-comment">// 简短名称（如 "fes", "uboot"）</span><br>    <span class="hljs-keyword">pub</span> maintype: &amp;<span class="hljs-symbol">'static</span> <span class="hljs-type">str</span>,   <span class="hljs-comment">// Main Type 标识</span><br>    <span class="hljs-keyword">pub</span> subtype: &amp;<span class="hljs-symbol">'static</span> <span class="hljs-type">str</span>,    <span class="hljs-comment">// Sub Type 标识</span><br>}<br></code></pre></td></tr></tbody></table></figure><h3 id="IMAGE-DATA-TABLE-常量" data-id="IMAGE-DATA-TABLE-常量" class="notion-h"><a href="#IMAGE-DATA-TABLE-常量" class="headerlink" title="IMAGE_DATA_TABLE 常量"></a>IMAGE_DATA_TABLE 常量</h3><figure class="highlight rust"><table><tbody><tr><td class="code"><pre><code class="hljs rust"><span class="hljs-keyword">pub</span> <span class="hljs-keyword">const</span> IMAGE_DATA_TABLE: &amp;[ImageDataEntry] = &amp;[<br>    ImageDataEntry {<br>        name: <span class="hljs-string">"fes"</span>,<br>        maintype: <span class="hljs-string">"FES"</span>,<br>        subtype: <span class="hljs-string">"FES_1-0000000000"</span>,<br>    },<br>    ImageDataEntry {<br>        name: <span class="hljs-string">"uboot"</span>,<br>        maintype: <span class="hljs-string">"12345678"</span>,<br>        subtype: <span class="hljs-string">"UBOOT_0000000000"</span>,<br>    },<br>    ImageDataEntry {<br>        name: <span class="hljs-string">"mbr"</span>,<br>        maintype: <span class="hljs-string">"12345678"</span>,<br>        subtype: <span class="hljs-string">"1234567890___MBR"</span>,<br>    },<br>    ImageDataEntry {<br>        name: <span class="hljs-string">"dtb"</span>,<br>        maintype: <span class="hljs-string">"COMMON"</span>,<br>        subtype: <span class="hljs-string">"DTB_CONFIG000000"</span>,<br>    },<br>    <span class="hljs-comment">// ... 更多条目</span><br>];<br></code></pre></td></tr></tbody></table></figure><h3 id="HashMap-快速查找" data-id="HashMap-快速查找" class="notion-h"><a href="#HashMap-快速查找" class="headerlink" title="HashMap 快速查找"></a>HashMap 快速查找</h3><figure class="highlight rust"><table><tbody><tr><td class="code"><pre><code class="hljs rust"><span class="hljs-keyword">use</span> once_cell::sync::Lazy;<br><span class="hljs-keyword">use</span> std::collections::HashMap;<br><br><span class="hljs-comment">/// 延迟初始化的查找表</span><br><span class="hljs-keyword">static</span> IMAGE_ENTRY_MAP: Lazy&lt;HashMap&lt;&amp;<span class="hljs-symbol">'static</span> <span class="hljs-type">str</span>, &amp;<span class="hljs-symbol">'static</span> ImageDataEntry&gt;&gt; =<br>    Lazy::<span class="hljs-title function_ invoke__">new</span>(|| {<br>        IMAGE_DATA_TABLE<br>            .<span class="hljs-title function_ invoke__">iter</span>()<br>            .<span class="hljs-title function_ invoke__">map</span>(|entry| (entry.name, entry))<br>            .<span class="hljs-title function_ invoke__">collect</span>()<br>    });<br><br><span class="hljs-comment">/// 按名称查找条目</span><br><span class="hljs-keyword">pub</span> <span class="hljs-keyword">fn</span> <span class="hljs-title function_">get_image_data_entry</span>(name: &amp;<span class="hljs-type">str</span>) <span class="hljs-punctuation">-&gt;</span> <span class="hljs-type">Option</span>&lt;&amp;<span class="hljs-symbol">'static</span> ImageDataEntry&gt; {<br>    IMAGE_ENTRY_MAP.<span class="hljs-title function_ invoke__">get</span>(name).<span class="hljs-title function_ invoke__">copied</span>()<br>}<br></code></pre></td></tr></tbody></table></figure><hr><h2 id="Android-Sparse-格式支持" data-id="Android-Sparse-格式支持" class="notion-h"><a href="#Android-Sparse-格式支持" class="headerlink" title="Android Sparse 格式支持"></a>Android Sparse 格式支持</h2><h3 id="Sparse-Header-结构" data-id="Sparse-Header-结构" class="notion-h"><a href="#Sparse-Header-结构" class="headerlink" title="Sparse Header 结构"></a>Sparse Header 结构</h3><figure class="highlight rust"><table><tbody><tr><td class="code"><pre><code class="hljs rust"><span class="hljs-comment">/// Sparse 镜像魔数</span><br><span class="hljs-keyword">pub</span> <span class="hljs-keyword">const</span> SPARSE_HEADER_MAGIC: <span class="hljs-type">u32</span> = <span class="hljs-number">0xed26ff3a</span>;<br><br><span class="hljs-comment">/// Sparse 头结构</span><br><span class="hljs-meta">#[repr(C, packed)]</span><br><span class="hljs-meta">#[derive(Debug, Clone, Copy)]</span><br><span class="hljs-keyword">pub</span> <span class="hljs-keyword">struct</span> <span class="hljs-title class_">SparseHeader</span> {<br>    <span class="hljs-keyword">pub</span> magic: <span class="hljs-type">u32</span>,           <span class="hljs-comment">// 0xed26ff3a</span><br>    <span class="hljs-keyword">pub</span> major_version: <span class="hljs-type">u16</span>,   <span class="hljs-comment">// 主版本（必须为 1）</span><br>    <span class="hljs-keyword">pub</span> minor_version: <span class="hljs-type">u16</span>,   <span class="hljs-comment">// 次版本</span><br>    <span class="hljs-keyword">pub</span> file_hdr_sz: <span class="hljs-type">u16</span>,     <span class="hljs-comment">// 文件头大小（28）</span><br>    <span class="hljs-keyword">pub</span> chunk_hdr_sz: <span class="hljs-type">u16</span>,    <span class="hljs-comment">// 块头大小（12）</span><br>    <span class="hljs-keyword">pub</span> blk_sz: <span class="hljs-type">u32</span>,          <span class="hljs-comment">// 块大小</span><br>    <span class="hljs-keyword">pub</span> total_blks: <span class="hljs-type">u32</span>,      <span class="hljs-comment">// 总块数</span><br>    <span class="hljs-keyword">pub</span> total_chunks: <span class="hljs-type">u32</span>,    <span class="hljs-comment">// 总块数</span><br>    <span class="hljs-keyword">pub</span> image_checksum: <span class="hljs-type">u32</span>,  <span class="hljs-comment">// 校验和</span><br>}<br><br><span class="hljs-keyword">impl</span> <span class="hljs-title class_">SparseHeader</span> {<br>    <span class="hljs-comment">/// 验证头有效性</span><br>    <span class="hljs-keyword">pub</span> <span class="hljs-keyword">fn</span> <span class="hljs-title function_">is_valid</span>(&amp;<span class="hljs-keyword">self</span>) <span class="hljs-punctuation">-&gt;</span> <span class="hljs-type">bool</span> {<br>        <span class="hljs-keyword">self</span>.magic == SPARSE_HEADER_MAGIC<br>            &amp;&amp; <span class="hljs-keyword">self</span>.major_version == SPARSE_HEADER_MAJOR_VER<br>            &amp;&amp; <span class="hljs-keyword">self</span>.file_hdr_sz <span class="hljs-keyword">as</span> <span class="hljs-type">usize</span> == SPARSE_HEADER_SIZE<br>            &amp;&amp; <span class="hljs-keyword">self</span>.chunk_hdr_sz <span class="hljs-keyword">as</span> <span class="hljs-type">usize</span> == CHUNK_HEADER_SIZE<br>    }<br>}<br></code></pre></td></tr></tbody></table></figure><h3 id="Chunk-Header-结构" data-id="Chunk-Header-结构" class="notion-h"><a href="#Chunk-Header-结构" class="headerlink" title="Chunk Header 结构"></a>Chunk Header 结构</h3><figure class="highlight rust"><table><tbody><tr><td class="code"><pre><code class="hljs rust"><span class="hljs-comment">/// 块类型常量</span><br><span class="hljs-keyword">pub</span> <span class="hljs-keyword">const</span> CHUNK_TYPE_RAW: <span class="hljs-type">u16</span> = <span class="hljs-number">0xcac1</span>;      <span class="hljs-comment">// 原始数据块</span><br><span class="hljs-keyword">pub</span> <span class="hljs-keyword">const</span> CHUNK_TYPE_FILL: <span class="hljs-type">u16</span> = <span class="hljs-number">0xcac2</span>;     <span class="hljs-comment">// 填充块</span><br><span class="hljs-keyword">pub</span> <span class="hljs-keyword">const</span> CHUNK_TYPE_DONT_CARE: <span class="hljs-type">u16</span> = <span class="hljs-number">0xcac3</span>; <span class="hljs-comment">// 空块</span><br><span class="hljs-keyword">pub</span> <span class="hljs-keyword">const</span> CHUNK_TYPE_CRC32: <span class="hljs-type">u16</span> = <span class="hljs-number">0xcac4</span>;    <span class="hljs-comment">// CRC32 块</span><br><br><span class="hljs-comment">/// 块头结构</span><br><span class="hljs-meta">#[repr(C, packed)]</span><br><span class="hljs-meta">#[derive(Debug, Clone, Copy)]</span><br><span class="hljs-keyword">pub</span> <span class="hljs-keyword">struct</span> <span class="hljs-title class_">ChunkHeader</span> {<br>    <span class="hljs-keyword">pub</span> chunk_type: <span class="hljs-type">u16</span>,   <span class="hljs-comment">// 块类型</span><br>    <span class="hljs-keyword">pub</span> reserved: <span class="hljs-type">u16</span>,     <span class="hljs-comment">// 保留</span><br>    <span class="hljs-keyword">pub</span> chunk_sz: <span class="hljs-type">u32</span>,     <span class="hljs-comment">// 块大小（扇区数）</span><br>    <span class="hljs-keyword">pub</span> total_sz: <span class="hljs-type">u32</span>,     <span class="hljs-comment">// 总大小（包含头）</span><br>}<br><br><span class="hljs-keyword">impl</span> <span class="hljs-title class_">ChunkHeader</span> {<br>    <span class="hljs-comment">/// 计算数据大小</span><br>    <span class="hljs-keyword">pub</span> <span class="hljs-keyword">fn</span> <span class="hljs-title function_">data_size</span>(&amp;<span class="hljs-keyword">self</span>) <span class="hljs-punctuation">-&gt;</span> <span class="hljs-type">u32</span> {<br>        <span class="hljs-keyword">self</span>.total_sz.<span class="hljs-title function_ invoke__">saturating_sub</span>(CHUNK_HEADER_SIZE <span class="hljs-keyword">as</span> <span class="hljs-type">u32</span>)<br>    }<br>}<br></code></pre></td></tr></tbody></table></figure><h3 id="Sparse-格式检测" data-id="Sparse-格式检测" class="notion-h"><a href="#Sparse-格式检测" class="headerlink" title="Sparse 格式检测"></a>Sparse 格式检测</h3><figure class="highlight rust"><table><tbody><tr><td class="code"><pre><code class="hljs rust"><span class="hljs-comment">/// 检测是否为 Sparse 格式</span><br><span class="hljs-keyword">pub</span> <span class="hljs-keyword">fn</span> <span class="hljs-title function_">is_sparse_format</span>(data: &amp;[<span class="hljs-type">u8</span>]) <span class="hljs-punctuation">-&gt;</span> <span class="hljs-type">bool</span> {<br>    <span class="hljs-keyword">if</span> <span class="hljs-keyword">let</span> <span class="hljs-variable">Some</span>(header) = SparseHeader::<span class="hljs-title function_ invoke__">parse</span>(data) {<br>        header.<span class="hljs-title function_ invoke__">is_valid</span>()<br>    } <span class="hljs-keyword">else</span> {<br>        <span class="hljs-literal">false</span><br>    }<br>}<br><br><span class="hljs-comment">/// 解析并验证 Sparse 格式</span><br><span class="hljs-keyword">pub</span> <span class="hljs-keyword">fn</span> <span class="hljs-title function_">sparse_format_probe</span>(data: &amp;[<span class="hljs-type">u8</span>]) <span class="hljs-punctuation">-&gt;</span> crate::utils::FlashResult&lt;SparseHeader&gt; {<br>    <span class="hljs-keyword">use</span> crate::utils::FlashError;<br><br>    <span class="hljs-keyword">let</span> <span class="hljs-variable">header</span> = SparseHeader::<span class="hljs-title function_ invoke__">parse</span>(data).<span class="hljs-title function_ invoke__">ok_or_else</span>(|| {<br>        FlashError::<span class="hljs-title function_ invoke__">InvalidFirmwareFormat</span>(<br>            <span class="hljs-string">"Failed to parse sparse header: insufficient data"</span>.<span class="hljs-title function_ invoke__">to_string</span>(),<br>        )<br>    })?;<br><br>    <span class="hljs-keyword">if</span> header.magic != SPARSE_HEADER_MAGIC {<br>        <span class="hljs-keyword">return</span> <span class="hljs-title function_ invoke__">Err</span>(FlashError::<span class="hljs-title function_ invoke__">InvalidFirmwareFormat</span>(<span class="hljs-built_in">format!</span>(<br>            <span class="hljs-string">"Invalid sparse magic: expected 0x{:08x}, got 0x{:08x}"</span>,<br>            SPARSE_HEADER_MAGIC, header.magic<br>        )));<br>    }<br><br>    <span class="hljs-comment">// ... 其他验证</span><br><br>    <span class="hljs-title function_ invoke__">Ok</span>(*header)<br>}<br></code></pre></td></tr></tbody></table></figure><hr><h2 id="代码亮点" data-id="代码亮点" class="notion-h"><a href="#代码亮点" class="headerlink" title="代码亮点"></a>代码亮点</h2><h3 id="1-repr-C-packed-二进制映射" data-id="1-repr-C-packed-二进制映射" class="notion-h"><a href="#1-repr-C-packed-二进制映射" class="headerlink" title="1. #[repr(C, packed)] 二进制映射"></a>1. <code>#[repr(C, packed)]</code> 二进制映射</h3><figure class="highlight rust"><table><tbody><tr><td class="code"><pre><code class="hljs rust"><span class="hljs-meta">#[repr(C, packed)]</span><br><span class="hljs-keyword">pub</span> <span class="hljs-keyword">struct</span> <span class="hljs-title class_">ImageHeader</span> {<br>    <span class="hljs-keyword">pub</span> magic: [<span class="hljs-type">u8</span>; <span class="hljs-number">8</span>],<br>    <span class="hljs-keyword">pub</span> header_version: <span class="hljs-type">u32</span>,<br>    <span class="hljs-comment">// ...</span><br>}<br></code></pre></td></tr></tbody></table></figure><p><strong>作用：</strong></p><ul><li><code>#[repr(C)]</code> - 使用 C 语言布局规则</li><li><code>#[repr(packed)]</code> - 禁止对齐，字段紧密排列</li></ul><p><strong>嵌入式开发中的应用：</strong> 直接将结构体映射到二进制数据，避免手动偏移计算。</p><h3 id="2-Union-处理版本差异" data-id="2-Union-处理版本差异" class="notion-h"><a href="#2-Union-处理版本差异" class="headerlink" title="2. Union 处理版本差异"></a>2. Union 处理版本差异</h3><figure class="highlight rust"><table><tbody><tr><td class="code"><pre><code class="hljs rust"><span class="hljs-keyword">pub</span> <span class="hljs-keyword">union</span> <span class="hljs-title class_">ImageHeaderVersionData</span> {<br>    <span class="hljs-keyword">pub</span> v1: ImageHeaderV1,<br>    <span class="hljs-keyword">pub</span> v3: ImageHeaderV3,<br>}<br></code></pre></td></tr></tbody></table></figure><p><strong>安全访问：</strong></p><figure class="highlight rust"><table><tbody><tr><td class="code"><pre><code class="hljs rust"><span class="hljs-comment">// 使用 unsafe 块访问联合体字段</span><br><span class="hljs-keyword">pub</span> <span class="hljs-keyword">fn</span> <span class="hljs-title function_">num_files</span>(&amp;<span class="hljs-keyword">self</span>) <span class="hljs-punctuation">-&gt;</span> <span class="hljs-type">u32</span> {<br>    <span class="hljs-keyword">unsafe</span> {<br>        <span class="hljs-keyword">if</span> <span class="hljs-keyword">self</span>.header_version == <span class="hljs-number">0x0300</span> {<br>            <span class="hljs-keyword">self</span>.data.v3.num_files<br>        } <span class="hljs-keyword">else</span> {<br>            <span class="hljs-keyword">self</span>.data.v1.num_files<br>        }<br>    }<br>}<br></code></pre></td></tr></tbody></table></figure><h3 id="3-原始指针解析" data-id="3-原始指针解析" class="notion-h"><a href="#3-原始指针解析" class="headerlink" title="3. 原始指针解析"></a>3. 原始指针解析</h3><figure class="highlight rust"><table><tbody><tr><td class="code"><pre><code class="hljs rust"><span class="hljs-keyword">pub</span> <span class="hljs-keyword">fn</span> <span class="hljs-title function_">parse</span>(data: &amp;[<span class="hljs-type">u8</span>]) <span class="hljs-punctuation">-&gt;</span> <span class="hljs-type">Result</span>&lt;&amp;<span class="hljs-keyword">Self</span>, &amp;<span class="hljs-symbol">'static</span> <span class="hljs-type">str</span>&gt; {<br>    <span class="hljs-keyword">if</span> data.<span class="hljs-title function_ invoke__">len</span>() &lt; std::mem::size_of::&lt;ImageHeader&gt;() {<br>        <span class="hljs-keyword">return</span> <span class="hljs-title function_ invoke__">Err</span>(<span class="hljs-string">"Data too short for ImageHeader"</span>);<br>    }<br><br>    <span class="hljs-keyword">let</span> <span class="hljs-variable">ptr</span> = data.<span class="hljs-title function_ invoke__">as_ptr</span>() <span class="hljs-keyword">as</span> *<span class="hljs-keyword">const</span> ImageHeader;<br>    <span class="hljs-title function_ invoke__">Ok</span>(<span class="hljs-keyword">unsafe</span> { &amp;*ptr })<br>}<br></code></pre></td></tr></tbody></table></figure><p><strong>原理：</strong> 将字节数组的指针转换为结构体指针，直接引用内存。</p><h3 id="4-once-cell-Lazy-延迟初始化" data-id="4-once-cell-Lazy-延迟初始化" class="notion-h"><a href="#4-once-cell-Lazy-延迟初始化" class="headerlink" title="4. once_cell::Lazy 延迟初始化"></a>4. once_cell::Lazy 延迟初始化</h3><figure class="highlight rust"><table><tbody><tr><td class="code"><pre><code class="hljs rust"><span class="hljs-keyword">static</span> IMAGE_ENTRY_MAP: Lazy&lt;HashMap&lt;&amp;<span class="hljs-symbol">'static</span> <span class="hljs-type">str</span>, &amp;<span class="hljs-symbol">'static</span> ImageDataEntry&gt;&gt; =<br>    Lazy::<span class="hljs-title function_ invoke__">new</span>(|| {<br>        IMAGE_DATA_TABLE.<span class="hljs-title function_ invoke__">iter</span>().<span class="hljs-title function_ invoke__">map</span>(|entry| (entry.name, entry)).<span class="hljs-title function_ invoke__">collect</span>()<br>    });<br></code></pre></td></tr></tbody></table></figure><p><strong>优势：</strong></p><ul><li>首次访问时初始化，避免启动开销</li><li>线程安全</li><li>替代 <code>lazy_static!</code> 宏</li></ul><h3 id="5-分区名称到-SubType-的转换" data-id="5-分区名称到-SubType-的转换" class="notion-h"><a href="#5-分区名称到-SubType-的转换" class="headerlink" title="5. 分区名称到 SubType 的转换"></a>5. 分区名称到 SubType 的转换</h3><figure class="highlight rust"><table><tbody><tr><td class="code"><pre><code class="hljs rust"><span class="hljs-keyword">pub</span> <span class="hljs-keyword">fn</span> <span class="hljs-title function_">build_subtype_by_filename</span>(&amp;<span class="hljs-keyword">self</span>, partition_name: &amp;<span class="hljs-type">str</span>) <span class="hljs-punctuation">-&gt;</span> <span class="hljs-type">String</span> {<br>    <span class="hljs-keyword">let</span> <span class="hljs-variable">suffix</span> = <span class="hljs-built_in">format!</span>(<br>        <span class="hljs-string">"{}{}"</span>,<br>        partition_name.<span class="hljs-title function_ invoke__">to_uppercase</span>().<span class="hljs-title function_ invoke__">replace</span>(<span class="hljs-string">'.'</span>, <span class="hljs-string">"_"</span>),<br>        PARTITION_DOWNLOADFILE_SUFFIX  <span class="hljs-comment">// "0000000000"</span><br>    );<br>    <span class="hljs-keyword">if</span> suffix.<span class="hljs-title function_ invoke__">len</span>() &gt;= <span class="hljs-number">16</span> {<br>        suffix[..<span class="hljs-number">16</span>].<span class="hljs-title function_ invoke__">to_string</span>()<br>    } <span class="hljs-keyword">else</span> {<br>        <span class="hljs-built_in">format!</span>(<span class="hljs-string">"{:0&lt;16}"</span>, suffix)  <span class="hljs-comment">// 左填充到 16 字符</span><br>    }<br>}<br><br><span class="hljs-comment">// 示例：</span><br><span class="hljs-comment">// "boot" -&gt; "BOOT0000000000"</span><br><span class="hljs-comment">// "rootfs.data" -&gt; "ROOTFS_DATA00"</span><br></code></pre></td></tr></tbody></table></figure><hr><h2 id="调用流程分析" data-id="调用流程分析" class="notion-h"><a href="#调用流程分析" class="headerlink" title="调用流程分析"></a>调用流程分析</h2><h3 id="固件加载流程" data-id="固件加载流程" class="notion-h"><a href="#固件加载流程" class="headerlink" title="固件加载流程"></a>固件加载流程</h3><figure class="highlight rust"><table><tbody><tr><td class="code"><pre><code class="hljs rust"><span class="hljs-comment">// 在 flash 命令中的使用</span><br><span class="hljs-keyword">use</span> crate::firmware::OpenixPacker;<br><br><span class="hljs-keyword">fn</span> <span class="hljs-title function_">load_and_parse_firmware</span>(path: &amp;Path) <span class="hljs-punctuation">-&gt;</span> <span class="hljs-type">Result</span>&lt;ImageInfo, FlashError&gt; {<br>    <span class="hljs-comment">// 1. 创建 Packer</span><br>    <span class="hljs-keyword">let</span> <span class="hljs-keyword">mut </span><span class="hljs-variable">packer</span> = OpenixPacker::<span class="hljs-title function_ invoke__">new</span>();<br><br>    <span class="hljs-comment">// 2. 加载固件文件</span><br>    packer.<span class="hljs-title function_ invoke__">load</span>(path)?;<br><br>    <span class="hljs-comment">// 3. 获取镜像信息</span><br>    <span class="hljs-keyword">let</span> <span class="hljs-variable">info</span> = packer.<span class="hljs-title function_ invoke__">get_image_info</span>();<br><br>    <span class="hljs-comment">// 4. 检查加密状态</span><br>    <span class="hljs-keyword">if</span> info.is_encrypted {<br>        <span class="hljs-keyword">return</span> <span class="hljs-title function_ invoke__">Err</span>(FlashError::EncryptedNotSupported);<br>    }<br><br>    <span class="hljs-title function_ invoke__">Ok</span>(info)<br>}<br></code></pre></td></tr></tbody></table></figure><h3 id="组件提取流程" data-id="组件提取流程" class="notion-h"><a href="#组件提取流程" class="headerlink" title="组件提取流程"></a>组件提取流程</h3><figure class="highlight rust"><table><tbody><tr><td class="code"><pre><code class="hljs rust"><span class="hljs-comment">// 提取各组件数据</span><br><span class="hljs-keyword">fn</span> <span class="hljs-title function_">extract_components</span>(packer: &amp;<span class="hljs-keyword">mut</span> OpenixPacker) <span class="hljs-punctuation">-&gt;</span> <span class="hljs-type">Result</span>&lt;Components, FlashError&gt; {<br>    <span class="hljs-comment">// FES - DRAM 初始化脚本</span><br>    <span class="hljs-keyword">let</span> <span class="hljs-variable">fes</span> = packer.<span class="hljs-title function_ invoke__">get_fes</span>()?;<br><br>    <span class="hljs-comment">// U-Boot - 主 bootloader</span><br>    <span class="hljs-keyword">let</span> <span class="hljs-variable">uboot</span> = packer.<span class="hljs-title function_ invoke__">get_uboot</span>()?;<br><br>    <span class="hljs-comment">// DTB - 设备树</span><br>    <span class="hljs-keyword">let</span> <span class="hljs-variable">dtb</span> = packer.<span class="hljs-title function_ invoke__">get_dtb</span>()?;<br><br>    <span class="hljs-comment">// sys_config_bin - 系统配置</span><br>    <span class="hljs-keyword">let</span> <span class="hljs-variable">sys_config</span> = packer.<span class="hljs-title function_ invoke__">get_sys_config_bin</span>()?;<br><br>    <span class="hljs-comment">// MBR - 分区表</span><br>    <span class="hljs-keyword">let</span> <span class="hljs-variable">mbr</span> = packer.<span class="hljs-title function_ invoke__">get_mbr</span>()?;<br><br>    <span class="hljs-title function_ invoke__">Ok</span>(Components {<br>        fes,<br>        uboot,<br>        dtb,<br>        sys_config,<br>        mbr,<br>    })<br>}<br></code></pre></td></tr></tbody></table></figure><h3 id="分区数据提取流程" data-id="分区数据提取流程" class="notion-h"><a href="#分区数据提取流程" class="headerlink" title="分区数据提取流程"></a>分区数据提取流程</h3><figure class="highlight rust"><table><tbody><tr><td class="code"><pre><code class="hljs rust"><span class="hljs-comment">// 提取分区数据</span><br><span class="hljs-keyword">fn</span> <span class="hljs-title function_">extract_partition</span>(packer: &amp;<span class="hljs-keyword">mut</span> OpenixPacker, partition_name: &amp;<span class="hljs-type">str</span>) <span class="hljs-punctuation">-&gt;</span> <span class="hljs-type">Result</span>&lt;<span class="hljs-type">Vec</span>&lt;<span class="hljs-type">u8</span>&gt;, FlashError&gt; {<br>    <span class="hljs-comment">// 1. 构建 SubType</span><br>    <span class="hljs-keyword">let</span> <span class="hljs-variable">subtype</span> = packer.<span class="hljs-title function_ invoke__">build_subtype_by_filename</span>(partition_name);<br><br>    <span class="hljs-comment">// 2. 查找文件头</span><br>    <span class="hljs-keyword">let</span> <span class="hljs-variable">file_header</span> = packer.<span class="hljs-title function_ invoke__">get_file_header_by_maintype_subtype</span>(<span class="hljs-string">"12345678"</span>, &amp;subtype)<br>        .<span class="hljs-title function_ invoke__">ok_or_else</span>(|| FlashError::<span class="hljs-title function_ invoke__">PartitionDownloadFailed</span>(partition_name.<span class="hljs-title function_ invoke__">to_string</span>()))?;<br><br>    <span class="hljs-comment">// 3. 读取数据</span><br>    <span class="hljs-keyword">let</span> <span class="hljs-variable">header_version</span> = packer.<span class="hljs-title function_ invoke__">get_header_version</span>();<br>    packer.<span class="hljs-title function_ invoke__">read_data_at_offset</span>(<br>        file_header.<span class="hljs-title function_ invoke__">offset</span>(header_version),<br>        file_header.<span class="hljs-title function_ invoke__">original_length</span>(header_version),<br>    ).<span class="hljs-title function_ invoke__">map_err</span>(|e| FlashError::<span class="hljs-title function_ invoke__">PartitionDownloadFailed</span>(e.<span class="hljs-title function_ invoke__">to_string</span>()))<br>}<br></code></pre></td></tr></tbody></table></figure><hr><h2 id="实践示例" data-id="实践示例" class="notion-h"><a href="#实践示例" class="headerlink" title="实践示例"></a>实践示例</h2><h3 id="解析固件文件" data-id="解析固件文件" class="notion-h"><a href="#解析固件文件" class="headerlink" title="解析固件文件"></a>解析固件文件</h3><figure class="highlight rust"><table><tbody><tr><td class="code"><pre><code class="hljs rust"><span class="hljs-keyword">use</span> openixcli::firmware::{OpenixPacker, ImageInfo};<br><br><span class="hljs-keyword">fn</span> <span class="hljs-title function_">main</span>() <span class="hljs-punctuation">-&gt;</span> <span class="hljs-type">Result</span>&lt;(), <span class="hljs-type">Box</span>&lt;<span class="hljs-keyword">dyn</span> std::error::Error&gt;&gt; {<br>    <span class="hljs-keyword">let</span> <span class="hljs-keyword">mut </span><span class="hljs-variable">packer</span> = OpenixPacker::<span class="hljs-title function_ invoke__">new</span>();<br><br>    <span class="hljs-comment">// 加载固件</span><br>    packer.<span class="hljs-title function_ invoke__">load</span>(<span class="hljs-string">"firmware.fex"</span>)?;<br><br>    <span class="hljs-comment">// 获取基本信息</span><br>    <span class="hljs-keyword">let</span> <span class="hljs-variable">info</span> = packer.<span class="hljs-title function_ invoke__">get_image_info</span>();<br>    <span class="hljs-built_in">println!</span>(<span class="hljs-string">"固件大小: {} bytes"</span>, info.image_size);<br>    <span class="hljs-built_in">println!</span>(<span class="hljs-string">"文件数量: {}"</span>, info.num_files);<br>    <span class="hljs-built_in">println!</span>(<span class="hljs-string">"加密状态: {}"</span>, info.is_encrypted);<br><br>    <span class="hljs-comment">// 列出所有文件</span><br>    <span class="hljs-keyword">for</span> <span class="hljs-variable">file</span> <span class="hljs-keyword">in</span> &amp;info.files {<br>        <span class="hljs-built_in">println!</span>(<br>            <span class="hljs-string">"  {} ({}/{}): {} bytes at offset 0x{:08x}"</span>,<br>            file.filename,<br>            file.maintype,<br>            file.subtype,<br>            file.original_length,<br>            file.offset<br>        );<br>    }<br><br>    <span class="hljs-comment">// 提取 U-Boot</span><br>    <span class="hljs-keyword">let</span> <span class="hljs-variable">uboot</span> = packer.<span class="hljs-title function_ invoke__">get_uboot</span>()?;<br>    <span class="hljs-built_in">println!</span>(<span class="hljs-string">"U-Boot 大小: {} bytes"</span>, uboot.<span class="hljs-title function_ invoke__">len</span>());<br><br>    <span class="hljs-title function_ invoke__">Ok</span>(())<br>}<br></code></pre></td></tr></tbody></table></figure><h3 id="输出示例" data-id="输出示例" class="notion-h"><a href="#输出示例" class="headerlink" title="输出示例"></a>输出示例</h3><figure class="highlight plaintext"><table><tbody><tr><td class="code"><pre><code class="hljs plaintext">固件大小: 16777216 bytes<br>文件数量: 12<br>加密状态: false<br><br>  fes.fex (FES/FES_1-0000000000): 8192 bytes at offset 0x00003000<br>  u-boot.bin (12345678/UBOOT_0000000000): 524288 bytes at offset 0x00005000<br>  sunxi.fex (COMMON/SYS_CONFIG100000): 1024 bytes at offset 0x00085000<br>  ...<br>U-Boot 大小: 524288 bytes<br></code></pre></td></tr></tbody></table></figure><hr><h2 id="数据结构关系图" data-id="数据结构关系图" class="notion-h"><a href="#数据结构关系图" class="headerlink" title="数据结构关系图"></a>数据结构关系图</h2><pre class="mermaid">classDiagram    class OpenixPacker {        +file: Option~File~        +image_header: Option~ImageHeader~        +file_headers: Vec~FileHeader~        +is_encrypted: bool        +image_loaded: bool        +load(path)        +get_image_info() ImageInfo        +get_fes() Vec~u8~        +get_uboot() Vec~u8~        +get_mbr() Vec~u8~        +get_file_data_by_filename() Vec~u8~        +get_file_data_by_maintype_subtype() Vec~u8~    }    class ImageHeader {        +magic: [u8; 8]        +header_version: u32        +header_size: u32        +ram_base: u32        +version: u32        +image_size: u32        +data: ImageHeaderVersionData        +num_files() u32        +parse(data)    }    class ImageHeaderVersionData {        +v1: ImageHeaderV1        +v3: ImageHeaderV3    }    class FileHeader {        +filename_len: u32        +total_header_size: u32        +maintype: [u8; 8]        +subtype: [u8; 16]        +data: FileHeaderVersionData        +stored_length() u32        +original_length() u32        +offset() u32        +filename_str() String    }    class ImageInfo {        +header: ImageHeader        +files: Vec~FileInfo~        +is_encrypted: bool        +image_size: u32        +num_files: u32    }    class FileInfo {        +filename: String        +maintype: String        +subtype: String        +stored_length: u32        +original_length: u32        +offset: u32    }    OpenixPacker --&gt; ImageHeader    OpenixPacker --&gt; FileHeader    OpenixPacker --&gt; ImageInfo    ImageHeader --&gt; ImageHeaderVersionData    FileHeader --&gt; FileHeaderVersionData    ImageInfo --&gt; FileInfo    ImageInfo --&gt; ImageHeader</pre><hr><hr>]]>
    </content>
    <id>https://gloomyghost.com/posts/openixcli-firmware-module/</id>
    <link href="https://gloomyghost.com/posts/openixcli-firmware-module/"/>
    <published>2026-04-25T16:00:00.000Z</published>
    <summary>
      <![CDATA[<h2 id="概述" data-id="概述" class="notion-h"><a href="#概述" class="headerlink" title="概述"></a>概述</h2><p>固件解析是嵌入式刷写工具的核心功能。全志（Allwinner）芯片使用一种名为]]>
    </summary>
    <title>OpenixCLI Firmware 模块深度解析：IMAGEWTY 固件格式解析</title>
    <updated>2026-05-29T20:33:31.219Z</updated>
  </entry>
  <entry>
    <author>
      <name>柚木 鉉</name>
    </author>
    <category term="UTAU" scheme="https://gloomyghost.com/tags/UTAU/"/>
    <category term="lessampler" scheme="https://gloomyghost.com/tags/lessampler/"/>
    <content>
      <![CDATA[<p>Utils 模块提供 lessampler 的基础设施工具类，包括分级日志系统、高性能计时器、数组转换工具和自定义异常体系。这些工具类为整个项目提供一致的基础功能支持，确保代码的健壮性和可调试性。</p><p>良好的工具类设计是软件工程质量的重要体现。Utils 模块的设计遵循以下原则：</p><ul><li><strong>最小依赖</strong>：尽量使用标准库，减少外部依赖</li><li><strong>线程安全</strong>：支持多线程环境使用</li><li><strong>条件编译</strong>：通过宏开关控制调试功能</li><li><strong>语义清晰</strong>：类名和接口设计直观易懂</li></ul><h2 id="模块结构" data-id="模块结构" class="notion-h"><a href="#模块结构" class="headerlink" title="模块结构"></a>模块结构</h2><figure class="highlight plaintext"><table><tbody><tr><td class="code"><pre><code class="hljs plaintext">Utils/<br>├── LOG.h           # 分级日志系统<br>├── Timer.h         # 微秒精度计时器<br>├── VectorWrapper.h # 数组转向量模板<br>└── exception.h     # 自定义异常体系<br></code></pre></td></tr></tbody></table></figure><hr><h2 id="LOG-h-分级日志系统" data-id="LOG-h-分级日志系统" class="notion-h"><a href="#LOG-h-分级日志系统" class="headerlink" title="LOG.h 分级日志系统"></a>LOG.h 分级日志系统</h2><p>YALL（Yet Another Logging Library）是一个分级、彩色、线程安全的日志系统。</p><h3 id="日志级别定义" data-id="日志级别定义" class="notion-h"><a href="#日志级别定义" class="headerlink" title="日志级别定义"></a>日志级别定义</h3><figure class="highlight cpp"><table><tbody><tr><td class="code"><pre><code class="hljs cpp"><span class="hljs-keyword">enum</span> <span class="hljs-title class_">Yall_LEVEL</span> {<br>    LOG_DUMP,      <span class="hljs-comment">// 数据转存（调试专用）</span><br>    LOG_EVAL,      <span class="hljs-comment">// 性能评估（计时专用）</span><br>    LOG_DEBUG,     <span class="hljs-comment">// 调试信息（包含源码位置）</span><br>    LOG_OK,        <span class="hljs-comment">// 成功状态（绿色）</span><br>    LOG_INFO,      <span class="hljs-comment">// 信息提示（蓝色）</span><br>    LOG_WARN,      <span class="hljs-comment">// 警告信息（黄色）</span><br>    LOG_ERROR,     <span class="hljs-comment">// 错误信息（红色）</span><br>    LOG_CRITICAL,  <span class="hljs-comment">// 严重错误（红色背景）</span><br>};<br></code></pre></td></tr></tbody></table></figure><p><strong>级别设计理念</strong>：</p><table><thead><tr><th>级别</th><th>用途</th><th>条件编译</th><th>输出颜色</th></tr></thead><tbody><tr><td>DUMP</td><td>详细数据输出</td><td>DUMP_DATA 宏</td><td>白色 + 源码位置</td></tr><tr><td>EVAL</td><td>性能计时输出</td><td>TIME_EVAL 宏</td><td>紫色</td></tr><tr><td>DEBUG</td><td>调试追踪</td><td>DEBUG_MODE 宏</td><td>白色 + 源码位置</td></tr><tr><td>OK</td><td>操作成功</td><td>常开</td><td>绿色</td></tr><tr><td>INFO</td><td>信息提示</td><td>常开</td><td>蓝色</td></tr><tr><td>WARN</td><td>警告信息</td><td>常开</td><td>黄色</td></tr><tr><td>ERROR</td><td>错误报告</td><td>常开</td><td>红色</td></tr><tr><td>CRITICAL</td><td>严重错误</td><td>常开</td><td>红色背景</td></tr></tbody></table><h3 id="Yall-Inst-基类" data-id="Yall-Inst-基类" class="notion-h"><a href="#Yall-Inst-基类" class="headerlink" title="Yall_Inst 基类"></a>Yall_Inst 基类</h3><figure class="highlight cpp"><table><tbody><tr><td class="code"><pre><code class="hljs cpp"><span class="hljs-keyword">class</span> <span class="hljs-title class_">Yall_Inst</span> {<br><span class="hljs-keyword">public</span>:<br>    <span class="hljs-function"><span class="hljs-keyword">explicit</span> <span class="hljs-title">Yall_Inst</span><span class="hljs-params">(Yall_LEVEL logLevel)</span> </span>{<br>        <span class="hljs-keyword">this</span>-&gt;logLevel = logLevel;<br>    };<br><br>    <span class="hljs-keyword">virtual</span> <span class="hljs-type">void</span> <span class="hljs-keyword">operator</span>&lt;&lt;(<span class="hljs-type">const</span> std::string &amp;msg) {};<br><br><span class="hljs-keyword">protected</span>:<br>    std::string name;<br>    Yall_LEVEL logLevel;<br>    std::mutex streamMtx;  <span class="hljs-comment">// 线程安全互斥锁</span><br>};<br></code></pre></td></tr></tbody></table></figure><p><strong>设计要点</strong>：</p><ul><li>使用 <code>std::mutex</code> 确保多线程环境下的输出顺序</li><li>基类定义接口，派生类实现具体行为</li></ul><h3 id="Yall-Instance-标准日志器" data-id="Yall-Instance-标准日志器" class="notion-h"><a href="#Yall-Instance-标准日志器" class="headerlink" title="Yall_Instance 标准日志器"></a>Yall_Instance 标准日志器</h3><figure class="highlight cpp"><table><tbody><tr><td class="code"><pre><code class="hljs cpp"><span class="hljs-keyword">class</span> <span class="hljs-title class_">Yall_Instance</span> : Yall_Inst {<br><span class="hljs-keyword">public</span>:<br>    <span class="hljs-function"><span class="hljs-keyword">explicit</span> <span class="hljs-title">Yall_Instance</span><span class="hljs-params">(Yall_LEVEL logLevel)</span> : Yall_Inst(logLevel) {</span>};<br><br>    <span class="hljs-type">void</span> <span class="hljs-keyword">operator</span>&lt;&lt;(<span class="hljs-type">const</span> std::string &amp;msg) <span class="hljs-keyword">override</span> {<br>        <span class="hljs-function">std::lock_guard&lt;std::mutex&gt; <span class="hljs-title">lock</span><span class="hljs-params">(streamMtx)</span></span>;<br><br>        <span class="hljs-keyword">switch</span> (logLevel) {<br>            <span class="hljs-keyword">case</span> Yall_LEVEL::LOG_OK:<br>                std::cout &lt;&lt; cc::green &lt;&lt; <span class="hljs-string">"[OKAY]"</span> &lt;&lt; cc::reset;<br>                <span class="hljs-keyword">break</span>;<br>            <span class="hljs-keyword">case</span> Yall_LEVEL::LOG_INFO:<br>                std::cout &lt;&lt; cc::cyan &lt;&lt; <span class="hljs-string">"[INFO]"</span> &lt;&lt; cc::reset;<br>                <span class="hljs-keyword">break</span>;<br>            <span class="hljs-keyword">case</span> Yall_LEVEL::LOG_WARN:<br>                std::cout &lt;&lt; cc::yellow &lt;&lt; <span class="hljs-string">"[WARN]"</span> &lt;&lt; cc::reset;<br>                <span class="hljs-keyword">break</span>;<br>            <span class="hljs-keyword">case</span> Yall_LEVEL::LOG_ERROR:<br>                std::cout &lt;&lt; cc::red &lt;&lt; <span class="hljs-string">"[ERRO]"</span> &lt;&lt; cc::reset;<br>                <span class="hljs-keyword">break</span>;<br>            <span class="hljs-keyword">case</span> Yall_LEVEL::LOG_CRITICAL:<br>                std::cout &lt;&lt; cc::on_red &lt;&lt; <span class="hljs-string">"[CRIT]"</span> &lt;&lt; cc::reset;<br>                <span class="hljs-keyword">break</span>;<br><span class="hljs-meta">#<span class="hljs-keyword">if</span> TIME_EVAL</span><br>            <span class="hljs-keyword">case</span> Yall_LEVEL::LOG_EVAL:<br>                std::cout &lt;&lt; cc::magenta &lt;&lt; <span class="hljs-string">"[TIME]"</span> &lt;&lt; cc::reset;<br>                <span class="hljs-keyword">break</span>;<br><span class="hljs-meta">#<span class="hljs-keyword">endif</span></span><br>            <span class="hljs-keyword">default</span>:<br>                <span class="hljs-keyword">break</span>;<br>        }<br>        std::cout &lt;&lt; <span class="hljs-string">" "</span> &lt;&lt; msg &lt;&lt; <span class="hljs-string">" "</span> &lt;&lt; std::endl;<br>    };<br>};<br></code></pre></td></tr></tbody></table></figure><p><strong>ColorCout 库集成</strong>：</p><ul><li><code>cc::green</code>, <code>cc::cyan</code>, <code>cc::yellow</code> 等：设置文本颜色</li><li><code>cc::on_red</code>：设置背景色</li><li><code>cc::reset</code>：重置颜色</li></ul><h3 id="Yall-Debug-Instance-调试日志器" data-id="Yall-Debug-Instance-调试日志器" class="notion-h"><a href="#Yall-Debug-Instance-调试日志器" class="headerlink" title="Yall_Debug_Instance 调试日志器"></a>Yall_Debug_Instance 调试日志器</h3><figure class="highlight cpp"><table><tbody><tr><td class="code"><pre><code class="hljs cpp"><span class="hljs-keyword">class</span> <span class="hljs-title class_">Yall_Debug_Instance</span> : Yall_Inst {<br><span class="hljs-keyword">public</span>:<br>    <span class="hljs-function"><span class="hljs-keyword">explicit</span> <span class="hljs-title">Yall_Debug_Instance</span><span class="hljs-params">(Yall_LEVEL logLevel)</span> : Yall_Inst(logLevel) {</span><br>        enable = <span class="hljs-literal">false</span>;<br>    }<br><br>    <span class="hljs-function"><span class="hljs-type">void</span> <span class="hljs-title">SetDebugInfo</span><span class="hljs-params">(<span class="hljs-type">const</span> std::string &amp;file, <span class="hljs-type">const</span> std::string &amp;func, <span class="hljs-type">int</span> line)</span> </span>{<br>        <span class="hljs-keyword">this</span>-&gt;FILE = file;<br>        <span class="hljs-keyword">this</span>-&gt;FUNC = func;<br>        <span class="hljs-keyword">this</span>-&gt;LINE = line;<br>    }<br><br>    <span class="hljs-function"><span class="hljs-type">void</span> <span class="hljs-title">EnableDebug</span><span class="hljs-params">()</span> </span>{ enable = <span class="hljs-literal">true</span>; }<br>    <span class="hljs-function"><span class="hljs-type">void</span> <span class="hljs-title">DisableDebug</span><span class="hljs-params">()</span> </span>{ enable = <span class="hljs-literal">false</span>; }<br><br>    <span class="hljs-type">void</span> <span class="hljs-keyword">operator</span>&lt;&lt;(<span class="hljs-type">const</span> std::string &amp;msg) <span class="hljs-keyword">override</span> {<br>        <span class="hljs-function">std::lock_guard&lt;std::mutex&gt; <span class="hljs-title">lock</span><span class="hljs-params">(streamMtx)</span></span>;<br><br>        <span class="hljs-keyword">switch</span> (logLevel) {<br><span class="hljs-meta">#<span class="hljs-keyword">if</span> DUMP_DATA</span><br>            <span class="hljs-keyword">case</span> LOG_DUMP:<br>                std::cout &lt;&lt; cc::cyan &lt;&lt; <span class="hljs-string">"[FUNC] "</span> &lt;&lt; std::left &lt;&lt; std::<span class="hljs-built_in">setw</span>(<span class="hljs-number">23</span>) &lt;&lt; cc::reset &lt;&lt; <span class="hljs-built_in">fmt</span>(<span class="hljs-keyword">this</span>-&gt;FUNC) &lt;&lt; <span class="hljs-string">" "</span><br>                       &lt;&lt; cc::yellow &lt;&lt; <span class="hljs-string">"[FILE] "</span> &lt;&lt; std::<span class="hljs-built_in">setw</span>(<span class="hljs-number">23</span>) &lt;&lt; cc::reset &lt;&lt; <span class="hljs-built_in">fmt</span>(<span class="hljs-keyword">this</span>-&gt;FILE) &lt;&lt; <span class="hljs-string">" "</span><br>                       &lt;&lt; cc::green &lt;&lt; <span class="hljs-string">"[LINE] "</span> &lt;&lt; std::<span class="hljs-built_in">setw</span>(<span class="hljs-number">4</span>) &lt;&lt; cc::reset &lt;&lt; <span class="hljs-keyword">this</span>-&gt;LINE &lt;&lt; <span class="hljs-string">" "</span><br>                       &lt;&lt; cc::white &lt;&lt; <span class="hljs-string">"[DUMP] "</span> &lt;&lt; cc::reset &lt;&lt; msg &lt;&lt; <span class="hljs-string">" "</span> &lt;&lt; std::endl;<br>                <span class="hljs-keyword">break</span>;<br><span class="hljs-meta">#<span class="hljs-keyword">endif</span></span><br>            <span class="hljs-keyword">case</span> LOG_DEBUG:<br>                std::cout &lt;&lt; cc::cyan &lt;&lt; <span class="hljs-string">"[FUNC] "</span> &lt;&lt; std::left &lt;&lt; std::<span class="hljs-built_in">setw</span>(<span class="hljs-number">23</span>) &lt;&lt; cc::reset &lt;&lt; <span class="hljs-built_in">fmt</span>(<span class="hljs-keyword">this</span>-&gt;FUNC) &lt;&lt; <span class="hljs-string">" "</span><br>                       &lt;&lt; cc::yellow &lt;&lt; <span class="hljs-string">"[FILE] "</span> &lt;&lt; std::<span class="hljs-built_in">setw</span>(<span class="hljs-number">23</span>) &lt;&lt; cc::reset &lt;&lt; <span class="hljs-built_in">fmt</span>(<span class="hljs-keyword">this</span>-&gt;FILE) &lt;&lt; <span class="hljs-string">" "</span><br>                       &lt;&lt; cc::green &lt;&lt; <span class="hljs-string">"[LINE] "</span> &lt;&lt; std::<span class="hljs-built_in">setw</span>(<span class="hljs-number">4</span>) &lt;&lt; cc::reset &lt;&lt; <span class="hljs-keyword">this</span>-&gt;LINE &lt;&lt; <span class="hljs-string">" "</span><br>                       &lt;&lt; cc::white &lt;&lt; <span class="hljs-string">"[DEBUG] "</span> &lt;&lt; cc::reset &lt;&lt; msg &lt;&lt; <span class="hljs-string">" "</span> &lt;&lt; std::endl;<br>                <span class="hljs-keyword">break</span>;<br>            <span class="hljs-keyword">default</span>:<br>                <span class="hljs-keyword">break</span>;<br>        }<br>    }<br><br><span class="hljs-keyword">private</span>:<br>    std::string FILE = {};<br>    std::string FUNC = {};<br>    <span class="hljs-type">int</span> LINE = {};<br>    <span class="hljs-type">bool</span> enable = <span class="hljs-literal">false</span>;<br><br>    <span class="hljs-comment">// 路径截断：只保留最后 20 字符</span><br>    <span class="hljs-function"><span class="hljs-type">static</span> std::string <span class="hljs-title">fmt</span><span class="hljs-params">(std::string sv)</span> </span>{<br>        <span class="hljs-keyword">if</span> (sv.<span class="hljs-built_in">length</span>() &gt; <span class="hljs-number">20</span>) {<br>            <span class="hljs-keyword">return</span> std::<span class="hljs-built_in">string</span>(<span class="hljs-string">"..."</span>) + sv.<span class="hljs-built_in">substr</span>(sv.<span class="hljs-built_in">length</span>() - <span class="hljs-number">20</span>, sv.<span class="hljs-built_in">length</span>());<br>        } <span class="hljs-keyword">else</span> {<br>            <span class="hljs-keyword">return</span> sv;<br>        }<br>    }<br>};<br></code></pre></td></tr></tbody></table></figure><p><strong>调试输出格式</strong>：</p><figure class="highlight plaintext"><table><tbody><tr><td class="code"><pre><code class="hljs plaintext">[FUNC] ...WorldModule::F0Estim [FILE] ...orldModule/WorldModule.cpp [LINE]  123 [DEBUG] Estimating F0...<br></code></pre></td></tr></tbody></table></figure><h3 id="Yall-单例管理器" data-id="Yall-单例管理器" class="notion-h"><a href="#Yall-单例管理器" class="headerlink" title="Yall 单例管理器"></a>Yall 单例管理器</h3><figure class="highlight cpp"><table><tbody><tr><td class="code"><pre><code class="hljs cpp"><span class="hljs-keyword">class</span> <span class="hljs-title class_">Yall</span> {<br><span class="hljs-keyword">public</span>:<br>    <span class="hljs-built_in">Yall</span>(Yall <span class="hljs-type">const</span> &amp;) = <span class="hljs-keyword">delete</span>;  <span class="hljs-comment">// 禁止拷贝</span><br>    <span class="hljs-type">void</span> <span class="hljs-keyword">operator</span>=(Yall <span class="hljs-type">const</span> &amp;) = <span class="hljs-keyword">delete</span>;  <span class="hljs-comment">// 禁止赋值</span><br><br>    <span class="hljs-function"><span class="hljs-type">static</span> Yall_Instance &amp;<span class="hljs-title">GetYall</span><span class="hljs-params">(Yall_LEVEL logLevel)</span> </span>{<br>        <span class="hljs-keyword">auto</span> it = <span class="hljs-built_in">GetInstance</span>().yall_inst.<span class="hljs-built_in">find</span>(logLevel);<br>        <span class="hljs-keyword">if</span> (it == <span class="hljs-built_in">GetInstance</span>().yall_inst.<span class="hljs-built_in">end</span>()) {<br>            <span class="hljs-keyword">auto</span> *logger = <span class="hljs-keyword">new</span> <span class="hljs-built_in">Yall_Instance</span>(logLevel);<br>            <span class="hljs-built_in">GetInstance</span>().yall_inst[logLevel] = logger;<br>            <span class="hljs-keyword">return</span> *logger;<br>        }<br>        <span class="hljs-keyword">return</span> *it-&gt;second;<br>    };<br><br>    <span class="hljs-function"><span class="hljs-type">static</span> Yall_Debug_Instance &amp;<span class="hljs-title">GetDebugYall</span><span class="hljs-params">(Yall_LEVEL logLevel,</span></span><br><span class="hljs-params"><span class="hljs-function">        <span class="hljs-type">const</span> std::string &amp;FILE, <span class="hljs-type">const</span> std::string &amp;FUNC, <span class="hljs-type">int</span> LINE)</span> </span>{<br>        <span class="hljs-keyword">auto</span> it = <span class="hljs-built_in">GetDebugInstance</span>().yall_debug_inst.<span class="hljs-built_in">find</span>(logLevel);<br>        <span class="hljs-keyword">if</span> (it == <span class="hljs-built_in">GetDebugInstance</span>().yall_debug_inst.<span class="hljs-built_in">end</span>()) {<br>            <span class="hljs-keyword">auto</span> *logger = <span class="hljs-keyword">new</span> <span class="hljs-built_in">Yall_Debug_Instance</span>(logLevel);<br>            <span class="hljs-built_in">GetDebugInstance</span>().yall_debug_inst[logLevel] = logger;<br>            logger-&gt;<span class="hljs-built_in">SetDebugInfo</span>(FILE, FUNC, LINE);<br>            <span class="hljs-keyword">return</span> *logger;<br>        }<br>        it-&gt;second-&gt;<span class="hljs-built_in">SetDebugInfo</span>(FILE, FUNC, LINE);<br>        <span class="hljs-keyword">return</span> *it-&gt;second;<br>    };<br><br><span class="hljs-keyword">private</span>:<br>    std::unordered_map&lt;Yall_LEVEL, Yall_Instance *&gt; yall_inst;<br>    std::unordered_map&lt;Yall_LEVEL, Yall_Debug_Instance *&gt; yall_debug_inst;<br><br>    <span class="hljs-built_in">Yall</span>() = <span class="hljs-keyword">default</span>;<br><br>    <span class="hljs-function"><span class="hljs-type">static</span> Yall &amp;<span class="hljs-title">GetInstance</span><span class="hljs-params">()</span> </span>{<br>        <span class="hljs-type">static</span> Yall inst;<br>        <span class="hljs-keyword">return</span> inst;<br>    };<br><br>    <span class="hljs-function"><span class="hljs-type">static</span> Yall &amp;<span class="hljs-title">GetDebugInstance</span><span class="hljs-params">()</span> </span>{<br>        <span class="hljs-type">static</span> Yall inst_d;<br>        <span class="hljs-keyword">return</span> inst_d;<br>    };<br>};<br></code></pre></td></tr></tbody></table></figure><p><strong>单例模式特点</strong>：</p><ul><li>使用静态局部变量实现 Meyers 单例</li><li><code>unordered_map</code> 存储各级别的日志器实例</li><li>避免每次日志调用都创建新对象</li></ul><h3 id="宏接口定义" data-id="宏接口定义" class="notion-h"><a href="#宏接口定义" class="headerlink" title="宏接口定义"></a>宏接口定义</h3><figure class="highlight cpp"><table><tbody><tr><td class="code"><pre><code class="hljs cpp"><span class="hljs-comment">// 函数名宏：GCC 使用 __PRETTY_FUNCTION__，其他使用 __func__</span><br><span class="hljs-meta">#<span class="hljs-keyword">if</span> __GNUC__</span><br><span class="hljs-meta">#<span class="hljs-keyword">define</span> YALL_FUNC_        __PRETTY_FUNCTION__</span><br><span class="hljs-meta">#<span class="hljs-keyword">else</span></span><br><span class="hljs-meta">#<span class="hljs-keyword">define</span> YALL_FUNC_        __func__</span><br><span class="hljs-meta">#<span class="hljs-keyword">endif</span></span><br><br><span class="hljs-comment">// 日志宏定义</span><br><span class="hljs-meta">#<span class="hljs-keyword">define</span> YALL_DUMP_        Yall::GetDebugYall(Yall_LEVEL::LOG_DUMP, __FILE__, YALL_FUNC_, __LINE__)</span><br><span class="hljs-meta">#<span class="hljs-keyword">define</span> YALL_DEBUG_       Yall::GetDebugYall(Yall_LEVEL::LOG_DEBUG, __FILE__, YALL_FUNC_, __LINE__)</span><br><span class="hljs-meta">#<span class="hljs-keyword">define</span> YALL_OK_          Yall::GetYall(Yall_LEVEL::LOG_OK)</span><br><span class="hljs-meta">#<span class="hljs-keyword">define</span> YALL_EVAL_        Yall::GetYall(Yall_LEVEL::LOG_EVAL)</span><br><span class="hljs-meta">#<span class="hljs-keyword">define</span> YALL_INFO_        Yall::GetYall(Yall_LEVEL::LOG_INFO)</span><br><span class="hljs-meta">#<span class="hljs-keyword">define</span> YALL_WARN_        Yall::GetYall(Yall_LEVEL::LOG_WARN)</span><br><span class="hljs-meta">#<span class="hljs-keyword">define</span> YALL_ERROR_       Yall::GetYall(Yall_LEVEL::LOG_ERROR)</span><br><span class="hljs-meta">#<span class="hljs-keyword">define</span> YALL_CRITICAL_    Yall::GetYall(Yall_LEVEL::LOG_CRITICAL)</span><br></code></pre></td></tr></tbody></table></figure><h3 id="使用示例" data-id="使用示例" class="notion-h"><a href="#使用示例" class="headerlink" title="使用示例"></a>使用示例</h3><figure class="highlight cpp"><table><tbody><tr><td class="code"><pre><code class="hljs cpp"><span class="hljs-comment">// 基本日志</span><br>YALL_INFO_ &lt;&lt; <span class="hljs-string">"Starting audio processing..."</span>;<br>YALL_OK_ &lt;&lt; <span class="hljs-string">"Model loaded successfully"</span>;<br>YALL_WARN_ &lt;&lt; <span class="hljs-string">"Audio file is stereo, converting to mono"</span>;<br>YALL_ERROR_ &lt;&lt; <span class="hljs-string">"Failed to open audio file"</span>;<br><br><span class="hljs-comment">// 调试日志（自动包含源码位置）</span><br>YALL_DEBUG_ &lt;&lt; <span class="hljs-string">"Initializing WorldModule"</span>;<br><br><span class="hljs-comment">// 性能计时</span><br>YALL_EVAL_ &lt;&lt; <span class="hljs-string">"F0 Estimation: 152ms"</span>;<br><br><span class="hljs-comment">// 数据转存（仅在 DUMP_DATA 宏启用时）</span><br>YALL_DUMP_ &lt;&lt; <span class="hljs-string">"F0 array: [440.0, 441.5, 439.8, ...]"</span>;<br></code></pre></td></tr></tbody></table></figure><hr><h2 id="Timer-h-高精度计时器" data-id="Timer-h-高精度计时器" class="notion-h"><a href="#Timer-h-高精度计时器" class="headerlink" title="Timer.h 高精度计时器"></a>Timer.h 高精度计时器</h2><p>Timer 类提供微秒级别的计时功能，用于性能分析和优化。</p><h3 id="类定义" data-id="类定义" class="notion-h"><a href="#类定义" class="headerlink" title="类定义"></a>类定义</h3><figure class="highlight cpp"><table><tbody><tr><td class="code"><pre><code class="hljs cpp"><span class="hljs-keyword">class</span> <span class="hljs-title class_">Timer</span> {<br><span class="hljs-keyword">public</span>:<br>    <span class="hljs-built_in">Timer</span>() {<br>        g_start_time = <span class="hljs-built_in">get_perf_count</span>();  <span class="hljs-comment">// 全局起始时间</span><br>        start_time = g_start_time;        <span class="hljs-comment">// 当前计时起始</span><br>    };<br><br>    <span class="hljs-function"><span class="hljs-type">void</span> <span class="hljs-title">SetTimer</span><span class="hljs-params">()</span> </span>{<br>        end_time = <span class="hljs-number">0</span>;<br>        start_time = <span class="hljs-built_in">get_perf_count</span>();    <span class="hljs-comment">// 重置计时</span><br>    }<br><br>    <span class="hljs-function"><span class="hljs-type">uint64_t</span> <span class="hljs-title">GetTimer</span><span class="hljs-params">()</span> </span>{<br>        end_time = <span class="hljs-built_in">get_perf_count</span>();<br>        <span class="hljs-keyword">return</span> end_time - start_time;     <span class="hljs-comment">// 返回微秒数</span><br>    }<br><br>    <span class="hljs-function">std::string <span class="hljs-title">GetTimer</span><span class="hljs-params">(<span class="hljs-type">const</span> std::string &amp;info)</span> </span>{<br>        end_time = <span class="hljs-built_in">get_perf_count</span>();<br>        <span class="hljs-keyword">return</span> [&amp;]() -&gt; std::string {<br>            <span class="hljs-keyword">auto</span> time = end_time - start_time;<br>            <span class="hljs-keyword">if</span> (time / <span class="hljs-number">1000</span> == <span class="hljs-number">0</span>)<br>                <span class="hljs-keyword">return</span> info + std::<span class="hljs-built_in">to_string</span>(end_time - start_time) + <span class="hljs-string">"us"</span>;<br>            <span class="hljs-keyword">else</span><br>                <span class="hljs-keyword">return</span> info + std::<span class="hljs-built_in">to_string</span>((end_time - start_time) / <span class="hljs-number">1000</span>) + <span class="hljs-string">"ms"</span>;<br>        }();<br>    }<br><br>    <span class="hljs-function">std::string <span class="hljs-title">EndTimer</span><span class="hljs-params">()</span> </span>{<br>        g_end_time = <span class="hljs-built_in">get_perf_count</span>();<br>        <span class="hljs-keyword">return</span> [&amp;]() -&gt; std::string {<br>            <span class="hljs-keyword">auto</span> time = g_end_time - g_start_time;<br>            <span class="hljs-keyword">if</span> (time / <span class="hljs-number">1000</span> == <span class="hljs-number">0</span>)<br>                <span class="hljs-keyword">return</span> std::<span class="hljs-built_in">to_string</span>(g_end_time - g_start_time) + <span class="hljs-string">"us"</span>;<br>            <span class="hljs-keyword">else</span><br>                <span class="hljs-keyword">return</span> std::<span class="hljs-built_in">to_string</span>((g_end_time - g_start_time) / <span class="hljs-number">1000</span>) + <span class="hljs-string">"ms"</span>;<br>        }();<br>    }<br><br><span class="hljs-keyword">protected</span>:<br>    <span class="hljs-type">uint64_t</span> start_time = <span class="hljs-number">0</span>;<br>    <span class="hljs-type">uint64_t</span> end_time = <span class="hljs-number">0</span>;<br>    <span class="hljs-type">uint64_t</span> g_start_time = <span class="hljs-number">0</span>;   <span class="hljs-comment">// 全局起始（构造时）</span><br>    <span class="hljs-type">uint64_t</span> g_end_time = <span class="hljs-number">0</span>;     <span class="hljs-comment">// 全局结束</span><br><br><span class="hljs-keyword">private</span>:<br>    <span class="hljs-function"><span class="hljs-type">static</span> <span class="hljs-type">uint64_t</span> <span class="hljs-title">get_perf_count</span><span class="hljs-params">()</span> </span>{<br>        <span class="hljs-keyword">return</span> std::chrono::<span class="hljs-built_in">duration_cast</span>&lt;std::chrono::microseconds&gt;(<br>            std::chrono::steady_clock::<span class="hljs-built_in">now</span>().<span class="hljs-built_in">time_since_epoch</span>()).<span class="hljs-built_in">count</span>();<br>    };<br>};<br></code></pre></td></tr></tbody></table></figure><h3 id="计时精度" data-id="计时精度" class="notion-h"><a href="#计时精度" class="headerlink" title="计时精度"></a>计时精度</h3><p>使用 <code>std::chrono::steady_clock</code>：</p><ul><li><strong>单调递增</strong>：不受系统时间调整影响</li><li><strong>高精度</strong>：微秒级别分辨率</li><li><strong>跨平台</strong>：标准 C++11 实现</li></ul><h3 id="使用模式" data-id="使用模式" class="notion-h"><a href="#使用模式" class="headerlink" title="使用模式"></a>使用模式</h3><figure class="highlight cpp"><table><tbody><tr><td class="code"><pre><code class="hljs cpp">Timer timer;<br><br><span class="hljs-comment">// 模式1：分段计时</span><br>timer.<span class="hljs-built_in">SetTimer</span>();<br><span class="hljs-comment">// ... 执行操作1 ...</span><br>YALL_EVAL_ &lt;&lt; timer.<span class="hljs-built_in">GetTimer</span>(<span class="hljs-string">"操作1耗时: "</span>);<br><br>timer.<span class="hljs-built_in">SetTimer</span>();<br><span class="hljs-comment">// ... 执行操作2 ...</span><br>YALL_EVAL_ &lt;&lt; timer.<span class="hljs-built_in">GetTimer</span>(<span class="hljs-string">"操作2耗时: "</span>);<br><br><span class="hljs-comment">// 模式2：总计时</span><br><span class="hljs-comment">// ... 所有操作 ...</span><br>YALL_INFO_ &lt;&lt; <span class="hljs-string">"总耗时: "</span> + timer.<span class="hljs-built_in">EndTimer</span>();<br></code></pre></td></tr></tbody></table></figure><h3 id="实际应用示例" data-id="实际应用示例" class="notion-h"><a href="#实际应用示例" class="headerlink" title="实际应用示例"></a>实际应用示例</h3><figure class="highlight cpp"><table><tbody><tr><td class="code"><pre><code class="hljs cpp"><span class="hljs-function"><span class="hljs-type">void</span> <span class="hljs-title">lessampler::run</span><span class="hljs-params">()</span> </span>{<br>    Timer timer;<br><br>    <span class="hljs-comment">// 音频模型检查和加载</span><br>    timer.<span class="hljs-built_in">SetTimer</span>();<br>    audio_model_io.<span class="hljs-built_in">ReadAudioModel</span>(configure);<br>    YALL_INFO_ &lt;&lt; timer.<span class="hljs-built_in">GetTimer</span>(<span class="hljs-string">"Read Audio Model: "</span>);<br><br>    <span class="hljs-comment">// 音频变换处理</span><br>    timer.<span class="hljs-built_in">SetTimer</span>();<br>    <span class="hljs-function">AudioProcess <span class="hljs-title">audioProcess</span><span class="hljs-params">(origin_audio_model, shine_para)</span></span>;<br>    YALL_INFO_ &lt;&lt; timer.<span class="hljs-built_in">GetTimer</span>(<span class="hljs-string">"Processing Model: "</span>);<br><br>    <span class="hljs-comment">// 音频合成</span><br>    timer.<span class="hljs-built_in">SetTimer</span>();<br>    <span class="hljs-function">Synthesis <span class="hljs-title">synthesis</span><span class="hljs-params">(trans_audio_model, shine_para.output_samples)</span></span>;<br>    YALL_INFO_ &lt;&lt; timer.<span class="hljs-built_in">GetTimer</span>(<span class="hljs-string">"Synthesis Audio: "</span>);<br><br>    <span class="hljs-comment">// 总耗时</span><br>    YALL_OK_ &lt;&lt; <span class="hljs-string">"All Process Done: "</span> + timer.<span class="hljs-built_in">EndTimer</span>();<br>}<br></code></pre></td></tr></tbody></table></figure><hr><h2 id="VectorWrapper-h-数组转向量模板" data-id="VectorWrapper-h-数组转向量模板" class="notion-h"><a href="#VectorWrapper-h-数组转向量模板" class="headerlink" title="VectorWrapper.h 数组转向量模板"></a>VectorWrapper.h 数组转向量模板</h2><p>VectorWrapper 是一个简单的模板工具，用于将 C 风格数组转换为 <code>std::vector</code>。</p><h3 id="类定义-1" data-id="类定义-1" class="notion-h"><a href="#类定义-1" class="headerlink" title="类定义"></a>类定义</h3><figure class="highlight cpp"><table><tbody><tr><td class="code"><pre><code class="hljs cpp"><span class="hljs-keyword">template</span>&lt;<span class="hljs-keyword">typename</span> T, <span class="hljs-type">int</span> N&gt;<br><span class="hljs-keyword">struct</span> <span class="hljs-title class_">VectorWrapper</span> {<br>    <span class="hljs-function"><span class="hljs-keyword">explicit</span> <span class="hljs-title">VectorWrapper</span><span class="hljs-params">(T (&amp;D)[N])</span> </span>{<br>        std::<span class="hljs-built_in">copy</span>(D, D + N, std::<span class="hljs-built_in">back_inserter</span>(v));<br>    }<br><br>    std::vector&lt;T&gt; v;<br>};<br></code></pre></td></tr></tbody></table></figure><p><strong>模板参数</strong>：</p><ul><li><code>T</code>：数组元素类型</li><li><code>N</code>：数组长度（编译时确定）</li></ul><h3 id="使用示例-1" data-id="使用示例-1" class="notion-h"><a href="#使用示例-1" class="headerlink" title="使用示例"></a>使用示例</h3><figure class="highlight cpp"><table><tbody><tr><td class="code"><pre><code class="hljs cpp"><span class="hljs-comment">// C 风格数组</span><br><span class="hljs-type">int</span> arr[] = {<span class="hljs-number">1</span>, <span class="hljs-number">2</span>, <span class="hljs-number">3</span>, <span class="hljs-number">4</span>, <span class="hljs-number">5</span>};<br><br><span class="hljs-comment">// 转换为 vector</span><br><span class="hljs-function">VectorWrapper&lt;<span class="hljs-type">int</span>, 5&gt; <span class="hljs-title">wrapper</span><span class="hljs-params">(arr)</span></span>;<br>std::vector&lt;<span class="hljs-type">int</span>&gt; vec = wrapper.v;  <span class="hljs-comment">// {1, 2, 3, 4, 5}</span><br><br><span class="hljs-comment">// 自动推导长度</span><br><span class="hljs-type">double</span> freq_table[] = {<span class="hljs-number">440.0</span>, <span class="hljs-number">466.16</span>, <span class="hljs-number">493.88</span>, <span class="hljs-number">523.25</span>};<br><span class="hljs-function">VectorWrapper&lt;<span class="hljs-type">double</span>, 4&gt; <span class="hljs-title">freq_vec</span><span class="hljs-params">(freq_table)</span></span>;<br></code></pre></td></tr></tbody></table></figure><p><strong>应用场景</strong>：</p><ul><li>将硬编码的数组转换为可操作的 vector</li><li>支持迭代器和 STL 算法</li><li>类型安全的数组转换</li></ul><hr><h2 id="exception-h-自定义异常体系" data-id="exception-h-自定义异常体系" class="notion-h"><a href="#exception-h-自定义异常体系" class="headerlink" title="exception.h 自定义异常体系"></a>exception.h 自定义异常体系</h2><p>exception.h 定义了 lessampler 专用的异常类型，提供语义清晰的错误报告。</p><h3 id="异常类型定义" data-id="异常类型定义" class="notion-h"><a href="#异常类型定义" class="headerlink" title="异常类型定义"></a>异常类型定义</h3><figure class="highlight cpp"><table><tbody><tr><td class="code"><pre><code class="hljs cpp"><span class="hljs-keyword">class</span> <span class="hljs-title class_">file_open_error</span> : <span class="hljs-keyword">public</span> std::runtime_error {<br><span class="hljs-keyword">public</span>:<br>    <span class="hljs-function"><span class="hljs-keyword">explicit</span> <span class="hljs-title">file_open_error</span><span class="hljs-params">(<span class="hljs-type">const</span> std::string &amp;what)</span></span><br><span class="hljs-function">        : std::runtime_error(<span class="hljs-string">"Fail to open file: "</span> + what + <span class="hljs-string">"."</span>) {</span>};<br>};<br><br><span class="hljs-keyword">class</span> <span class="hljs-title class_">header_check_error</span> : <span class="hljs-keyword">public</span> std::runtime_error {<br><span class="hljs-keyword">public</span>:<br>    <span class="hljs-built_in">header_check_error</span>(<span class="hljs-type">const</span> std::string &amp;what, <span class="hljs-type">const</span> std::string &amp;expect)<br>        : std::<span class="hljs-built_in">runtime_error</span>(<span class="hljs-string">"Header: "</span> + what + <span class="hljs-string">" is not same as "</span> + expect + <span class="hljs-string">"."</span>) {};<br>};<br><br><span class="hljs-keyword">class</span> <span class="hljs-title class_">file_version_error</span> : <span class="hljs-keyword">public</span> std::runtime_error {<br><span class="hljs-keyword">public</span>:<br>    <span class="hljs-function"><span class="hljs-keyword">explicit</span> <span class="hljs-title">file_version_error</span><span class="hljs-params">(<span class="hljs-type">const</span> std::string &amp;what)</span></span><br><span class="hljs-function">        : std::runtime_error(what + <span class="hljs-string">" Version Mismatch."</span>) {</span>};<br>};<br><br><span class="hljs-keyword">class</span> <span class="hljs-title class_">parameter_error</span> : <span class="hljs-keyword">public</span> std::runtime_error {<br><span class="hljs-keyword">public</span>:<br>    <span class="hljs-function"><span class="hljs-keyword">explicit</span> <span class="hljs-title">parameter_error</span><span class="hljs-params">(<span class="hljs-type">const</span> std::string &amp;what)</span></span><br><span class="hljs-function">        : std::runtime_error(<span class="hljs-string">"Parameter Error: "</span> + what + <span class="hljs-string">"."</span>) {</span>};<br>};<br><br><span class="hljs-keyword">class</span> <span class="hljs-title class_">type_error</span> : <span class="hljs-keyword">public</span> std::runtime_error {<br><span class="hljs-keyword">public</span>:<br>    <span class="hljs-function"><span class="hljs-keyword">explicit</span> <span class="hljs-title">type_error</span><span class="hljs-params">(<span class="hljs-type">const</span> std::string &amp;what)</span></span><br><span class="hljs-function">        : std::runtime_error(<span class="hljs-string">"Type Error: "</span> + what + <span class="hljs-string">"."</span>) {</span>};<br>};<br><br><span class="hljs-keyword">class</span> <span class="hljs-title class_">audio_file_error</span> : <span class="hljs-keyword">public</span> std::runtime_error {<br><span class="hljs-keyword">public</span>:<br>    <span class="hljs-function"><span class="hljs-keyword">explicit</span> <span class="hljs-title">audio_file_error</span><span class="hljs-params">(<span class="hljs-type">const</span> std::string &amp;what)</span></span><br><span class="hljs-function">        : std::runtime_error(<span class="hljs-string">"Audio File Error: "</span> + what + <span class="hljs-string">"."</span>) {</span>};<br>};<br></code></pre></td></tr></tbody></table></figure><h3 id="异常类型对照" data-id="异常类型对照" class="notion-h"><a href="#异常类型对照" class="headerlink" title="异常类型对照"></a>异常类型对照</h3><table><thead><tr><th>异常类型</th><th>使用场景</th><th>示例消息</th></tr></thead><tbody><tr><td><code>file_open_error</code></td><td>文件打开失败</td><td>“Fail to open file: input.wav.”</td></tr><tr><td><code>header_check_error</code></td><td>文件头验证失败</td><td>“Header: xxx is not same as shine.”</td></tr><tr><td><code>file_version_error</code></td><td>版本校验失败</td><td>“Please Regenerate Audio Model Version Mismatch.”</td></tr><tr><td><code>parameter_error</code></td><td>参数验证失败</td><td>“Parameter Error: offset exceeds audio length.”</td></tr><tr><td><code>type_error</code></td><td>类型转换错误</td><td>“Type Error: invalid FFT size value.”</td></tr><tr><td><code>audio_file_error</code></td><td>音频处理错误</td><td>“Audio File Error: unsupported format.”</td></tr></tbody></table><h3 id="异常处理模式" data-id="异常处理模式" class="notion-h"><a href="#异常处理模式" class="headerlink" title="异常处理模式"></a>异常处理模式</h3><figure class="highlight cpp"><table><tbody><tr><td class="code"><pre><code class="hljs cpp"><span class="hljs-comment">// lessampler main.cpp</span><br><span class="hljs-function"><span class="hljs-type">int</span> <span class="hljs-title">main</span><span class="hljs-params">(<span class="hljs-type">int</span> argc, <span class="hljs-type">char</span> *argv[])</span> </span>{<br>    <span class="hljs-keyword">try</span> {<br>        <span class="hljs-function">lessampler <span class="hljs-title">lessampler</span><span class="hljs-params">(argc, argv)</span></span>;<br>        lessampler.<span class="hljs-built_in">run</span>();<br>    } <span class="hljs-built_in">catch</span> (<span class="hljs-type">const</span> std::runtime_error &amp;error) {<br>        YALL_ERROR_ &lt;&lt; error.<span class="hljs-built_in">what</span>();<br>        <span class="hljs-keyword">return</span> <span class="hljs-number">-1</span>;<br>    }<br>    <span class="hljs-keyword">return</span> <span class="hljs-number">0</span>;<br>}<br></code></pre></td></tr></tbody></table></figure><h3 id="使用示例-2" data-id="使用示例-2" class="notion-h"><a href="#使用示例-2" class="headerlink" title="使用示例"></a>使用示例</h3><figure class="highlight cpp"><table><tbody><tr><td class="code"><pre><code class="hljs cpp"><span class="hljs-comment">// 文件打开检查</span><br><span class="hljs-function"><span class="hljs-type">void</span> <span class="hljs-title">ConfigUnit::SetConfig</span><span class="hljs-params">(<span class="hljs-type">const</span> std::filesystem::path &amp;exec_path)</span> </span>{<br>    <span class="hljs-keyword">if</span> (config_file_path.<span class="hljs-built_in">empty</span>()) {<br>        <span class="hljs-keyword">throw</span> <span class="hljs-built_in">file_open_error</span>(<span class="hljs-string">"Configure file: "</span> + config_file_path.<span class="hljs-built_in">string</span>());<br>    }<br>}<br><br><span class="hljs-comment">// 版本检查</span><br><span class="hljs-function"><span class="hljs-type">void</span> <span class="hljs-title">AudioModelIO::ReadAudioContent</span><span class="hljs-params">()</span> </span>{<br>    <span class="hljs-keyword">if</span> (ver_string != _configure.<span class="hljs-built_in">get_version</span>()) {<br>        <span class="hljs-keyword">throw</span> <span class="hljs-built_in">file_version_error</span>(<span class="hljs-string">"Please Regenerate Audio Model"</span>);<br>    }<br>}<br><br><span class="hljs-comment">// 参数验证</span><br><span class="hljs-function"><span class="hljs-type">void</span> <span class="hljs-title">libUTAU::CheckPara</span><span class="hljs-params">(<span class="hljs-type">const</span> lessAudioModel&amp; audioModel)</span> </span>{<br>    <span class="hljs-keyword">if</span> (utauPara.offset + utauPara.last_unused_part &gt;= utauPara.wave_length)<br>        <span class="hljs-keyword">throw</span> <span class="hljs-built_in">parameter_error</span>(<span class="hljs-string">"The audio offset and whitespace are greater than the required audio length"</span>);<br>}<br></code></pre></td></tr></tbody></table></figure><hr><h2 id="条件编译开关设计" data-id="条件编译开关设计" class="notion-h"><a href="#条件编译开关设计" class="headerlink" title="条件编译开关设计"></a>条件编译开关设计</h2><p>日志系统通过预定义宏控制输出行为：</p><figure class="highlight cpp"><table><tbody><tr><td class="code"><pre><code class="hljs cpp"><span class="hljs-comment">// 调试模式开关</span><br><span class="hljs-meta">#<span class="hljs-keyword">ifdef</span> DEBUG_MODE</span><br>    <span class="hljs-comment">// 启用 DEBUG 输出</span><br><span class="hljs-meta">#<span class="hljs-keyword">endif</span></span><br><br><span class="hljs-comment">// 数据转存开关</span><br><span class="hljs-meta">#<span class="hljs-keyword">ifdef</span> DUMP_DATA</span><br>    <span class="hljs-comment">// 启用 DUMP 输出</span><br><span class="hljs-meta">#<span class="hljs-keyword">endif</span></span><br><br><span class="hljs-comment">// 性能评估开关</span><br><span class="hljs-meta">#<span class="hljs-keyword">ifdef</span> TIME_EVAL</span><br>    <span class="hljs-comment">// 启用 EVAL 输出</span><br><span class="hljs-meta">#<span class="hljs-keyword">endif</span></span><br></code></pre></td></tr></tbody></table></figure><p><strong>配置方式</strong>：</p><p>通过 CMake 定义：</p><figure class="highlight cmake"><table><tbody><tr><td class="code"><pre><code class="hljs cmake"><span class="hljs-keyword">if</span>(CMAKE_BUILD_TYPE <span class="hljs-keyword">MATCHES</span> Debug)<br>    <span class="hljs-keyword">target_compile_definitions</span>(lessampler PRIVATE DEBUG_MODE DUMP_DATA TIME_EVAL)<br><span class="hljs-keyword">endif</span>()<br></code></pre></td></tr></tbody></table></figure><p>或通过 lessconfig.ini：</p><figure class="highlight ini"><table><tbody><tr><td class="code"><pre><code class="hljs ini"><span class="hljs-section">[config]</span><br><span class="hljs-attr">debug</span> = <span class="hljs-number">1</span><br></code></pre></td></tr></tbody></table></figure><hr><h2 id="工具类设计哲学总结" data-id="工具类设计哲学总结" class="notion-h"><a href="#工具类设计哲学总结" class="headerlink" title="工具类设计哲学总结"></a>工具类设计哲学总结</h2><h3 id="LOG-h-设计要点" data-id="LOG-h-设计要点" class="notion-h"><a href="#LOG-h-设计要点" class="headerlink" title="LOG.h 设计要点"></a>LOG.h 设计要点</h3><ol><li><strong>分级输出</strong>：不同级别用不同颜色，便于快速识别</li><li><strong>条件编译</strong>：调试输出不影响发布版性能</li><li><strong>源码追踪</strong>：DEBUG 级别自动附加文件名、函数名、行号</li><li><strong>线程安全</strong>：mutex 保护多线程输出</li><li><strong>单例模式</strong>：避免重复创建日志器对象</li></ol><h3 id="Timer-h-设计要点" data-id="Timer-h-设计要点" class="notion-h"><a href="#Timer-h-设计要点" class="headerlink" title="Timer.h 设计要点"></a>Timer.h 设计要点</h3><ol><li><strong>高精度</strong>：微秒级别，足够测量大多数操作</li><li><strong>单调时钟</strong>：不受系统时间调整影响</li><li><strong>双模式</strong>：支持分段计时和总计时</li><li><strong>自动格式化</strong>：根据时长选择 μs 或 ms 单位</li></ol><h3 id="VectorWrapper-h-设计要点" data-id="VectorWrapper-h-设计要点" class="notion-h"><a href="#VectorWrapper-h-设计要点" class="headerlink" title="VectorWrapper.h 设计要点"></a>VectorWrapper.h 设计要点</h3><ol><li><strong>模板推导</strong>：自动获取数组长度，无需手动指定</li><li><strong>类型安全</strong>：编译时检查类型匹配</li><li><strong>简单实用</strong>：单一职责，易于理解和使用</li></ol><h3 id="exception-h-设计要点" data-id="exception-h-设计要点" class="notion-h"><a href="#exception-h-设计要点" class="headerlink" title="exception.h 设计要点"></a>exception.h 设计要点</h3><ol><li><strong>继承标准异常</strong>：符合 C++ 异常处理规范</li><li><strong>语义命名</strong>：类名直接表达错误类型</li><li><strong>统一格式</strong>：所有异常消息格式一致</li><li><strong>顶层捕获</strong>：main 函数统一捕获处理</li></ol>]]>
    </content>
    <id>https://gloomyghost.com/live/2026-04-25-lessampler-utils-module.aspx</id>
    <link href="https://gloomyghost.com/live/2026-04-25-lessampler-utils-module.aspx"/>
    <published>2026-04-24T16:00:00.000Z</published>
    <summary>
      <![CDATA[<p>Utils 模块提供 lessampler 的基础设施工具类，包括分级日志系统、高性能计时器、数组转换工具和自定义异常体系。这些工具类为整个项目提供一致的基础功能支持，确保代码的健壮性和可调试性。</p>
<p>良好的工具类设计是软件工程质量的重要体现。Utils 模块的设]]>
    </summary>
    <title>lessampler: Utils 模块 - 工具类的设计哲学</title>
    <updated>2026-05-29T20:33:31.218Z</updated>
  </entry>
  <entry>
    <author>
      <name>柚木 鉉</name>
    </author>
    <category term="UTAU" scheme="https://gloomyghost.com/tags/UTAU/"/>
    <category term="lessampler" scheme="https://gloomyghost.com/tags/lessampler/"/>
    <content>
      <![CDATA[<p>FileIO 模块负责 lessampler 的所有文件读写操作，包括音频模型文件的二进制存储、WAV 文件读写、JSON 导出以及批量模型生成。该模块是数据持久化的核心，确保音频分析结果能够高效存储并在后续合成时快速加载。</p><p>音频模型文件（<code>.lessaudio</code>）是 lessampler 的核心数据格式，存储了 WORLD 分析的全部结果。该格式经过优化，使用紧凑的二进制结构，支持版本校验以防止配置不一致导致的合成错误。</p><h2 id="模块结构" data-id="模块结构" class="notion-h"><a href="#模块结构" class="headerlink" title="模块结构"></a>模块结构</h2><figure class="highlight plaintext"><table><tbody><tr><td class="code"><pre><code class="hljs plaintext">FileIO/<br>├── AudioModelIO.h/cpp    # 音频模型文件读写<br>├── GenerateAudioModel.h/cpp # 批量模型生成<br>├── WavIO.h/cpp           # WAV 文件读写<br>├── JSONFileIO.h/cpp      # JSON 导出（调试用）<br>└── ZlibStream.h/cpp      # LZ4 压缩（预留）<br></code></pre></td></tr></tbody></table></figure><hr><h2 id="AudioModelIO-类详解" data-id="AudioModelIO-类详解" class="notion-h"><a href="#AudioModelIO-类详解" class="headerlink" title="AudioModelIO 类详解"></a>AudioModelIO 类详解</h2><p>AudioModelIO 是音频模型文件操作的核心类，负责 <code>.lessaudio</code> 文件的读写和版本验证。</p><h3 id="类定义" data-id="类定义" class="notion-h"><a href="#类定义" class="headerlink" title="类定义"></a>类定义</h3><figure class="highlight cpp"><table><tbody><tr><td class="code"><pre><code class="hljs cpp"><span class="hljs-keyword">class</span> <span class="hljs-title class_">AudioModelIO</span> {<br><span class="hljs-keyword">public</span>:<br>    <span class="hljs-built_in">AudioModelIO</span>(std::filesystem::path Path, lessAudioModel audioModel, lessConfigure configure);<br>    <span class="hljs-function"><span class="hljs-keyword">explicit</span> <span class="hljs-title">AudioModelIO</span><span class="hljs-params">(std::filesystem::path Path)</span></span>;<br>    ~<span class="hljs-built_in">AudioModelIO</span>();<br><br>    <span class="hljs-function"><span class="hljs-type">void</span> <span class="hljs-title">SetFilePath</span><span class="hljs-params">(<span class="hljs-type">const</span> std::filesystem::path &amp;Path)</span></span>;<br>    <span class="hljs-function"><span class="hljs-type">void</span> <span class="hljs-title">SetAudioModel</span><span class="hljs-params">(lessAudioModel audioModel)</span></span>;<br>    std::<span class="hljs-function">filesystem::path <span class="hljs-title">GetFilePath</span><span class="hljs-params">()</span></span>;<br>    <span class="hljs-function">lessAudioModel <span class="hljs-title">GetAudioModel</span><span class="hljs-params">()</span></span>;<br><br>    <span class="hljs-function"><span class="hljs-type">bool</span> <span class="hljs-title">CheckAudioModel</span><span class="hljs-params">(lessConfigure configure)</span></span>;  <span class="hljs-comment">// 检查模型存在性和版本</span><br>    <span class="hljs-function"><span class="hljs-type">void</span> <span class="hljs-title">SaveAudioModel</span><span class="hljs-params">()</span></span>;                          <span class="hljs-comment">// 保存模型</span><br>    <span class="hljs-function">lessAudioModel <span class="hljs-title">ReadAudioModel</span><span class="hljs-params">(lessConfigure configure)</span></span>;  <span class="hljs-comment">// 读取模型</span><br><br><span class="hljs-keyword">private</span>:<br>    lessAudioModel _audioModel{};<br>    lessConfigure _configure{};<br>    std::filesystem::path root_file_path{};<br>    std::filesystem::path in_file_path{};<br>    std::filesystem::path audio_model_file_path{};  <span class="hljs-comment">// .lessaudio 路径</span><br><br><span class="hljs-keyword">protected</span>:<br>    <span class="hljs-type">const</span> std::string audio_model_file_ext = <span class="hljs-string">"lessaudio"</span>;  <span class="hljs-comment">// 文件扩展名</span><br>    <span class="hljs-type">const</span> <span class="hljs-type">char</span> lessaudio_header[<span class="hljs-number">6</span>] = {<span class="hljs-string">'s'</span>, <span class="hljs-string">'h'</span>, <span class="hljs-string">'i'</span>, <span class="hljs-string">'n'</span>, <span class="hljs-string">'e'</span>, <span class="hljs-string">'\0'</span>};  <span class="hljs-comment">// 文件头</span><br>    <span class="hljs-type">const</span> <span class="hljs-type">char</span> lessaudio_ending[<span class="hljs-number">5</span>] = {<span class="hljs-string">'5'</span>, <span class="hljs-string">'4'</span>, <span class="hljs-string">'0'</span>, <span class="hljs-string">'2'</span>, <span class="hljs-string">'\0'</span>};       <span class="hljs-comment">// 文件尾</span><br><br><span class="hljs-keyword">private</span>:<br>    <span class="hljs-function">std::ofstream <span class="hljs-title">WriteAudioContent</span><span class="hljs-params">()</span></span>;   <span class="hljs-comment">// 写入二进制内容</span><br>    <span class="hljs-function"><span class="hljs-type">void</span> <span class="hljs-title">ReadAudioContent</span><span class="hljs-params">()</span></span>;             <span class="hljs-comment">// 读取二进制内容</span><br>    <span class="hljs-function"><span class="hljs-type">void</span> <span class="hljs-title">GenerateFilePath</span><span class="hljs-params">()</span></span>;             <span class="hljs-comment">// 生成模型文件路径</span><br>    <span class="hljs-function"><span class="hljs-type">bool</span> <span class="hljs-title">CheckAudioModelVersion</span><span class="hljs-params">()</span></span>;       <span class="hljs-comment">// 验证版本校验码</span><br>    <span class="hljs-function"><span class="hljs-type">static</span> <span class="hljs-type">bool</span> <span class="hljs-title">CheckAudioModelFile</span><span class="hljs-params">(<span class="hljs-type">const</span> std::filesystem::path &amp;path)</span></span>;<br>};<br></code></pre></td></tr></tbody></table></figure><h3 id="lessaudio-文件格式设计" data-id="lessaudio-文件格式设计" class="notion-h"><a href="#lessaudio-文件格式设计" class="headerlink" title=".lessaudio 文件格式设计"></a>.lessaudio 文件格式设计</h3><p>文件结构如下：</p><figure class="highlight plaintext"><table><tbody><tr><td class="code"><pre><code class="hljs plaintext">┌─────────────────────────────┐<br>│ Header: "shine\0"           │ 6 bytes - 文件标识<br>├─────────────────────────────┤<br>│ Version Size                │ sizeof(std::streamsize)<br>│ Version String              │ 40 bytes - SHA-1 校验码<br>├─────────────────────────────┤<br>│ x_length                    │ sizeof(int)<br>│ fs                          │ sizeof(int)<br>│ frame_period                │ sizeof(double)<br>│ w_length                    │ sizeof(int)<br>│ fft_size                    │ sizeof(int)<br>├─────────────────────────────┤<br>│ F0 Size                     │ sizeof(std::streamsize)<br>│ F0 Array                    │ f0_size × sizeof(double)<br>├─────────────────────────────┤<br>│ SP Outer Size               │ sizeof(std::streamsize)<br>│ For each frame:             │<br>│   SP Inner Size             │ sizeof(std::streamsize)<br>│   SP Frame Data             │ inner_size × sizeof(double)<br>├─────────────────────────────┤<br>│ AP Outer Size               │ sizeof(std::streamsize)<br>│ For each frame:             │<br>│   AP Inner Size             │ sizeof(std::streamsize)<br>│   AP Frame Data             │ inner_size × sizeof(double)<br>├─────────────────────────────┤<br>│ Ending: "5402\0"            │ 5 bytes - 文件结束标识<br>└─────────────────────────────┘<br></code></pre></td></tr></tbody></table></figure><h3 id="WriteAudioContent-写入实现" data-id="WriteAudioContent-写入实现" class="notion-h"><a href="#WriteAudioContent-写入实现" class="headerlink" title="WriteAudioContent() 写入实现"></a>WriteAudioContent() 写入实现</h3><figure class="highlight cpp"><table><tbody><tr><td class="code"><pre><code class="hljs cpp"><span class="hljs-function">std::ofstream <span class="hljs-title">AudioModelIO::WriteAudioContent</span><span class="hljs-params">()</span> </span>{<br>    <span class="hljs-function">std::ofstream <span class="hljs-title">audio_out_model</span><span class="hljs-params">(audio_model_file_path, std::ios::out | std::ios::binary)</span></span>;<br><br>    <span class="hljs-comment">// 1. 写入文件头</span><br>    audio_out_model.<span class="hljs-built_in">write</span>(lessaudio_header, <span class="hljs-built_in">sizeof</span>(<span class="hljs-type">char</span>) * <span class="hljs-number">6</span>);<br><br>    <span class="hljs-comment">// 2. 写入版本校验码</span><br>    std::streamsize ver_string_size = _configure.<span class="hljs-built_in">get_version</span>().<span class="hljs-built_in">size</span>();<br>    <span class="hljs-keyword">auto</span> ver_string = _configure.<span class="hljs-built_in">get_version</span>();<br>    audio_out_model.<span class="hljs-built_in">write</span>(<span class="hljs-built_in">reinterpret_cast</span>&lt;<span class="hljs-type">const</span> <span class="hljs-type">char</span> *&gt;(&amp;ver_string_size), <span class="hljs-built_in">sizeof</span>(std::streamsize));<br>    audio_out_model.<span class="hljs-built_in">write</span>(ver_string.<span class="hljs-built_in">c_str</span>(), ver_string_size * <span class="hljs-built_in">sizeof</span>(<span class="hljs-type">char</span>));<br><br>    <span class="hljs-comment">// 3. 写入基本信息</span><br>    <span class="hljs-type">int</span> x_length = _audioModel.x.<span class="hljs-built_in">size</span>();<br>    audio_out_model.<span class="hljs-built_in">write</span>(<span class="hljs-built_in">reinterpret_cast</span>&lt;<span class="hljs-type">const</span> <span class="hljs-type">char</span> *&gt;(&amp;x_length), <span class="hljs-built_in">sizeof</span>(<span class="hljs-type">int</span>));<br>    audio_out_model.<span class="hljs-built_in">write</span>(<span class="hljs-built_in">reinterpret_cast</span>&lt;<span class="hljs-type">const</span> <span class="hljs-type">char</span> *&gt;(&amp;_audioModel.fs), <span class="hljs-built_in">sizeof</span>(<span class="hljs-type">int</span>));<br>    audio_out_model.<span class="hljs-built_in">write</span>(<span class="hljs-built_in">reinterpret_cast</span>&lt;<span class="hljs-type">const</span> <span class="hljs-type">char</span> *&gt;(&amp;_audioModel.frame_period), <span class="hljs-built_in">sizeof</span>(<span class="hljs-type">double</span>));<br>    audio_out_model.<span class="hljs-built_in">write</span>(<span class="hljs-built_in">reinterpret_cast</span>&lt;<span class="hljs-type">const</span> <span class="hljs-type">char</span> *&gt;(&amp;_audioModel.w_length), <span class="hljs-built_in">sizeof</span>(<span class="hljs-type">int</span>));<br>    audio_out_model.<span class="hljs-built_in">write</span>(<span class="hljs-built_in">reinterpret_cast</span>&lt;<span class="hljs-type">const</span> <span class="hljs-type">char</span> *&gt;(&amp;_audioModel.fft_size), <span class="hljs-built_in">sizeof</span>(<span class="hljs-type">int</span>));<br><br>    <span class="hljs-comment">// 4. 写入 F0 数据</span><br>    std::streamsize f0_size = _audioModel.f<span class="hljs-number">0.</span><span class="hljs-built_in">size</span>();<br>    audio_out_model.<span class="hljs-built_in">write</span>(<span class="hljs-built_in">reinterpret_cast</span>&lt;<span class="hljs-type">const</span> <span class="hljs-type">char</span> *&gt;(&amp;f0_size), <span class="hljs-built_in">sizeof</span>(std::streamsize));<br>    audio_out_model.<span class="hljs-built_in">write</span>(<span class="hljs-built_in">reinterpret_cast</span>&lt;<span class="hljs-type">const</span> <span class="hljs-type">char</span> *&gt;(&amp;_audioModel.f0[<span class="hljs-number">0</span>]), f0_size * <span class="hljs-built_in">sizeof</span>(<span class="hljs-type">double</span>));<br><br>    <span class="hljs-comment">// 5. 写入频谱包络数据（二维数组）</span><br>    std::streamsize sp_size = _audioModel.spectrogram.<span class="hljs-built_in">size</span>();<br>    audio_out_model.<span class="hljs-built_in">write</span>(<span class="hljs-built_in">reinterpret_cast</span>&lt;<span class="hljs-type">const</span> <span class="hljs-type">char</span> *&gt;(&amp;sp_size), <span class="hljs-built_in">sizeof</span>(std::streamsize));<br>    <span class="hljs-keyword">for</span> (<span class="hljs-keyword">auto</span> &amp;item: _audioModel.spectrogram) {<br>        std::streamsize size = item.<span class="hljs-built_in">size</span>();<br>        audio_out_model.<span class="hljs-built_in">write</span>(<span class="hljs-built_in">reinterpret_cast</span>&lt;<span class="hljs-type">const</span> <span class="hljs-type">char</span> *&gt;(&amp;size), <span class="hljs-built_in">sizeof</span>(std::streamsize));<br>        audio_out_model.<span class="hljs-built_in">write</span>(<span class="hljs-built_in">reinterpret_cast</span>&lt;<span class="hljs-type">const</span> <span class="hljs-type">char</span> *&gt;(&amp;item[<span class="hljs-number">0</span>]), item.<span class="hljs-built_in">size</span>() * <span class="hljs-built_in">sizeof</span>(<span class="hljs-type">double</span>));<br>    }<br><br>    <span class="hljs-comment">// 6. 写入非周期性数据（二维数组）</span><br>    std::streamsize ap_size = _audioModel.aperiodicity.<span class="hljs-built_in">size</span>();<br>    audio_out_model.<span class="hljs-built_in">write</span>(<span class="hljs-built_in">reinterpret_cast</span>&lt;<span class="hljs-type">const</span> <span class="hljs-type">char</span> *&gt;(&amp;ap_size), <span class="hljs-built_in">sizeof</span>(std::streamsize));<br>    <span class="hljs-keyword">for</span> (<span class="hljs-keyword">auto</span> &amp;item: _audioModel.aperiodicity) {<br>        <span class="hljs-keyword">auto</span> size = item.<span class="hljs-built_in">size</span>();<br>        audio_out_model.<span class="hljs-built_in">write</span>(<span class="hljs-built_in">reinterpret_cast</span>&lt;<span class="hljs-type">const</span> <span class="hljs-type">char</span> *&gt;(&amp;size), <span class="hljs-built_in">sizeof</span>(std::streamsize));<br>        audio_out_model.<span class="hljs-built_in">write</span>(<span class="hljs-built_in">reinterpret_cast</span>&lt;<span class="hljs-type">const</span> <span class="hljs-type">char</span> *&gt;(&amp;item[<span class="hljs-number">0</span>]), item.<span class="hljs-built_in">size</span>() * <span class="hljs-built_in">sizeof</span>(<span class="hljs-type">double</span>));<br>    }<br><br>    <span class="hljs-comment">// 7. 写入文件尾</span><br>    audio_out_model.<span class="hljs-built_in">write</span>(lessaudio_ending, <span class="hljs-built_in">sizeof</span>(<span class="hljs-type">char</span>) * <span class="hljs-number">5</span>);<br><br>    <span class="hljs-keyword">return</span> audio_out_model;<br>}<br></code></pre></td></tr></tbody></table></figure><p><strong>写入要点</strong>：</p><ul><li>使用二进制模式 (<code>std::ios::binary</code>) 避免平台换行符差异</li><li>二维数组按行存储，每行先写入长度再写入数据</li><li>使用 <code>reinterpret_cast</code> 将数值转为字节序列</li></ul><h3 id="ReadAudioContent-读取实现" data-id="ReadAudioContent-读取实现" class="notion-h"><a href="#ReadAudioContent-读取实现" class="headerlink" title="ReadAudioContent() 读取实现"></a>ReadAudioContent() 读取实现</h3><figure class="highlight cpp"><table><tbody><tr><td class="code"><pre><code class="hljs cpp"><span class="hljs-function"><span class="hljs-type">void</span> <span class="hljs-title">AudioModelIO::ReadAudioContent</span><span class="hljs-params">()</span> </span>{<br>    <span class="hljs-function">std::ifstream <span class="hljs-title">audio_in_model</span><span class="hljs-params">(audio_model_file_path, std::ios::in | std::ios::binary)</span></span>;<br><br>    <span class="hljs-comment">// 1. 检查文件头</span><br>    <span class="hljs-keyword">auto</span> header = <span class="hljs-keyword">new</span> <span class="hljs-type">char</span>[<span class="hljs-number">6</span>];<br>    audio_in_model.<span class="hljs-built_in">read</span>(header, <span class="hljs-built_in">sizeof</span>(<span class="hljs-type">char</span>) * <span class="hljs-number">6</span>);<br>    <span class="hljs-keyword">if</span> (std::<span class="hljs-built_in">string</span>(lessaudio_header) != std::<span class="hljs-built_in">string</span>(header)) {<br>        <span class="hljs-keyword">throw</span> <span class="hljs-built_in">header_check_error</span>(header, lessaudio_header);<br>    }<br><br>    <span class="hljs-comment">// 2. 检查版本校验码</span><br>    std::streamsize ver_string_size;<br>    audio_in_model.<span class="hljs-built_in">read</span>(<span class="hljs-built_in">reinterpret_cast</span>&lt;<span class="hljs-type">char</span> *&gt;(&amp;ver_string_size), <span class="hljs-built_in">sizeof</span>(std::streamsize));<br>    std::vector&lt;<span class="hljs-type">char</span>&gt; _temp(ver_string_size);<br>    audio_in_model.<span class="hljs-built_in">read</span>(<span class="hljs-built_in">reinterpret_cast</span>&lt;<span class="hljs-type">char</span> *&gt;(&amp;_temp[<span class="hljs-number">0</span>]), std::<span class="hljs-built_in">streamsize</span>(ver_string_size * <span class="hljs-built_in">sizeof</span>(<span class="hljs-type">char</span>)));<br>    <span class="hljs-function">std::string <span class="hljs-title">ver_string</span><span class="hljs-params">(_temp.begin(), _temp.end())</span></span>;<br><br>    <span class="hljs-keyword">if</span> (ver_string != _configure.<span class="hljs-built_in">get_version</span>()) {<br>        <span class="hljs-keyword">throw</span> <span class="hljs-built_in">file_version_error</span>(<span class="hljs-string">"Please Regenerate Audio Model"</span>);<br>    }<br><br>    <span class="hljs-comment">// 3. 读取基本信息</span><br>    <span class="hljs-type">int</span> x_length;<br>    audio_in_model.<span class="hljs-built_in">read</span>(<span class="hljs-built_in">reinterpret_cast</span>&lt;<span class="hljs-type">char</span> *&gt;(&amp;x_length), <span class="hljs-built_in">sizeof</span>(<span class="hljs-type">int</span>));<br>    audio_in_model.<span class="hljs-built_in">read</span>(<span class="hljs-built_in">reinterpret_cast</span>&lt;<span class="hljs-type">char</span> *&gt;(&amp;_audioModel.fs), <span class="hljs-built_in">sizeof</span>(<span class="hljs-type">int</span>));<br>    audio_in_model.<span class="hljs-built_in">read</span>(<span class="hljs-built_in">reinterpret_cast</span>&lt;<span class="hljs-type">char</span> *&gt;(&amp;_audioModel.frame_period), <span class="hljs-built_in">sizeof</span>(<span class="hljs-type">double</span>));<br>    audio_in_model.<span class="hljs-built_in">read</span>(<span class="hljs-built_in">reinterpret_cast</span>&lt;<span class="hljs-type">char</span> *&gt;(&amp;_audioModel.w_length), <span class="hljs-built_in">sizeof</span>(<span class="hljs-type">int</span>));<br>    audio_in_model.<span class="hljs-built_in">read</span>(<span class="hljs-built_in">reinterpret_cast</span>&lt;<span class="hljs-type">char</span> *&gt;(&amp;_audioModel.fft_size), <span class="hljs-built_in">sizeof</span>(<span class="hljs-type">int</span>));<br><br>    _audioModel.x.<span class="hljs-built_in">resize</span>(x_length);<br><br>    <span class="hljs-comment">// 4. 读取 F0 数据</span><br>    std::streamsize f0_length_size = <span class="hljs-number">0</span>;<br>    audio_in_model.<span class="hljs-built_in">read</span>(<span class="hljs-built_in">reinterpret_cast</span>&lt;<span class="hljs-type">char</span> *&gt;(&amp;f0_length_size), <span class="hljs-built_in">sizeof</span>(std::streamsize));<br>    _audioModel.f<span class="hljs-number">0.</span><span class="hljs-built_in">resize</span>(f0_length_size);<br>    audio_in_model.<span class="hljs-built_in">read</span>(<span class="hljs-built_in">reinterpret_cast</span>&lt;<span class="hljs-type">char</span> *&gt;(&amp;_audioModel.f0[<span class="hljs-number">0</span>]), std::<span class="hljs-built_in">streamsize</span>(f0_length_size * <span class="hljs-built_in">sizeof</span>(<span class="hljs-type">double</span>)));<br><br>    <span class="hljs-comment">// 5. 读取频谱包络数据</span><br>    std::streamsize sp_length_size = <span class="hljs-number">0</span>;<br>    audio_in_model.<span class="hljs-built_in">read</span>(<span class="hljs-built_in">reinterpret_cast</span>&lt;<span class="hljs-type">char</span> *&gt;(&amp;sp_length_size), <span class="hljs-built_in">sizeof</span>(std::streamsize));<br>    <span class="hljs-keyword">for</span> (std::streamsize n = <span class="hljs-number">0</span>; n &lt; sp_length_size; ++n) {<br>        std::streamsize sp_inner_length_size = <span class="hljs-number">0</span>;<br>        audio_in_model.<span class="hljs-built_in">read</span>(<span class="hljs-built_in">reinterpret_cast</span>&lt;<span class="hljs-type">char</span> *&gt;(&amp;sp_inner_length_size), <span class="hljs-built_in">sizeof</span>(std::streamsize));<br>        _audioModel.spectrogram.<span class="hljs-built_in">resize</span>(sp_length_size, std::<span class="hljs-built_in">vector</span>&lt;<span class="hljs-type">double</span>&gt;(sp_inner_length_size));<br>        audio_in_model.<span class="hljs-built_in">read</span>(<span class="hljs-built_in">reinterpret_cast</span>&lt;<span class="hljs-type">char</span> *&gt;(&amp;_audioModel.spectrogram[n][<span class="hljs-number">0</span>]), std::<span class="hljs-built_in">streamsize</span>(sp_inner_length_size * <span class="hljs-built_in">sizeof</span>(<span class="hljs-type">double</span>)));<br>    }<br><br>    <span class="hljs-comment">// 6. 读取非周期性数据</span><br>    std::streamsize ap_length_size = <span class="hljs-number">0</span>;<br>    audio_in_model.<span class="hljs-built_in">read</span>(<span class="hljs-built_in">reinterpret_cast</span>&lt;<span class="hljs-type">char</span> *&gt;(&amp;ap_length_size), <span class="hljs-built_in">sizeof</span>(std::streamsize));<br>    <span class="hljs-keyword">for</span> (std::streamsize n = <span class="hljs-number">0</span>; n &lt; ap_length_size; ++n) {<br>        std::streamsize ap_inner_length_size = <span class="hljs-number">0</span>;<br>        audio_in_model.<span class="hljs-built_in">read</span>(<span class="hljs-built_in">reinterpret_cast</span>&lt;<span class="hljs-type">char</span> *&gt;(&amp;ap_inner_length_size), <span class="hljs-built_in">sizeof</span>(std::streamsize));<br>        _audioModel.aperiodicity.<span class="hljs-built_in">resize</span>(ap_length_size, std::<span class="hljs-built_in">vector</span>&lt;<span class="hljs-type">double</span>&gt;(ap_inner_length_size));<br>        audio_in_model.<span class="hljs-built_in">read</span>(<span class="hljs-built_in">reinterpret_cast</span>&lt;<span class="hljs-type">char</span> *&gt;(&amp;_audioModel.aperiodicity[n][<span class="hljs-number">0</span>]), std::<span class="hljs-built_in">streamsize</span>(ap_inner_length_size * <span class="hljs-built_in">sizeof</span>(<span class="hljs-type">double</span>)));<br>    }<br><br>    <span class="hljs-comment">// 7. 检查文件尾</span><br>    <span class="hljs-keyword">auto</span> end = <span class="hljs-keyword">new</span> <span class="hljs-type">char</span>[<span class="hljs-number">6</span>];<br>    audio_in_model.<span class="hljs-built_in">read</span>(end, <span class="hljs-built_in">sizeof</span>(<span class="hljs-type">char</span>) * <span class="hljs-number">6</span>);<br>    <span class="hljs-keyword">if</span> (std::<span class="hljs-built_in">string</span>(lessaudio_ending) != std::<span class="hljs-built_in">string</span>(end)) {<br>        <span class="hljs-keyword">throw</span> <span class="hljs-built_in">header_check_error</span>(end, lessaudio_ending);<br>    }<br>}<br></code></pre></td></tr></tbody></table></figure><h3 id="CheckAudioModel-模型检查" data-id="CheckAudioModel-模型检查" class="notion-h"><a href="#CheckAudioModel-模型检查" class="headerlink" title="CheckAudioModel() 模型检查"></a>CheckAudioModel() 模型检查</h3><figure class="highlight cpp"><table><tbody><tr><td class="code"><pre><code class="hljs cpp"><span class="hljs-function"><span class="hljs-type">bool</span> <span class="hljs-title">AudioModelIO::CheckAudioModel</span><span class="hljs-params">(lessConfigure configure)</span> </span>{<br>    _configure = std::<span class="hljs-built_in">move</span>(configure);<br><br>    <span class="hljs-keyword">if</span> (!<span class="hljs-built_in">CheckAudioModelFile</span>(audio_model_file_path)) {<br>        YALL_DEBUG_ &lt;&lt; <span class="hljs-string">"Audio Model NOT Exist."</span>;<br>        <span class="hljs-keyword">return</span> <span class="hljs-literal">false</span>;<br>    } <span class="hljs-keyword">else</span> {<br>        <span class="hljs-keyword">if</span> (!<span class="hljs-built_in">CheckAudioModelVersion</span>()) {<br>            YALL_WARN_ &lt;&lt; <span class="hljs-string">"Audio Model Exist, But Version Check Fail. Regenerate..."</span>;<br>            <span class="hljs-keyword">return</span> <span class="hljs-literal">false</span>;<br>        }<br>        YALL_DEBUG_ &lt;&lt; <span class="hljs-string">"Audio Model Exist."</span>;<br>        <span class="hljs-keyword">return</span> <span class="hljs-literal">true</span>;<br>    }<br>}<br></code></pre></td></tr></tbody></table></figure><h3 id="CheckAudioModelVersion-版本验证" data-id="CheckAudioModelVersion-版本验证" class="notion-h"><a href="#CheckAudioModelVersion-版本验证" class="headerlink" title="CheckAudioModelVersion() 版本验证"></a>CheckAudioModelVersion() 版本验证</h3><figure class="highlight cpp"><table><tbody><tr><td class="code"><pre><code class="hljs cpp"><span class="hljs-function"><span class="hljs-type">bool</span> <span class="hljs-title">AudioModelIO::CheckAudioModelVersion</span><span class="hljs-params">()</span> </span>{<br>    <span class="hljs-function">std::ifstream <span class="hljs-title">audio_in_model</span><span class="hljs-params">(audio_model_file_path, std::ios::in | std::ios::binary)</span></span>;<br><br>    <span class="hljs-comment">// 检查文件头</span><br>    <span class="hljs-keyword">auto</span> header = <span class="hljs-keyword">new</span> <span class="hljs-type">char</span>[<span class="hljs-number">6</span>];<br>    audio_in_model.<span class="hljs-built_in">read</span>(header, <span class="hljs-built_in">sizeof</span>(<span class="hljs-type">char</span>) * <span class="hljs-number">6</span>);<br>    <span class="hljs-keyword">if</span> (std::<span class="hljs-built_in">string</span>(lessaudio_header) != std::<span class="hljs-built_in">string</span>(header)) {<br>        <span class="hljs-keyword">return</span> <span class="hljs-literal">false</span>;<br>    }<br><br>    <span class="hljs-comment">// 检查版本校验码</span><br>    std::streamsize ver_string_size;<br>    audio_in_model.<span class="hljs-built_in">read</span>(<span class="hljs-built_in">reinterpret_cast</span>&lt;<span class="hljs-type">char</span> *&gt;(&amp;ver_string_size), <span class="hljs-built_in">sizeof</span>(std::streamsize));<br>    std::vector&lt;<span class="hljs-type">char</span>&gt; _temp(ver_string_size);<br>    audio_in_model.<span class="hljs-built_in">read</span>(<span class="hljs-built_in">reinterpret_cast</span>&lt;<span class="hljs-type">char</span> *&gt;(&amp;_temp[<span class="hljs-number">0</span>]), std::<span class="hljs-built_in">streamsize</span>(ver_string_size * <span class="hljs-built_in">sizeof</span>(<span class="hljs-type">char</span>)));<br>    <span class="hljs-function">std::string <span class="hljs-title">ver_string</span><span class="hljs-params">(_temp.begin(), _temp.end())</span></span>;<br><br>    <span class="hljs-keyword">if</span> (ver_string != _configure.<span class="hljs-built_in">get_version</span>()) {<br>        <span class="hljs-keyword">return</span> <span class="hljs-literal">false</span>;<br>    }<br>    <span class="hljs-keyword">return</span> <span class="hljs-literal">true</span>;<br>}<br></code></pre></td></tr></tbody></table></figure><hr><h2 id="GenerateAudioModel-类详解" data-id="GenerateAudioModel-类详解" class="notion-h"><a href="#GenerateAudioModel-类详解" class="headerlink" title="GenerateAudioModel 类详解"></a>GenerateAudioModel 类详解</h2><p>GenerateAudioModel 负责批量生成音频模型，使用多线程并行处理提高效率。</p><h3 id="类定义-1" data-id="类定义-1" class="notion-h"><a href="#类定义-1" class="headerlink" title="类定义"></a>类定义</h3><figure class="highlight cpp"><table><tbody><tr><td class="code"><pre><code class="hljs cpp"><span class="hljs-keyword">class</span> <span class="hljs-title class_">GenerateAudioModel</span> {<br><span class="hljs-keyword">public</span>:<br>    <span class="hljs-built_in">GenerateAudioModel</span>(std::filesystem::path path, lessConfigure configure);<br>    <span class="hljs-built_in">GenerateAudioModel</span>(<span class="hljs-type">char</span> *path, lessConfigure configure);  <span class="hljs-comment">// 单文件模式</span><br><br>    <span class="hljs-function"><span class="hljs-type">void</span> <span class="hljs-title">PrintWavFiles</span><span class="hljs-params">()</span></span>;<br><br><span class="hljs-keyword">private</span>:<br>    std::filesystem::path target_voice_path;<br>    std::vector&lt;std::filesystem::path&gt; wav_files;<br>    lessConfigure configure;<br><br><span class="hljs-keyword">private</span>:<br>    <span class="hljs-function"><span class="hljs-type">void</span> <span class="hljs-title">GetWavFileLists</span><span class="hljs-params">()</span></span>;           <span class="hljs-comment">// 获取 WAV 文件列表</span><br>    <span class="hljs-function"><span class="hljs-type">void</span> <span class="hljs-title">GenerateModelFromFile</span><span class="hljs-params">()</span></span>;     <span class="hljs-comment">// 多线程生成</span><br>    <span class="hljs-function"><span class="hljs-type">void</span> <span class="hljs-title">WavFileModel</span><span class="hljs-params">(<span class="hljs-type">const</span> std::filesystem::path &amp;wav_path)</span></span>;  <span class="hljs-comment">// 单文件处理</span><br><br>    <span class="hljs-comment">// 并行 for_each 实现</span><br>    <span class="hljs-function"><span class="hljs-keyword">template</span>&lt;<span class="hljs-keyword">class</span> I, <span class="hljs-keyword">class</span> F&gt;</span><br><span class="hljs-function">    <span class="hljs-type">void</span> <span class="hljs-title">for_each</span><span class="hljs-params">(<span class="hljs-type">size_t</span> thread_count, I begin, I end, F f)</span></span>;<br><br>    <span class="hljs-function"><span class="hljs-keyword">template</span>&lt;<span class="hljs-keyword">class</span> I, <span class="hljs-keyword">class</span> F&gt;</span><br><span class="hljs-function">    <span class="hljs-type">void</span> <span class="hljs-title">for_each</span><span class="hljs-params">(I begin, I end, F f)</span></span>;<br>};<br></code></pre></td></tr></tbody></table></figure><h3 id="GetWavFileLists-文件扫描" data-id="GetWavFileLists-文件扫描" class="notion-h"><a href="#GetWavFileLists-文件扫描" class="headerlink" title="GetWavFileLists() 文件扫描"></a>GetWavFileLists() 文件扫描</h3><figure class="highlight cpp"><table><tbody><tr><td class="code"><pre><code class="hljs cpp"><span class="hljs-function"><span class="hljs-type">void</span> <span class="hljs-title">GenerateAudioModel::GetWavFileLists</span><span class="hljs-params">()</span> </span>{<br>    YALL_INFO_ &lt;&lt; <span class="hljs-string">"Working on folder: "</span> + target_voice_path.<span class="hljs-built_in">string</span>();<br>    <span class="hljs-keyword">for</span> (<span class="hljs-type">const</span> <span class="hljs-keyword">auto</span> &amp;entry: std::filesystem::<span class="hljs-built_in">directory_iterator</span>(target_voice_path)) {<br>        <span class="hljs-keyword">if</span> (entry.<span class="hljs-built_in">path</span>().<span class="hljs-built_in">extension</span>().<span class="hljs-built_in">string</span>() == <span class="hljs-string">".wav"</span>) {<br>            wav_files.<span class="hljs-built_in">push_back</span>(entry.<span class="hljs-built_in">path</span>());<br>        }<br>    }<br>}<br></code></pre></td></tr></tbody></table></figure><p>使用 C++17 的 <code>std::filesystem</code> 遍历目录，筛选 <code>.wav</code> 文件。</p><h3 id="WavFileModel-单文件处理" data-id="WavFileModel-单文件处理" class="notion-h"><a href="#WavFileModel-单文件处理" class="headerlink" title="WavFileModel() 单文件处理"></a>WavFileModel() 单文件处理</h3><figure class="highlight cpp"><table><tbody><tr><td class="code"><pre><code class="hljs cpp"><span class="hljs-function"><span class="hljs-type">void</span> <span class="hljs-title">GenerateAudioModel::WavFileModel</span><span class="hljs-params">(<span class="hljs-type">const</span> std::filesystem::path &amp;wav_path)</span> </span>{<br>    <span class="hljs-comment">// 读取 WAV 文件</span><br>    <span class="hljs-keyword">auto</span> x_length = WavIO::<span class="hljs-built_in">GetAudioLength</span>(wav_path.<span class="hljs-built_in">string</span>().<span class="hljs-built_in">c_str</span>());<br>    <span class="hljs-keyword">auto</span> x = <span class="hljs-keyword">new</span> <span class="hljs-type">double</span>[x_length];<br>    <span class="hljs-keyword">auto</span> fs = WavIO::<span class="hljs-built_in">WavRead</span>(wav_path.<span class="hljs-built_in">string</span>().<span class="hljs-built_in">c_str</span>(), x);<br><br>    <span class="hljs-comment">// 应用 AutoAMP（可选）</span><br>    <span class="hljs-keyword">if</span> (configure.model_amp != <span class="hljs-number">0.0</span>) {<br>        YALL_DEBUG_ &lt;&lt; <span class="hljs-string">"Apply AMP Before Modeling"</span>;<br>        <span class="hljs-function">AutoAMP <span class="hljs-title">amp</span><span class="hljs-params">(x, x_length, configure.model_amp)</span></span>;<br>        x = amp.<span class="hljs-built_in">GetAMP</span>();<br>    }<br><br>    <span class="hljs-comment">// 执行 WORLD 分析</span><br>    <span class="hljs-function">AudioModel <span class="hljs-title">audioModel</span><span class="hljs-params">(x, x_length, fs, configure)</span></span>;<br>    <span class="hljs-keyword">auto</span> model = audioModel.<span class="hljs-built_in">GetAudioModel</span>();<br><br>    <span class="hljs-comment">// 保存音频模型</span><br>    <span class="hljs-function">AudioModelIO <span class="hljs-title">audioModelIO</span><span class="hljs-params">(wav_path.string(), model, configure)</span></span>;<br>    audioModelIO.<span class="hljs-built_in">SaveAudioModel</span>();<br>}<br></code></pre></td></tr></tbody></table></figure><h3 id="for-each-并行处理实现" data-id="for-each-并行处理实现" class="notion-h"><a href="#for-each-并行处理实现" class="headerlink" title="for_each() 并行处理实现"></a>for_each() 并行处理实现</h3><figure class="highlight cpp"><table><tbody><tr><td class="code"><pre><code class="hljs cpp"><span class="hljs-function"><span class="hljs-keyword">template</span>&lt;<span class="hljs-keyword">class</span> I, <span class="hljs-keyword">class</span> F&gt;</span><br><span class="hljs-function"><span class="hljs-type">void</span> <span class="hljs-title">GenerateAudioModel::for_each</span><span class="hljs-params">(<span class="hljs-type">size_t</span> thread_count, I begin, I end, F f)</span> </span>{<br>    I it = begin;<br><br>    <span class="hljs-comment">// 边界检查：空或单元素</span><br>    <span class="hljs-keyword">if</span> (it == end)<br>        <span class="hljs-keyword">return</span>;<br>    <span class="hljs-keyword">if</span> (++it == end) {<br>        <span class="hljs-built_in">f</span>(*begin);<br>        <span class="hljs-keyword">return</span>;<br>    }<br><br>    <span class="hljs-comment">// 硬件并发数为 0 时使用 1 线程</span><br>    <span class="hljs-keyword">if</span> (thread_count == <span class="hljs-number">0</span>) {<br>        thread_count = <span class="hljs-number">1</span>;<br>    }<br><br>    std::vector&lt;std::thread&gt; threads;<br>    threads.<span class="hljs-built_in">reserve</span>(thread_count - <span class="hljs-number">1</span>);<br><br>    <span class="hljs-comment">// 工作函数：从迭代器取元素并处理</span><br>    <span class="hljs-keyword">auto</span> worker_ = [&amp;begin, &amp;end, &amp;f] {<br>        <span class="hljs-keyword">while</span> (<span class="hljs-literal">true</span>) {<br>            I it;<br>            it = begin;<br>            <span class="hljs-keyword">if</span> (it == end)<br>                <span class="hljs-keyword">break</span>;<br>            ++begin;<br>            <span class="hljs-built_in">f</span>(*it);<br>        }<br>    };<br><br>    <span class="hljs-comment">// 创建工作线程</span><br>    <span class="hljs-keyword">for</span> (<span class="hljs-type">unsigned</span> i = <span class="hljs-number">0</span>; i &lt; thread_count - <span class="hljs-number">1</span>; ++i, ++it) {<br>        <span class="hljs-keyword">if</span> (it == end)<br>            <span class="hljs-keyword">break</span>;<br>        threads.<span class="hljs-built_in">emplace_back</span>(std::<span class="hljs-built_in">thread</span>(worker_));<br>    }<br><br>    <span class="hljs-comment">// 主线程也参与工作</span><br>    <span class="hljs-built_in">worker_</span>();<br><br>    <span class="hljs-comment">// 等待所有线程完成</span><br>    <span class="hljs-keyword">for</span> (<span class="hljs-keyword">auto</span> &amp;th: threads) {<br>        th.<span class="hljs-built_in">join</span>();<br>    }<br>}<br><br><span class="hljs-comment">// 默认使用硬件并发数的一半</span><br><span class="hljs-function"><span class="hljs-keyword">template</span>&lt;<span class="hljs-keyword">class</span> I, <span class="hljs-keyword">class</span> F&gt;</span><br><span class="hljs-function"><span class="hljs-type">void</span> <span class="hljs-title">GenerateAudioModel::for_each</span><span class="hljs-params">(I begin, I end, F f)</span> </span>{<br>    for_each(std::<span class="hljs-built_in">lround</span>(std::thread::<span class="hljs-built_in">hardware_concurrency</span>() / <span class="hljs-number">2</span>), begin, end, f);<br>}<br></code></pre></td></tr></tbody></table></figure><p><strong>并行处理要点</strong>：</p><ul><li>使用 <code>std::thread::hardware_concurrency()</code> 获取可用线程数</li><li>默认使用一半线程数，避免过度占用 CPU</li><li>主线程也参与处理，提高效率</li><li>使用 <code>std::join()</code> 确保所有线程完成</li></ul><hr><h2 id="WavIO-类详解" data-id="WavIO-类详解" class="notion-h"><a href="#WavIO-类详解" class="headerlink" title="WavIO 类详解"></a>WavIO 类详解</h2><p>WavIO 使用 libsndfile 库处理 WAV 文件读写。</p><h3 id="类定义-2" data-id="类定义-2" class="notion-h"><a href="#类定义-2" class="headerlink" title="类定义"></a>类定义</h3><figure class="highlight cpp"><table><tbody><tr><td class="code"><pre><code class="hljs cpp"><span class="hljs-keyword">class</span> <span class="hljs-title class_">WavIO</span> {<br><span class="hljs-keyword">public</span>:<br>    <span class="hljs-built_in">WavIO</span>() = <span class="hljs-keyword">default</span>;<br><br>    <span class="hljs-function"><span class="hljs-type">static</span> <span class="hljs-type">int</span> <span class="hljs-title">GetAudioLength</span><span class="hljs-params">(<span class="hljs-type">const</span> <span class="hljs-type">char</span> *filename)</span></span>;  <span class="hljs-comment">// 获取采样数</span><br>    <span class="hljs-function"><span class="hljs-type">static</span> <span class="hljs-type">int</span> <span class="hljs-title">WavRead</span><span class="hljs-params">(<span class="hljs-type">const</span> <span class="hljs-type">char</span> *FilePath, <span class="hljs-type">double</span> *output)</span></span>;  <span class="hljs-comment">// 读取 WAV</span><br>    <span class="hljs-function"><span class="hljs-type">static</span> <span class="hljs-type">void</span> <span class="hljs-title">WriteWav</span><span class="hljs-params">(<span class="hljs-type">const</span> std::filesystem::path &amp;path, <span class="hljs-type">double</span> *x, <span class="hljs-type">long</span> <span class="hljs-type">long</span> x_length, <span class="hljs-type">int</span> fs)</span></span>;  <span class="hljs-comment">// 写入 WAV</span><br>};<br></code></pre></td></tr></tbody></table></figure><h3 id="WavRead-WAV-读取" data-id="WavRead-WAV-读取" class="notion-h"><a href="#WavRead-WAV-读取" class="headerlink" title="WavRead() WAV 读取"></a>WavRead() WAV 读取</h3><figure class="highlight cpp"><table><tbody><tr><td class="code"><pre><code class="hljs cpp"><span class="hljs-keyword">extern</span> <span class="hljs-string">"C"</span> {<br><span class="hljs-meta">#<span class="hljs-keyword">include</span> <span class="hljs-string">&lt;sndfile.h&gt;</span></span><br>}<br><br><span class="hljs-function"><span class="hljs-type">int</span> <span class="hljs-title">WavIO::WavRead</span><span class="hljs-params">(<span class="hljs-type">const</span> <span class="hljs-type">char</span> *FilePath, <span class="hljs-type">double</span> *output)</span> </span>{<br>    SNDFILE *sf;<br>    SF_INFO info;<br>    info.format = <span class="hljs-number">0</span>;  <span class="hljs-comment">// 读取时自动检测格式</span><br><br>    sf = <span class="hljs-built_in">sf_open</span>(FilePath, SFM_READ, &amp;info);<br>    <span class="hljs-keyword">if</span> (sf == <span class="hljs-literal">nullptr</span>) {<br>        YALL_ERROR_ &lt;&lt; <span class="hljs-string">"Failed to open the file."</span>;<br>        <span class="hljs-built_in">exit</span>(<span class="hljs-number">-1</span>);<br>    }<br><br>    <span class="hljs-type">sf_count_t</span> f = info.frames;<br>    <span class="hljs-type">int</span> c = info.channels;<br>    <span class="hljs-keyword">auto</span> num_items = f * c;<br>    <span class="hljs-keyword">auto</span> buf = <span class="hljs-keyword">new</span> <span class="hljs-type">double</span>[num_items];<br><br>    <span class="hljs-comment">// 读取所有采样</span><br>    <span class="hljs-keyword">auto</span> num = <span class="hljs-built_in">sf_read_double</span>(sf, buf, num_items);<br>    <span class="hljs-built_in">sf_close</span>(sf);<br><br>    <span class="hljs-comment">// 复制数据到输出缓冲区</span><br>    <span class="hljs-keyword">for</span> (<span class="hljs-type">int</span> i = <span class="hljs-number">0</span>; i &lt; num; i += c) {<br>        <span class="hljs-keyword">for</span> (<span class="hljs-type">int</span> j = <span class="hljs-number">0</span>; j &lt; c; ++j) {<br>            <span class="hljs-keyword">if</span> ((i + j) &lt; f) {<br>                output[i + j] = buf[i + j];<br>            }<br>        }<br>    }<br><br>    <span class="hljs-comment">// 立体声转单声道处理</span><br>    <span class="hljs-keyword">if</span> (c &gt; <span class="hljs-number">1</span>) {<br>        YALL_WARN_ &lt;&lt; <span class="hljs-string">"Can't read stereo file for lessampler. handle it as mono."</span>;<br>        <span class="hljs-keyword">auto</span> temp_ = <span class="hljs-keyword">new</span> <span class="hljs-type">double</span>[num];<br>        <span class="hljs-keyword">for</span> (<span class="hljs-type">int</span> i = <span class="hljs-number">0</span>; i &lt; num; i++) {<br>            temp_[i] = <span class="hljs-number">0</span>;<br>            <span class="hljs-keyword">for</span> (<span class="hljs-type">int</span> j = <span class="hljs-number">0</span>; j &lt; info.channels; j++)<br>                temp_[i] += output[i * info.channels + j];<br>            temp_[i] /= info.channels;  <span class="hljs-comment">// 平均声道</span><br>        }<br><br>        <span class="hljs-keyword">for</span> (<span class="hljs-type">int</span> i = <span class="hljs-number">0</span>; i &lt; num; ++i) {<br>            output[i] = temp_[i];<br>        }<br>    }<br><br>    <span class="hljs-keyword">delete</span>[] buf;<br>    <span class="hljs-keyword">return</span> info.samplerate;  <span class="hljs-comment">// 返回采样率</span><br>}<br></code></pre></td></tr></tbody></table></figure><p><strong>立体声处理</strong>：</p><ul><li>lessampler 只支持单声道</li><li>如果输入是立体声，将两声道平均合并为单声道</li><li>公式：<code>mono = (left + right) / 2</code></li></ul><h3 id="WriteWav-WAV-写入" data-id="WriteWav-WAV-写入" class="notion-h"><a href="#WriteWav-WAV-写入" class="headerlink" title="WriteWav() WAV 写入"></a>WriteWav() WAV 写入</h3><figure class="highlight cpp"><table><tbody><tr><td class="code"><pre><code class="hljs cpp"><span class="hljs-function"><span class="hljs-type">void</span> <span class="hljs-title">WavIO::WriteWav</span><span class="hljs-params">(<span class="hljs-type">const</span> std::filesystem::path &amp;path, <span class="hljs-type">double</span> *x, <span class="hljs-type">long</span> <span class="hljs-type">long</span> x_length, <span class="hljs-type">int</span> fs)</span> </span>{<br>    SNDFILE *sf;<br>    SF_INFO info;<br><br>    <span class="hljs-comment">// 设置输出格式</span><br>    info.channels = <span class="hljs-number">1</span>;  <span class="hljs-comment">// 单声道</span><br>    info.samplerate = fs;<br>    info.frames = x_length;<br>    info.format = SF_FORMAT_WAV | SF_FORMAT_PCM_16;  <span class="hljs-comment">// 16-bit PCM WAV</span><br><br>    sf = <span class="hljs-built_in">sf_open</span>(path.<span class="hljs-built_in">string</span>().<span class="hljs-built_in">c_str</span>(), SFM_WRITE, &amp;info);<br>    <span class="hljs-keyword">if</span> (sf == <span class="hljs-literal">nullptr</span>) {<br>        YALL_ERROR_ &lt;&lt; <span class="hljs-string">"Failed to open the file."</span>;<br>        <span class="hljs-built_in">exit</span>(<span class="hljs-number">-1</span>);<br>    }<br><br>    <span class="hljs-built_in">sf_write_double</span>(sf, x, x_length);<br>    <span class="hljs-built_in">sf_close</span>(sf);<br>}<br></code></pre></td></tr></tbody></table></figure><p><strong>输出格式</strong>：</p><ul><li>WAV 格式（SF_FORMAT_WAV）</li><li>16-bit PCM（SF_FORMAT_PCM_16）</li><li>单声道</li></ul><hr><h2 id="JSONFileIO-类详解" data-id="JSONFileIO-类详解" class="notion-h"><a href="#JSONFileIO-类详解" class="headerlink" title="JSONFileIO 类详解"></a>JSONFileIO 类详解</h2><p>JSONFileIO 用于导出 JSON 格式的音频模型，主要用于调试和分析。</p><h3 id="JSON-输出格式" data-id="JSON-输出格式" class="notion-h"><a href="#JSON-输出格式" class="headerlink" title="JSON 输出格式"></a>JSON 输出格式</h3><figure class="highlight json"><table><tbody><tr><td class="code"><pre><code class="hljs json"><span class="hljs-punctuation">{</span><br>  <span class="hljs-attr">"NODE"</span><span class="hljs-punctuation">:</span> <span class="hljs-string">"LESS_F0_DOUBLE"</span><span class="hljs-punctuation">,</span><br>  <span class="hljs-attr">"F0LEN"</span><span class="hljs-punctuation">:</span> <span class="hljs-number">1024</span><span class="hljs-punctuation">,</span><br>  <span class="hljs-attr">"F0"</span><span class="hljs-punctuation">:</span> <span class="hljs-punctuation">[</span><span class="hljs-number">440.0</span><span class="hljs-punctuation">,</span> <span class="hljs-number">441.5</span><span class="hljs-punctuation">,</span> <span class="hljs-number">439.8</span><span class="hljs-punctuation">,</span> ...<span class="hljs-punctuation">]</span><span class="hljs-punctuation">,</span><br>  <span class="hljs-attr">"FFTSIZE"</span><span class="hljs-punctuation">:</span> <span class="hljs-number">1024</span><span class="hljs-punctuation">,</span><br>  <span class="hljs-attr">"WLEN"</span><span class="hljs-punctuation">:</span> <span class="hljs-number">513</span><span class="hljs-punctuation">,</span><br>  <span class="hljs-attr">"SEQ"</span><span class="hljs-punctuation">:</span> <span class="hljs-punctuation">[</span><br>    <span class="hljs-punctuation">[</span><span class="hljs-number">0.1</span><span class="hljs-punctuation">,</span> <span class="hljs-number">0.2</span><span class="hljs-punctuation">,</span> <span class="hljs-number">0.3</span><span class="hljs-punctuation">,</span> ...<span class="hljs-punctuation">]</span><span class="hljs-punctuation">,</span>  <span class="hljs-comment">// 第一帧频谱包络</span><br>    <span class="hljs-punctuation">[</span><span class="hljs-number">0.1</span><span class="hljs-punctuation">,</span> <span class="hljs-number">0.2</span><span class="hljs-punctuation">,</span> <span class="hljs-number">0.3</span><span class="hljs-punctuation">,</span> ...<span class="hljs-punctuation">]</span><span class="hljs-punctuation">,</span>  <span class="hljs-comment">// 第二帧频谱包络</span><br>    ...<br>  <span class="hljs-punctuation">]</span><span class="hljs-punctuation">,</span><br>  <span class="hljs-attr">"AP"</span><span class="hljs-punctuation">:</span> <span class="hljs-punctuation">[</span><br>    <span class="hljs-punctuation">[</span><span class="hljs-number">0.05</span><span class="hljs-punctuation">,</span> <span class="hljs-number">0.06</span><span class="hljs-punctuation">,</span> <span class="hljs-number">0.07</span><span class="hljs-punctuation">,</span> ...<span class="hljs-punctuation">]</span><span class="hljs-punctuation">,</span>  <span class="hljs-comment">// 第一帧非周期性</span><br>    <span class="hljs-punctuation">[</span><span class="hljs-number">0.05</span><span class="hljs-punctuation">,</span> <span class="hljs-number">0.06</span><span class="hljs-punctuation">,</span> <span class="hljs-number">0.07</span><span class="hljs-punctuation">,</span> ...<span class="hljs-punctuation">]</span><span class="hljs-punctuation">,</span>  <span class="hljs-comment">// 第二帧非周期性</span><br>    ...<br>  <span class="hljs-punctuation">]</span><br><span class="hljs-punctuation">}</span><br></code></pre></td></tr></tbody></table></figure><h3 id="SaveJsonModel-实现" data-id="SaveJsonModel-实现" class="notion-h"><a href="#SaveJsonModel-实现" class="headerlink" title="SaveJsonModel() 实现"></a>SaveJsonModel() 实现</h3><figure class="highlight cpp"><table><tbody><tr><td class="code"><pre><code class="hljs cpp"><span class="hljs-function"><span class="hljs-type">void</span> <span class="hljs-title">JSONFileIO::SaveJsonModel</span><span class="hljs-params">()</span> </span>{<br>    rapidjson::StringBuffer s;<br>    <span class="hljs-function">rapidjson::PrettyWriter&lt;rapidjson::StringBuffer&gt; <span class="hljs-title">writer</span><span class="hljs-params">(s)</span></span>;<br><br>    writer.<span class="hljs-built_in">StartObject</span>();<br><br>    <span class="hljs-comment">// 节点标识</span><br>    writer.<span class="hljs-built_in">Key</span>(<span class="hljs-string">"NODE"</span>);<br>    writer.<span class="hljs-built_in">String</span>(<span class="hljs-string">"LESS_F0_DOUBLE"</span>);<br><br>    <span class="hljs-comment">// F0 长度</span><br>    writer.<span class="hljs-built_in">Key</span>(<span class="hljs-string">"F0LEN"</span>);<br>    writer.<span class="hljs-built_in">Uint</span>(_audioModel.f<span class="hljs-number">0.</span><span class="hljs-built_in">size</span>());<br><br>    <span class="hljs-comment">// F0 数据</span><br>    writer.<span class="hljs-built_in">Key</span>(<span class="hljs-string">"F0"</span>);<br>    writer.<span class="hljs-built_in">StartArray</span>();<br>    <span class="hljs-keyword">for</span> (<span class="hljs-type">double</span> i : _audioModel.f0) {<br>        writer.<span class="hljs-built_in">Double</span>(i);<br>    }<br>    writer.<span class="hljs-built_in">EndArray</span>();<br><br>    <span class="hljs-comment">// FFT 大小</span><br>    writer.<span class="hljs-built_in">Key</span>(<span class="hljs-string">"FFTSIZE"</span>);<br>    writer.<span class="hljs-built_in">Int</span>(_audioModel.fft_size);<br><br>    <span class="hljs-comment">// W 长度</span><br>    writer.<span class="hljs-built_in">Key</span>(<span class="hljs-string">"WLEN"</span>);<br>    writer.<span class="hljs-built_in">Int</span>(_audioModel.w_length);<br><br>    <span class="hljs-comment">// 频谱包络数据</span><br>    writer.<span class="hljs-built_in">Key</span>(<span class="hljs-string">"SEQ"</span>);<br>    writer.<span class="hljs-built_in">StartArray</span>();<br>    <span class="hljs-keyword">for</span> (<span class="hljs-type">int</span> i = <span class="hljs-number">0</span>; i &lt; _audioModel.f<span class="hljs-number">0.</span><span class="hljs-built_in">size</span>(); ++i) {<br>        writer.<span class="hljs-built_in">StartArray</span>();<br>        <span class="hljs-keyword">for</span> (<span class="hljs-type">int</span> j = <span class="hljs-number">0</span>; j &lt; _audioModel.w_length; ++j) {<br>            writer.<span class="hljs-built_in">Double</span>(_audioModel.spectrogram[i][j]);<br>        }<br>        writer.<span class="hljs-built_in">EndArray</span>();<br>    }<br>    writer.<span class="hljs-built_in">EndArray</span>();<br><br>    <span class="hljs-comment">// 非周期性数据</span><br>    writer.<span class="hljs-built_in">Key</span>(<span class="hljs-string">"AP"</span>);<br>    writer.<span class="hljs-built_in">StartArray</span>();<br>    <span class="hljs-keyword">for</span> (<span class="hljs-type">int</span> i = <span class="hljs-number">0</span>; i &lt; _audioModel.f<span class="hljs-number">0.</span><span class="hljs-built_in">size</span>(); ++i) {<br>        writer.<span class="hljs-built_in">StartArray</span>();<br>        <span class="hljs-keyword">for</span> (<span class="hljs-type">int</span> j = <span class="hljs-number">0</span>; j &lt; _audioModel.w_length; ++j) {<br>            writer.<span class="hljs-built_in">Double</span>(_audioModel.aperiodicity[i][j]);<br>        }<br>        writer.<span class="hljs-built_in">EndArray</span>();<br>    }<br>    writer.<span class="hljs-built_in">EndArray</span>();<br><br>    writer.<span class="hljs-built_in">EndObject</span>();<br><br>    <span class="hljs-comment">// 写入文件</span><br>    <span class="hljs-function">std::ofstream <span class="hljs-title">fout</span><span class="hljs-params">(Path, std::ios::out)</span></span>;<br>    <span class="hljs-keyword">if</span> (!fout)<br>        <span class="hljs-keyword">throw</span> <span class="hljs-built_in">file_open_error</span>(Path);<br>    fout &lt;&lt; s.<span class="hljs-built_in">GetString</span>();<br>    fout.<span class="hljs-built_in">close</span>();<br>}<br></code></pre></td></tr></tbody></table></figure><hr><h2 id="数据流图" data-id="数据流图" class="notion-h"><a href="#数据流图" class="headerlink" title="数据流图"></a>数据流图</h2><figure class="highlight plaintext"><table><tbody><tr><td class="code"><pre><code class="hljs plaintext">WAV 文件输入<br>     │<br>     ▼<br>┌────────────┐<br>│   WavIO    │<br>│  WavRead   │<br>└────────────┘<br>     │<br>     ▼<br>PCM 数据 (x, x_length, fs)<br>     │<br>     ▼<br>┌─────────────────────┐<br>│ GenerateAudioModel  │<br>│  - GetWavFileLists  │ 扫描目录<br>│  - for_each         │ 多线程并行<br>│  - WavFileModel     │ 单文件处理<br>└─────────────────────┘<br>     │<br>     ├──────────────────┐<br>     │                  │<br>     ▼                  ▼<br>┌────────────┐    ┌────────────┐<br>│ AutoAMP    │    │ AudioModel │<br>│ (可选)      │    │ WORLD 分析  │<br>└────────────┘    └────────────┘<br>     │                  │<br>     └──────────────────┤<br>                        │<br>                        ▼<br>               lessAudioModel<br>                        │<br>                        ▼<br>               ┌──────────────────┐<br>               │ AudioModelIO     │<br>               │ SaveAudioModel   │<br>               └──────────────────┘<br>                        │<br>                        ▼<br>               .lessaudio 文件<br><br>加载流程：<br>.lessaudio 文件<br>     │<br>     ▼<br>┌────────────┐<br>│ AudioModelIO │<br>│ CheckAudioModel │ 验证版本<br>│ ReadAudioModel │ 读取数据<br>└────────────┘<br>     │<br>     ▼<br>lessAudioModel<br>     │<br>     ▼<br>AudioProcess → Synthesis → AutoAMP<br>     │<br>     ▼<br>┌────────────┐<br>│   WavIO    │<br>│  WriteWav  │<br>└────────────┘<br>     │<br>     ▼<br>输出 WAV 文件<br></code></pre></td></tr></tbody></table></figure><hr><h2 id="使用示例" data-id="使用示例" class="notion-h"><a href="#使用示例" class="headerlink" title="使用示例"></a>使用示例</h2><h3 id="批量生成音频模型" data-id="批量生成音频模型" class="notion-h"><a href="#批量生成音频模型" class="headerlink" title="批量生成音频模型"></a>批量生成音频模型</h3><figure class="highlight cpp"><table><tbody><tr><td class="code"><pre><code class="hljs cpp"><span class="hljs-meta">#<span class="hljs-keyword">include</span> <span class="hljs-string">"FileIO/GenerateAudioModel.h"</span></span><br><span class="hljs-meta">#<span class="hljs-keyword">include</span> <span class="hljs-string">"ConfigUnit/ConfigUnit.h"</span></span><br><br><span class="hljs-comment">// 加载配置</span><br><span class="hljs-function">ConfigUnit <span class="hljs-title">configUnit</span><span class="hljs-params">(exec_path)</span></span>;<br>lessConfigure configure = configUnit.<span class="hljs-built_in">GetConfig</span>();<br><br><span class="hljs-comment">// 批量生成音源库模型</span><br>std::filesystem::path voice_path = <span class="hljs-string">"path/to/voicebank"</span>;<br><span class="hljs-function">GenerateAudioModel <span class="hljs-title">generator</span><span class="hljs-params">(voice_path, configure)</span></span>;<br><br><span class="hljs-comment">// 自动扫描所有 WAV 文件并生成 .lessaudio</span><br></code></pre></td></tr></tbody></table></figure><h3 id="单文件模型生成" data-id="单文件模型生成" class="notion-h"><a href="#单文件模型生成" class="headerlink" title="单文件模型生成"></a>单文件模型生成</h3><figure class="highlight cpp"><table><tbody><tr><td class="code"><pre><code class="hljs cpp"><span class="hljs-comment">// 单个 WAV 文件生成模型</span><br><span class="hljs-function">GenerateAudioModel <span class="hljs-title">generator</span><span class="hljs-params">(<span class="hljs-string">"input.wav"</span>, configure)</span></span>;<br></code></pre></td></tr></tbody></table></figure><h3 id="读取和使用音频模型" data-id="读取和使用音频模型" class="notion-h"><a href="#读取和使用音频模型" class="headerlink" title="读取和使用音频模型"></a>读取和使用音频模型</h3><figure class="highlight cpp"><table><tbody><tr><td class="code"><pre><code class="hljs cpp"><span class="hljs-meta">#<span class="hljs-keyword">include</span> <span class="hljs-string">"FileIO/AudioModelIO.h"</span></span><br><br><span class="hljs-comment">// 创建 IO 对象</span><br><span class="hljs-function">AudioModelIO <span class="hljs-title">audioModelIO</span><span class="hljs-params">(<span class="hljs-string">"input.wav"</span>)</span></span>;<br><br><span class="hljs-comment">// 检查模型是否存在且版本匹配</span><br><span class="hljs-keyword">if</span> (!audioModelIO.<span class="hljs-built_in">CheckAudioModel</span>(configure)) {<br>    <span class="hljs-comment">// 需要重新生成</span><br>    <span class="hljs-function">GenerateAudioModel <span class="hljs-title">generator</span><span class="hljs-params">(<span class="hljs-string">"input.wav"</span>, configure)</span></span>;<br>}<br><br><span class="hljs-comment">// 读取模型</span><br>lessAudioModel model = audioModelIO.<span class="hljs-built_in">ReadAudioModel</span>(configure);<br><br><span class="hljs-comment">// 使用模型进行合成...</span><br></code></pre></td></tr></tbody></table></figure><h3 id="导出-JSON（调试）" data-id="导出-JSON（调试）" class="notion-h"><a href="#导出-JSON（调试）" class="headerlink" title="导出 JSON（调试）"></a>导出 JSON（调试）</h3><figure class="highlight cpp"><table><tbody><tr><td class="code"><pre><code class="hljs cpp"><span class="hljs-meta">#<span class="hljs-keyword">include</span> <span class="hljs-string">"FileIO/JSONFileIO.h"</span></span><br><br><span class="hljs-comment">// 导出 JSON 格式用于分析</span><br><span class="hljs-function">JSONFileIO <span class="hljs-title">jsonExporter</span><span class="hljs-params">(model, <span class="hljs-string">"model.json"</span>)</span></span>;<br></code></pre></td></tr></tbody></table></figure><hr><h2 id="文件大小估算" data-id="文件大小估算" class="notion-h"><a href="#文件大小估算" class="headerlink" title="文件大小估算"></a>文件大小估算</h2><p>对于典型的音频文件：</p><table><thead><tr><th>参数</th><th>值</th></tr></thead><tbody><tr><td>采样率</td><td>44100 Hz</td></tr><tr><td>时长</td><td>1 秒</td></tr><tr><td>帧周期</td><td>5.8 ms</td></tr><tr><td>帧数</td><td>~172 帧</td></tr><tr><td>FFT 大小</td><td>1024</td></tr><tr><td>W 长度</td><td>513</td></tr></tbody></table><p><strong>文件大小估算</strong>：</p><p>$$\begin{aligned}&amp;\text{Header} + \text{Version} &amp;\approx&amp; 50 \text{ bytes} \\&amp;\text{基本信息} &amp;\approx&amp; 5 \times sizeof(int/double) \approx 28 \text{ bytes} \\&amp;\text{F0} &amp;=&amp; 172 \times sizeof(double) \approx 1376 \text{ bytes} \\&amp;\text{SP} &amp;=&amp; 172 \times 513 \times sizeof(double) \approx 710 \text{ KB} \\&amp;\text{AP} &amp;=&amp; 172 \times 513 \times sizeof(double) \approx 710 \text{ KB} \\&amp;\text{Ending} &amp;\approx&amp; 5 \text{ bytes} \\\hline&amp;\text{总计} &amp;\approx&amp; 1.4 \text{ MB/秒}\end{aligned}$$</p><p>对于典型音源库（100 个音素，每音素 0.5 秒），模型文件约 70 MB。</p>]]>
    </content>
    <id>https://gloomyghost.com/live/2026-04-24-lessampler-fileio-module.aspx</id>
    <link href="https://gloomyghost.com/live/2026-04-24-lessampler-fileio-module.aspx"/>
    <published>2026-04-23T16:00:00.000Z</published>
    <summary>
      <![CDATA[<p>FileIO 模块负责 lessampler 的所有文件读写操作，包括音频模型文件的二进制存储、WAV 文件读写、JSON 导出以及批量模型生成。该模块是数据持久化的核心，确保音频分析结果能够高效存储并在后续合成时快速加载。</p>
<p>音频模型文件（<code>.les]]>
    </summary>
    <title>lessampler: FileIO 模块 - 音频模型的存储与加载</title>
    <updated>2026-05-29T20:33:31.218Z</updated>
  </entry>
  <entry>
    <author>
      <name>柚木 鉉</name>
    </author>
    <category term="UTAU" scheme="https://gloomyghost.com/tags/UTAU/"/>
    <category term="lessampler" scheme="https://gloomyghost.com/tags/lessampler/"/>
    <content>
      <![CDATA[<p>ConfigUnit 模块负责 lessampler 的配置管理，包括全局配置、音源库配置和版本校验。该模块采用 INI 文件格式存储配置，使用 inicpp 库进行解析，并通过 SHA-1 校验和确保配置参数的一致性。</p><p>配置系统是歌声合成器的重要组成部分，因为不同的配置参数会产生不同的音频分析结果。如果用户更改配置后使用了旧配置生成的音频模型，会导致合成结果不准确。因此，ConfigUnit 模块设计了完善的版本校验机制来解决这个问题。</p><h2 id="模块结构" data-id="模块结构" class="notion-h"><a href="#模块结构" class="headerlink" title="模块结构"></a>模块结构</h2><figure class="highlight plaintext"><table><tbody><tr><td class="code"><pre><code class="hljs plaintext">ConfigUnit/<br>├── lessConfigure.h/cpp    # 配置参数结构体<br>├── ConfigUnit.h/cpp       # 全局配置加载<br>├── ConfigVoiceBank.h/cpp  # 音源库配置<br>├── ConfigFileIO.h         # 文件读写工具<br>└── SHA1.h/cpp             # SHA-1 校验实现<br></code></pre></td></tr></tbody></table></figure><hr><h2 id="lessConfigure-类详解" data-id="lessConfigure-类详解" class="notion-h"><a href="#lessConfigure-类详解" class="headerlink" title="lessConfigure 类详解"></a>lessConfigure 类详解</h2><p>lessConfigure 是配置参数的核心数据结构，定义了所有可配置的分析参数。</p><h3 id="类定义" data-id="类定义" class="notion-h"><a href="#类定义" class="headerlink" title="类定义"></a>类定义</h3><figure class="highlight cpp"><table><tbody><tr><td class="code"><pre><code class="hljs cpp"><span class="hljs-keyword">class</span> <span class="hljs-title class_">lessConfigure</span> {<br><span class="hljs-keyword">public</span>:<br>    <span class="hljs-function">std::string <span class="hljs-title">get_version</span><span class="hljs-params">()</span></span>;  <span class="hljs-comment">// 获取校验版本号</span><br><br><span class="hljs-keyword">public</span>:<br>    <span class="hljs-comment">// F0 估计算法选择</span><br>    <span class="hljs-keyword">enum class</span> <span class="hljs-title class_">F0_MODE</span> {<br>        F0_MODE_UNKNOWN = <span class="hljs-number">0</span>,<br>        F0_MODE_HARVEST = <span class="hljs-number">1</span>,  <span class="hljs-comment">// 高精度，慢</span><br>        F0_MODE_DIO = <span class="hljs-number">2</span>,      <span class="hljs-comment">// 快速，中等精度</span><br>    };<br><br>    <span class="hljs-comment">// 配置参数</span><br>    std::string version;<br>    <span class="hljs-type">bool</span> debug_mode = <span class="hljs-literal">false</span>;<br>    <span class="hljs-type">double</span> model_amp = <span class="hljs-number">0.85</span>;                      <span class="hljs-comment">// AutoAMP 强度</span><br>    <span class="hljs-type">double</span> audio_model_frame_period = (<span class="hljs-number">1000.0</span> * <span class="hljs-number">256</span> / <span class="hljs-number">44100</span>);  <span class="hljs-comment">// 帧周期</span><br>    <span class="hljs-type">bool</span> custom_fft_size = <span class="hljs-literal">false</span>;                 <span class="hljs-comment">// 是否自定义 FFT</span><br>    <span class="hljs-type">int</span> fft_size = <span class="hljs-number">1024</span>;                          <span class="hljs-comment">// FFT 大小</span><br>    F0_MODE f0_mode = F0_MODE::F0_MODE_HARVEST;  <span class="hljs-comment">// F0 算法</span><br>    <span class="hljs-type">int</span> f0_speed = <span class="hljs-number">1</span>;                             <span class="hljs-comment">// DIO 速度系数</span><br>    <span class="hljs-type">double</span> f0_dio_floor = <span class="hljs-number">40.0</span>;                   <span class="hljs-comment">// DIO F0 下限</span><br>    <span class="hljs-type">double</span> f0_harvest_floor = <span class="hljs-number">40.0</span>;               <span class="hljs-comment">// Harvest F0 下限</span><br>    <span class="hljs-type">double</span> f0_cheap_trick_floor = <span class="hljs-number">71.0</span>;           <span class="hljs-comment">// CheapTrick F0 下限</span><br>    <span class="hljs-type">double</span> f0_allow_range = <span class="hljs-number">0.1</span>;                  <span class="hljs-comment">// DIO 允许范围</span><br>    <span class="hljs-type">double</span> ap_threshold = <span class="hljs-number">0.10</span>;                   <span class="hljs-comment">// D4C 阈值</span><br><br><span class="hljs-keyword">public</span>:<br>    <span class="hljs-function"><span class="hljs-type">static</span> std::string <span class="hljs-title">get_f0_mode_str</span><span class="hljs-params">(F0_MODE f0_mode)</span></span>;<br><br><span class="hljs-keyword">private</span>:<br>    std::string version_data;<br>    <span class="hljs-function"><span class="hljs-type">void</span> <span class="hljs-title">make_ver</span><span class="hljs-params">()</span></span>;  <span class="hljs-comment">// 生成版本校验码</span><br>};<br></code></pre></td></tr></tbody></table></figure><h3 id="参数详解" data-id="参数详解" class="notion-h"><a href="#参数详解" class="headerlink" title="参数详解"></a>参数详解</h3><table><thead><tr><th>参数</th><th>默认值</th><th>作用</th><th>影响</th></tr></thead><tbody><tr><td><code>model_amp</code></td><td>0.85</td><td>AutoAMP 强度</td><td>音量归一化</td></tr><tr><td><code>audio_model_frame_period</code></td><td>~5.8ms</td><td>分析帧周期</td><td>时间分辨率</td></tr><tr><td><code>fft_size</code></td><td>1024</td><td>FFT 大小</td><td>频谱分辨率</td></tr><tr><td><code>f0_mode</code></td><td>HARVEST</td><td>F0 算法</td><td>分析精度/速度</td></tr><tr><td><code>f0_speed</code></td><td>1</td><td>DIO 降采样</td><td>DIO 处理速度</td></tr><tr><td><code>f0_dio_floor</code></td><td>40Hz</td><td>DIO F0 下限</td><td>低音分析范围</td></tr><tr><td><code>f0_harvest_floor</code></td><td>40Hz</td><td>Harvest F0 下限</td><td>低音分析范围</td></tr><tr><td><code>f0_cheap_trick_floor</code></td><td>71Hz</td><td>CheapTrick F0 下限</td><td>频谱包络质量</td></tr><tr><td><code>f0_allow_range</code></td><td>0.1</td><td>DIO 允许范围</td><td>F0 稳定性</td></tr><tr><td><code>ap_threshold</code></td><td>0.10</td><td>D4C 阈值</td><td>清浊音判定</td></tr></tbody></table><h3 id="make-ver-版本校验生成" data-id="make-ver-版本校验生成" class="notion-h"><a href="#make-ver-版本校验生成" class="headerlink" title="make_ver() 版本校验生成"></a>make_ver() 版本校验生成</h3><figure class="highlight cpp"><table><tbody><tr><td class="code"><pre><code class="hljs cpp"><span class="hljs-function"><span class="hljs-type">void</span> <span class="hljs-title">lessConfigure::make_ver</span><span class="hljs-params">()</span> </span>{<br>    <span class="hljs-comment">// 将所有关键参数拼接为字符串</span><br>    version_data = std::<span class="hljs-built_in">to_string</span>(audio_model_frame_period)<br>                   + std::<span class="hljs-built_in">to_string</span>(fft_size)<br>                   + std::<span class="hljs-built_in">to_string</span>(model_amp)<br>                   + std::<span class="hljs-built_in">to_string</span>(f0_speed)<br>                   + std::<span class="hljs-built_in">to_string</span>(f0_dio_floor)<br>                   + std::<span class="hljs-built_in">to_string</span>(f0_harvest_floor)<br>                   + std::<span class="hljs-built_in">to_string</span>(f0_cheap_trick_floor)<br>                   + std::<span class="hljs-built_in">to_string</span>(f0_allow_range)<br>                   + std::<span class="hljs-built_in">to_string</span>(ap_threshold)<br>                   + <span class="hljs-built_in">get_f0_mode_str</span>(f0_mode);<br><br>    <span class="hljs-comment">// 使用 SHA-1 生成校验码</span><br>    SHA1 checksum;<br>    checksum.<span class="hljs-built_in">update</span>(version_data);<br>    version_data = checksum.<span class="hljs-built_in">final</span>();  <span class="hljs-comment">// 返回 40 字符十六进制字符串</span><br>}<br></code></pre></td></tr></tbody></table></figure><p><strong>版本校验机制原理</strong>：</p><ol><li><strong>参数串联</strong>：将所有影响音频分析结果的参数连接成字符串</li><li><strong>SHA-1 哈希</strong>：生成 160 位（40 字符）的唯一校验码</li><li><strong>模型绑定</strong>：音频模型文件存储生成时的校验码</li><li><strong>加载验证</strong>：加载模型时比对校验码，确保配置一致</li></ol><hr><h2 id="SHA1-类实现" data-id="SHA1-类实现" class="notion-h"><a href="#SHA1-类实现" class="headerlink" title="SHA1 类实现"></a>SHA1 类实现</h2><p>SHA1 类提供了 SHA-1 哈希算法的 C++ 实现，用于生成配置版本校验码。</p><h3 id="类定义-1" data-id="类定义-1" class="notion-h"><a href="#类定义-1" class="headerlink" title="类定义"></a>类定义</h3><figure class="highlight cpp"><table><tbody><tr><td class="code"><pre><code class="hljs cpp"><span class="hljs-keyword">class</span> <span class="hljs-title class_">SHA1</span> {<br><span class="hljs-keyword">public</span>:<br>    <span class="hljs-built_in">SHA1</span>();<br><br>    <span class="hljs-function"><span class="hljs-type">void</span> <span class="hljs-title">update</span><span class="hljs-params">(<span class="hljs-type">const</span> std::string &amp;s)</span></span>;  <span class="hljs-comment">// 添加数据</span><br>    <span class="hljs-function"><span class="hljs-type">void</span> <span class="hljs-title">update</span><span class="hljs-params">(std::istream &amp;is)</span></span>;      <span class="hljs-comment">// 从流添加</span><br>    <span class="hljs-function">std::string <span class="hljs-title">final</span><span class="hljs-params">()</span></span>;                <span class="hljs-comment">// 完成并返回哈希</span><br><br>    <span class="hljs-function"><span class="hljs-type">static</span> std::string <span class="hljs-title">from_file</span><span class="hljs-params">(<span class="hljs-type">const</span> std::string &amp;filename)</span></span>;  <span class="hljs-comment">// 文件哈希</span><br><br><span class="hljs-keyword">private</span>:<br>    <span class="hljs-type">uint32_t</span> digest[<span class="hljs-number">5</span>]{};      <span class="hljs-comment">// 5 个 32 位哈希值（共 160 位）</span><br>    std::string buffer;        <span class="hljs-comment">// 输入缓冲区</span><br>    <span class="hljs-type">uint64_t</span> transforms{};     <span class="hljs-comment">// 变换次数</span><br>};<br></code></pre></td></tr></tbody></table></figure><h3 id="SHA-1-算法核心" data-id="SHA-1-算法核心" class="notion-h"><a href="#SHA-1-算法核心" class="headerlink" title="SHA-1 算法核心"></a>SHA-1 算法核心</h3><p>SHA-1 算法将输入数据分块处理，每块 512 位（64 字节）：</p><figure class="highlight cpp"><table><tbody><tr><td class="code"><pre><code class="hljs cpp"><span class="hljs-function"><span class="hljs-type">void</span> <span class="hljs-title">transform</span><span class="hljs-params">(<span class="hljs-type">uint32_t</span> digest[], <span class="hljs-type">uint32_t</span> block[<span class="hljs-number">16</span>], <span class="hljs-type">uint64_t</span> &amp;transforms)</span> </span>{<br>    <span class="hljs-comment">// 复制当前哈希值到工作变量</span><br>    <span class="hljs-type">uint32_t</span> a = digest[<span class="hljs-number">0</span>];<br>    <span class="hljs-type">uint32_t</span> b = digest[<span class="hljs-number">1</span>];<br>    <span class="hljs-type">uint32_t</span> c = digest[<span class="hljs-number">2</span>];<br>    <span class="hljs-type">uint32_t</span> d = digest[<span class="hljs-number">3</span>];<br>    <span class="hljs-type">uint32_t</span> e = digest[<span class="hljs-number">4</span>];<br><br>    <span class="hljs-comment">// 80 轮变换（分为 4 组，每组 20 轮）</span><br>    <span class="hljs-comment">// R0: 轮 0-15（使用原始数据）</span><br>    <span class="hljs-comment">// R1: 轮 16-19（使用扩展数据）</span><br>    <span class="hljs-comment">// R2: 轮 20-39</span><br>    <span class="hljs-comment">// R3: 轮 40-59</span><br>    <span class="hljs-comment">// R4: 轮 60-79</span><br><br>    <span class="hljs-comment">// ... 80 轮处理 ...</span><br><br>    <span class="hljs-comment">// 更新哈希值</span><br>    digest[<span class="hljs-number">0</span>] += a;<br>    digest[<span class="hljs-number">1</span>] += b;<br>    digest[<span class="hljs-number">2</span>] += c;<br>    digest[<span class="hljs-number">3</span>] += d;<br>    digest[<span class="hljs-number">4</span>] += e;<br><br>    transforms++;<br>}<br></code></pre></td></tr></tbody></table></figure><p><strong>SHA-1 处理流程</strong>：</p><ol><li><strong>初始化</strong>：设置 5 个初始哈希值（魔数）</li><li><strong>填充</strong>：将输入数据补齐到 512 位块边界</li><li><strong>分块处理</strong>：每块进行 80 轮变换</li><li><strong>输出</strong>：拼接 5 个 32 位值，转为 40 字符十六进制</li></ol><h3 id="final-输出函数" data-id="final-输出函数" class="notion-h"><a href="#final-输出函数" class="headerlink" title="final() 输出函数"></a>final() 输出函数</h3><figure class="highlight cpp"><table><tbody><tr><td class="code"><pre><code class="hljs cpp"><span class="hljs-function">std::string <span class="hljs-title">SHA1::final</span><span class="hljs-params">()</span> </span>{<br>    <span class="hljs-comment">// 计算总位数</span><br>    <span class="hljs-type">uint64_t</span> total_bits = (transforms * <span class="hljs-number">64</span> + buffer.<span class="hljs-built_in">size</span>()) * <span class="hljs-number">8</span>;<br><br>    <span class="hljs-comment">// 填充：添加 0x80，然后补零</span><br>    buffer += (<span class="hljs-type">char</span>) <span class="hljs-number">0x80</span>;<br>    <span class="hljs-keyword">while</span> (buffer.<span class="hljs-built_in">size</span>() &lt; <span class="hljs-number">64</span>) {<br>        buffer += (<span class="hljs-type">char</span>) <span class="hljs-number">0x00</span>;<br>    }<br><br>    <span class="hljs-comment">// 处理填充块</span><br>    <span class="hljs-type">uint32_t</span> block[<span class="hljs-number">16</span>];<br>    <span class="hljs-built_in">buffer_to_block</span>(buffer, block);<br><br>    <span class="hljs-comment">// 如果填充超出一个块，处理两个块</span><br>    <span class="hljs-keyword">if</span> (orig_size &gt; <span class="hljs-number">64</span> - <span class="hljs-number">8</span>) {<br>        <span class="hljs-built_in">transform</span>(digest, block, transforms);<br>        <span class="hljs-keyword">for</span> (<span class="hljs-type">size_t</span> i = <span class="hljs-number">0</span>; i &lt; <span class="hljs-number">16</span> - <span class="hljs-number">2</span>; i++) {<br>            block[i] = <span class="hljs-number">0</span>;<br>        }<br>    }<br><br>    <span class="hljs-comment">// 在最后 8 字节存储总位数</span><br>    block[<span class="hljs-number">16</span> - <span class="hljs-number">1</span>] = (<span class="hljs-type">uint32_t</span>) total_bits;<br>    block[<span class="hljs-number">16</span> - <span class="hljs-number">2</span>] = (<span class="hljs-type">uint32_t</span>) (total_bits &gt;&gt; <span class="hljs-number">32</span>);<br>    <span class="hljs-built_in">transform</span>(digest, block, transforms);<br><br>    <span class="hljs-comment">// 输出十六进制字符串</span><br>    std::ostringstream result;<br>    <span class="hljs-keyword">for</span> (<span class="hljs-type">unsigned</span> <span class="hljs-type">int</span> i: digest) {<br>        result &lt;&lt; std::hex &lt;&lt; std::<span class="hljs-built_in">setfill</span>(<span class="hljs-string">'0'</span>) &lt;&lt; std::<span class="hljs-built_in">setw</span>(<span class="hljs-number">8</span>);<br>        result &lt;&lt; i;<br>    }<br><br>    <span class="hljs-keyword">return</span> result.<span class="hljs-built_in">str</span>();  <span class="hljs-comment">// 40 字符十六进制</span><br>}<br></code></pre></td></tr></tbody></table></figure><hr><h2 id="ConfigUnit-类详解" data-id="ConfigUnit-类详解" class="notion-h"><a href="#ConfigUnit-类详解" class="headerlink" title="ConfigUnit 类详解"></a>ConfigUnit 类详解</h2><p>ConfigUnit 负责全局配置的加载和管理。</p><h3 id="类定义-2" data-id="类定义-2" class="notion-h"><a href="#类定义-2" class="headerlink" title="类定义"></a>类定义</h3><figure class="highlight cpp"><table><tbody><tr><td class="code"><pre><code class="hljs cpp"><span class="hljs-keyword">class</span> <span class="hljs-title class_">ConfigUnit</span> {<br><span class="hljs-keyword">public</span>:<br>    <span class="hljs-function"><span class="hljs-keyword">explicit</span> <span class="hljs-title">ConfigUnit</span><span class="hljs-params">(<span class="hljs-type">const</span> std::filesystem::path &amp;exec_path)</span></span>;<br><br>    <span class="hljs-function"><span class="hljs-type">void</span> <span class="hljs-title">SetConfig</span><span class="hljs-params">(<span class="hljs-type">const</span> std::filesystem::path &amp;exec_path)</span></span>;<br>    ~<span class="hljs-built_in">ConfigUnit</span>();<br><br>    <span class="hljs-function">lessConfigure <span class="hljs-title">GetConfig</span><span class="hljs-params">()</span> <span class="hljs-type">const</span></span>;<br><br><span class="hljs-keyword">private</span>:<br>    std::filesystem::path config_file_path;<br>    std::string config_file_data_string;<br>    inicpp::config config;<br>    inicpp::schema config_schema;<br>    lessConfigure configure;<br><br><span class="hljs-keyword">private</span>:<br>    <span class="hljs-function"><span class="hljs-type">void</span> <span class="hljs-title">make_schema</span><span class="hljs-params">()</span></span>;           <span class="hljs-comment">// 创建配置结构</span><br>    <span class="hljs-function"><span class="hljs-type">void</span> <span class="hljs-title">create_default_config</span><span class="hljs-params">()</span></span>; <span class="hljs-comment">// 生成默认配置</span><br>    <span class="hljs-function"><span class="hljs-type">void</span> <span class="hljs-title">parse_config</span><span class="hljs-params">()</span></span>;          <span class="hljs-comment">// 解析配置文件</span><br>};<br></code></pre></td></tr></tbody></table></figure><h3 id="SetConfig-加载流程" data-id="SetConfig-加载流程" class="notion-h"><a href="#SetConfig-加载流程" class="headerlink" title="SetConfig() 加载流程"></a>SetConfig() 加载流程</h3><figure class="highlight cpp"><table><tbody><tr><td class="code"><pre><code class="hljs cpp"><span class="hljs-function"><span class="hljs-type">void</span> <span class="hljs-title">ConfigUnit::SetConfig</span><span class="hljs-params">(<span class="hljs-type">const</span> std::filesystem::path &amp;exec_path)</span> </span>{<br>    <span class="hljs-comment">// 构建配置文件路径</span><br>    <span class="hljs-keyword">this</span>-&gt;config_file_path = exec_path / CONFIGFILENAME;  <span class="hljs-comment">// "lessconfig.ini"</span><br><br>    <span class="hljs-comment">// 创建配置结构（定义有效参数）</span><br>    <span class="hljs-built_in">make_schema</span>();<br><br>    <span class="hljs-comment">// 检查配置文件是否存在</span><br>    <span class="hljs-keyword">if</span> (std::filesystem::<span class="hljs-built_in">exists</span>(config_file_path)) {<br>        <span class="hljs-comment">// 存在：读取并解析</span><br>        config_file_data_string = ConfigFileIO::<span class="hljs-built_in">read_config_file</span>(config_file_path);<br>        <span class="hljs-built_in">parse_config</span>();<br>    } <span class="hljs-keyword">else</span> {<br>        <span class="hljs-comment">// 不存在：生成默认配置</span><br>        <span class="hljs-built_in">create_default_config</span>();<br>        ConfigFileIO::<span class="hljs-built_in">save_config_file</span>(config_file_path, config_file_data_string);<br>        <span class="hljs-built_in">parse_config</span>();<br>    }<br>}<br></code></pre></td></tr></tbody></table></figure><h3 id="make-schema-配置结构定义" data-id="make-schema-配置结构定义" class="notion-h"><a href="#make-schema-配置结构定义" class="headerlink" title="make_schema() 配置结构定义"></a>make_schema() 配置结构定义</h3><figure class="highlight cpp"><table><tbody><tr><td class="code"><pre><code class="hljs cpp"><span class="hljs-function"><span class="hljs-type">void</span> <span class="hljs-title">ConfigUnit::make_schema</span><span class="hljs-params">()</span> </span>{<br>    <span class="hljs-comment">// [config] 全局设置部分</span><br>    inicpp::section_schema_params section_config_params{};<br>    section_config_params.name = <span class="hljs-string">"config"</span>;<br>    section_config_params.comment = <span class="hljs-string">"\n============ Gobal Settings ===========\n"</span>;<br>    section_config_params.requirement = inicpp::item_requirement::mandatory;<br>    config_schema.<span class="hljs-built_in">add_section</span>(section_config_params);<br><br>    <span class="hljs-comment">// version: 软件版本号</span><br>    inicpp::option_schema_params&lt;inicpp::<span class="hljs-type">string_ini_t</span>&gt; version{};<br>    version.name = <span class="hljs-string">"version"</span>;<br>    version.default_value = PROJECT_GIT_HASH;<br>    config_schema.<span class="hljs-built_in">add_option</span>(<span class="hljs-string">"config"</span>, version);<br><br>    <span class="hljs-comment">// debug: 调试模式开关</span><br>    inicpp::option_schema_params&lt;inicpp::<span class="hljs-type">boolean_ini_t</span>&gt; debug{};<br>    debug.name = <span class="hljs-string">"debug"</span>;<br>    debug.default_value = <span class="hljs-string">"0"</span>;<br>    config_schema.<span class="hljs-built_in">add_option</span>(<span class="hljs-string">"config"</span>, debug);<br><br>    <span class="hljs-comment">// [audio_model] 音频模型部分</span><br>    inicpp::section_schema_params section_audio_model_params{};<br>    section_audio_model_params.name = <span class="hljs-string">"audio_model"</span>;<br>    section_audio_model_params.comment =<br>        <span class="hljs-string">"\n========= Audio Model Settings ========\n"</span><br>        <span class="hljs-string">"Note: modifying any of the parameters here will require remodeling the voice db\n"</span>;<br>    config_schema.<span class="hljs-built_in">add_section</span>(section_audio_model_params);<br><br>    <span class="hljs-comment">// frame_period: 帧周期</span><br>    inicpp::option_schema_params&lt;inicpp::<span class="hljs-type">float_ini_t</span>&gt; frame_period{};<br>    frame_period.name = <span class="hljs-string">"frame_period"</span>;<br>    frame_period.default_value = std::<span class="hljs-built_in">to_string</span>(configure.audio_model_frame_period);<br>    config_schema.<span class="hljs-built_in">add_option</span>(<span class="hljs-string">"audio_model"</span>, frame_period);<br><br>    <span class="hljs-comment">// fft_size: FFT 大小（支持 "auto" 或数值）</span><br>    inicpp::option_schema_params&lt;inicpp::<span class="hljs-type">float_ini_t</span>&gt; fft_size{};<br>    fft_size.name = <span class="hljs-string">"fft_size"</span>;<br>    fft_size.default_value = <span class="hljs-string">"auto"</span>;<br>    config_schema.<span class="hljs-built_in">add_option</span>(<span class="hljs-string">"audio_model"</span>, fft_size);<br><br>    <span class="hljs-comment">// model_amp: AutoAMP 强度</span><br>    inicpp::option_schema_params&lt;inicpp::<span class="hljs-type">float_ini_t</span>&gt; model_amp{};<br>    model_amp.name = <span class="hljs-string">"model_amp"</span>;<br>    model_amp.default_value = std::<span class="hljs-built_in">to_string</span>(configure.model_amp);<br>    model_amp.comment = <span class="hljs-string">"Apply AutoAMP before Modeling..."</span>;<br>    config_schema.<span class="hljs-built_in">add_option</span>(<span class="hljs-string">"audio_model"</span>, model_amp);<br><br>    <span class="hljs-comment">// [f0] F0 估计部分</span><br>    <span class="hljs-comment">// ... 类似结构定义 f0_mode, f0_speed, f0_floor 等参数</span><br><br>    <span class="hljs-comment">// [ap] 非周期性部分</span><br>    <span class="hljs-comment">// ... 定义 ap_threshold 参数</span><br>}<br></code></pre></td></tr></tbody></table></figure><h3 id="parse-config-解析函数" data-id="parse-config-解析函数" class="notion-h"><a href="#parse-config-解析函数" class="headerlink" title="parse_config() 解析函数"></a>parse_config() 解析函数</h3><figure class="highlight cpp"><table><tbody><tr><td class="code"><pre><code class="hljs cpp"><span class="hljs-function"><span class="hljs-type">void</span> <span class="hljs-title">ConfigUnit::parse_config</span><span class="hljs-params">()</span> </span>{<br>    <span class="hljs-comment">// 使用 inicpp 解析 INI 文件</span><br>    config = inicpp::parser::<span class="hljs-built_in">load</span>(config_file_data_string);<br><br>    <span class="hljs-comment">// 解析 [config] 部分</span><br>    <span class="hljs-keyword">auto</span> config_section = config[<span class="hljs-string">"config"</span>];<br>    configure.version = config_section[<span class="hljs-string">"version"</span>].<span class="hljs-built_in">get</span>&lt;inicpp::<span class="hljs-type">string_ini_t</span>&gt;();<br>    <span class="hljs-keyword">if</span> (configure.version != PROJECT_GIT_HASH) {<br>        YALL_WARN_ &lt;&lt; <span class="hljs-string">"Configure file version does not match software version."</span>;<br>    }<br>    configure.debug_mode = config_section[<span class="hljs-string">"debug"</span>].<span class="hljs-built_in">get</span>&lt;inicpp::<span class="hljs-type">boolean_ini_t</span>&gt;();<br><br>    <span class="hljs-comment">// 解析 [audio_model] 部分</span><br>    <span class="hljs-keyword">auto</span> audio_model_section = config[<span class="hljs-string">"audio_model"</span>];<br>    configure.audio_model_frame_period =<br>        audio_model_section[<span class="hljs-string">"frame_period"</span>].<span class="hljs-built_in">get</span>&lt;inicpp::<span class="hljs-type">float_ini_t</span>&gt;();<br>    configure.model_amp =<br>        audio_model_section[<span class="hljs-string">"model_amp"</span>].<span class="hljs-built_in">get</span>&lt;inicpp::<span class="hljs-type">float_ini_t</span>&gt;();<br><br>    <span class="hljs-comment">// FFT 大小：特殊处理 "auto" 字符串</span><br>    <span class="hljs-keyword">if</span> (audio_model_section[<span class="hljs-string">"fft_size"</span>].<span class="hljs-built_in">get</span>&lt;inicpp::<span class="hljs-type">string_ini_t</span>&gt;() == <span class="hljs-string">"auto"</span>) {<br>        configure.fft_size = <span class="hljs-number">0</span>;<br>        configure.custom_fft_size = <span class="hljs-literal">false</span>;<br>    } <span class="hljs-keyword">else</span> {<br>        std::stringstream ss;<br>        ss &lt;&lt; audio_model_section[<span class="hljs-string">"fft_size"</span>].<span class="hljs-built_in">get</span>&lt;inicpp::<span class="hljs-type">string_ini_t</span>&gt;();<br>        ss &gt;&gt; configure.fft_size;<br>        configure.custom_fft_size = <span class="hljs-literal">true</span>;<br>    }<br><br>    <span class="hljs-comment">// 解析 [f0] 部分</span><br>    <span class="hljs-keyword">auto</span> f0_section = config[<span class="hljs-string">"f0"</span>];<br><br>    <span class="hljs-comment">// F0 算法：字符串转枚举</span><br>    configure.f0_mode = [&amp;]() -&gt; lessConfigure::F0_MODE {<br>        <span class="hljs-keyword">auto</span> f0_mode = f0_section[<span class="hljs-string">"f0_mode"</span>].<span class="hljs-built_in">get</span>&lt;inicpp::<span class="hljs-type">string_ini_t</span>&gt;();<br>        std::<span class="hljs-built_in">transform</span>(f0_mode.<span class="hljs-built_in">begin</span>(), f0_mode.<span class="hljs-built_in">end</span>(), f0_mode.<span class="hljs-built_in">begin</span>(), ::toupper);<br>        <span class="hljs-keyword">if</span> (f0_mode == <span class="hljs-string">"DIO"</span>)<br>            <span class="hljs-keyword">return</span> lessConfigure::F0_MODE::F0_MODE_DIO;<br>        <span class="hljs-keyword">else</span> <span class="hljs-keyword">if</span> (f0_mode == <span class="hljs-string">"HARVEST"</span>)<br>            <span class="hljs-keyword">return</span> lessConfigure::F0_MODE::F0_MODE_HARVEST;<br>        <span class="hljs-keyword">else</span><br>            <span class="hljs-keyword">return</span> lessConfigure::F0_MODE::F0_MODE_UNKNOWN;<br>    }();<br><br>    configure.f0_speed = <span class="hljs-built_in">static_cast</span>&lt;<span class="hljs-type">int</span>&gt;(f0_section[<span class="hljs-string">"f0_speed"</span>].<span class="hljs-built_in">get</span>&lt;inicpp::<span class="hljs-type">signed_ini_t</span>&gt;());<br>    configure.f0_dio_floor = f0_section[<span class="hljs-string">"f0_dio_floor"</span>].<span class="hljs-built_in">get</span>&lt;inicpp::<span class="hljs-type">float_ini_t</span>&gt;();<br>    configure.f0_harvest_floor = f0_section[<span class="hljs-string">"f0_harvest_floor"</span>].<span class="hljs-built_in">get</span>&lt;inicpp::<span class="hljs-type">float_ini_t</span>&gt;();<br>    configure.f0_cheap_trick_floor = f0_section[<span class="hljs-string">"f0_cheap_trick_floor"</span>].<span class="hljs-built_in">get</span>&lt;inicpp::<span class="hljs-type">float_ini_t</span>&gt;();<br>    configure.f0_allow_range = f0_section[<span class="hljs-string">"f0_allow_range"</span>].<span class="hljs-built_in">get</span>&lt;inicpp::<span class="hljs-type">float_ini_t</span>&gt;();<br><br>    <span class="hljs-comment">// 解析 [ap] 部分</span><br>    <span class="hljs-keyword">auto</span> ap_section = config[<span class="hljs-string">"ap"</span>];<br>    configure.ap_threshold = ap_section[<span class="hljs-string">"ap_threshold"</span>].<span class="hljs-built_in">get</span>&lt;inicpp::<span class="hljs-type">float_ini_t</span>&gt;();<br>}<br></code></pre></td></tr></tbody></table></figure><hr><h2 id="配置文件格式" data-id="配置文件格式" class="notion-h"><a href="#配置文件格式" class="headerlink" title="配置文件格式"></a>配置文件格式</h2><p>生成的配置文件 <code>lessconfig.ini</code> 格式如下：</p><figure class="highlight ini"><table><tbody><tr><td class="code"><pre><code class="hljs ini">============ Gobal <span class="hljs-attr">Settings</span> ===========<br><br><span class="hljs-section">[config]</span><br><span class="hljs-attr">version</span> = &lt;git_hash&gt;<br><span class="hljs-attr">debug</span> = <span class="hljs-number">0</span><br><br>========= Audio Model <span class="hljs-attr">Settings</span> ========<br>Note: modifying any of the parameters here will require remodeling the voice db<br><br><span class="hljs-section">[audio_model]</span><br><span class="hljs-attr">frame_period</span> = <span class="hljs-number">5.80499</span><br><span class="hljs-attr">fft_size</span> = auto<br><span class="hljs-attr">model_amp</span> = <span class="hljs-number">0.85</span><br><br>============= F0 <span class="hljs-attr">Settings</span> =============<br>Note: modifying any of the parameters here will require remodeling the voice db<br><br><span class="hljs-section">[f0]</span><br><span class="hljs-attr">f0_mode</span> = HARVEST<br><span class="hljs-attr">f0_speed</span> = <span class="hljs-number">1</span><br><span class="hljs-attr">f0_dio_floor</span> = <span class="hljs-number">40.0</span><br><span class="hljs-attr">f0_harvest_floor</span> = <span class="hljs-number">40.0</span><br><span class="hljs-attr">f0_cheap_trick_floor</span> = <span class="hljs-number">71.0</span><br><span class="hljs-attr">f0_allow_range</span> = <span class="hljs-number">0.1</span><br><br>============= AP <span class="hljs-attr">Settings</span> =============<br><br><span class="hljs-section">[ap]</span><br><span class="hljs-attr">ap_threshold</span> = <span class="hljs-number">0.10</span><br></code></pre></td></tr></tbody></table></figure><hr><h2 id="ConfigVoiceBank-类详解" data-id="ConfigVoiceBank-类详解" class="notion-h"><a href="#ConfigVoiceBank-类详解" class="headerlink" title="ConfigVoiceBank 类详解"></a>ConfigVoiceBank 类详解</h2><p>ConfigVoiceBank 允许为特定的音源库覆盖全局配置。</p><h3 id="类定义-3" data-id="类定义-3" class="notion-h"><a href="#类定义-3" class="headerlink" title="类定义"></a>类定义</h3><figure class="highlight cpp"><table><tbody><tr><td class="code"><pre><code class="hljs cpp"><span class="hljs-keyword">class</span> <span class="hljs-title class_">ConfigVoiceBank</span> {<br><span class="hljs-keyword">public</span>:<br>    <span class="hljs-built_in">ConfigVoiceBank</span>(std::filesystem::path _voice_path, lessConfigure _configure);<br>    <span class="hljs-function"><span class="hljs-keyword">explicit</span> <span class="hljs-title">ConfigVoiceBank</span><span class="hljs-params">(lessConfigure _configure)</span></span>;<br><br>    <span class="hljs-function"><span class="hljs-type">void</span> <span class="hljs-title">SetVoiceConfig</span><span class="hljs-params">()</span></span>;<br><br><span class="hljs-keyword">private</span>:<br>    lessConfigure configure;<br>    std::filesystem::path voice_path;<br>    std::filesystem::path voice_config_path;<br>    inicpp::config config;<br>    inicpp::schema config_schema;<br>    std::string config_file_data_string;<br><br><span class="hljs-keyword">private</span>:<br>    <span class="hljs-function"><span class="hljs-type">void</span> <span class="hljs-title">parse_config</span><span class="hljs-params">()</span></span>;<br>};<br></code></pre></td></tr></tbody></table></figure><h3 id="音源库配置机制" data-id="音源库配置机制" class="notion-h"><a href="#音源库配置机制" class="headerlink" title="音源库配置机制"></a>音源库配置机制</h3><figure class="highlight cpp"><table><tbody><tr><td class="code"><pre><code class="hljs cpp"><span class="hljs-function"><span class="hljs-type">void</span> <span class="hljs-title">ConfigVoiceBank::SetVoiceConfig</span><span class="hljs-params">()</span> </span>{<br>    <span class="hljs-comment">// 构建音源库配置文件路径</span><br>    voice_config_path = voice_path / VOIICEBANKCONFIGFILENAME;<br><br>    <span class="hljs-keyword">if</span> (voice_config_path.<span class="hljs-built_in">empty</span>()) {<br>        <span class="hljs-keyword">throw</span> <span class="hljs-built_in">file_open_error</span>(<span class="hljs-string">"Configure file: "</span> + voice_config_path.<span class="hljs-built_in">string</span>());<br>    }<br>}<br><br><span class="hljs-function"><span class="hljs-type">void</span> <span class="hljs-title">ConfigVoiceBank::parse_config</span><span class="hljs-params">()</span> </span>{<br>    <span class="hljs-comment">// 解析音源库配置（结构同全局配置）</span><br>    config = inicpp::parser::<span class="hljs-built_in">load</span>(config_file_data_string);<br><br>    <span class="hljs-comment">// 覆盖全局配置参数</span><br>    <span class="hljs-keyword">auto</span> audio_model_section = config[<span class="hljs-string">"audio_model"</span>];<br>    configure.audio_model_frame_period = audio_model_section[<span class="hljs-string">"frame_period"</span>].<span class="hljs-built_in">get</span>&lt;inicpp::<span class="hljs-type">float_ini_t</span>&gt;();<br>    configure.model_amp = audio_model_section[<span class="hljs-string">"model_amp"</span>].<span class="hljs-built_in">get</span>&lt;inicpp::<span class="hljs-type">float_ini_t</span>&gt;();<br><br>    <span class="hljs-comment">// ... 其他参数覆盖</span><br>}<br></code></pre></td></tr></tbody></table></figure><p><strong>使用场景</strong>：</p><p>不同音源库可能有不同的录制条件：</p><ul><li>采样率不同 → 需要调整帧周期</li><li>音量不均 → 需要调整 model_amp</li><li>低音音源 → 需要降低 f0_floor</li></ul><hr><h2 id="配置层次结构" data-id="配置层次结构" class="notion-h"><a href="#配置层次结构" class="headerlink" title="配置层次结构"></a>配置层次结构</h2><figure class="highlight plaintext"><table><tbody><tr><td class="code"><pre><code class="hljs plaintext">┌─────────────────────────────┐<br>│   lessconfig.ini            │ 全局配置<br>│   (exec_path/lessconfig.ini)│<br>└──────────────────┬──────────┘<br>                   │ 默认值<br>                   ▼<br>┌─────────────────────────────┐<br>│   lessConfigure              │ 配置数据结构<br>│   - 全局参数                 │<br>│   - get_version() → SHA-1   │<br>└──────────────────┬──────────┘<br>                   │<br>                   ▼<br>┌─────────────────────────────┐<br>│   voicebankconfig.ini       │ 音源库配置（可选）<br>│   (voice_path/...)          │<br>└──────────────────┬──────────┘<br>                   │ 覆盖<br>                   ▼<br>┌─────────────────────────────┐<br>│   lessConfigure              │ 最终配置<br>│   - 全局参数                 │<br>│   - 音源库覆盖               │<br>│   - 版本校验码               │<br>└─────────────────────────────┘<br>                   │<br>                   ▼<br>┌─────────────────────────────┐<br>│   AudioModel 生成            │<br>│   - 存储版本校验码           │<br>└─────────────────────────────┘<br>                   │<br>                   ▼<br>┌─────────────────────────────┐<br>│   AudioModel 加载            │<br>│   - 验证版本校验码           │<br>│   - 不匹配则重新生成         │<br>└─────────────────────────────┘<br></code></pre></td></tr></tbody></table></figure><hr><h2 id="版本校验流程" data-id="版本校验流程" class="notion-h"><a href="#版本校验流程" class="headerlink" title="版本校验流程"></a>版本校验流程</h2><h3 id="生成时（AudioModelIO）" data-id="生成时（AudioModelIO）" class="notion-h"><a href="#生成时（AudioModelIO）" class="headerlink" title="生成时（AudioModelIO）"></a>生成时（AudioModelIO）</h3><figure class="highlight cpp"><table><tbody><tr><td class="code"><pre><code class="hljs cpp"><span class="hljs-function"><span class="hljs-type">void</span> <span class="hljs-title">AudioModelIO::SaveAudioModel</span><span class="hljs-params">(...)</span> </span>{<br>    <span class="hljs-comment">// 生成版本校验码</span><br>    std::string version = configure.<span class="hljs-built_in">get_version</span>();<br><br>    <span class="hljs-comment">// 写入文件头</span><br>    file.<span class="hljs-built_in">write</span>(<span class="hljs-string">"shine"</span>, <span class="hljs-number">6</span>);  <span class="hljs-comment">// 文件标识</span><br><br>    <span class="hljs-comment">// 写入版本校验码长度和内容</span><br>    <span class="hljs-type">size_t</span> ver_size = version.<span class="hljs-built_in">size</span>();<br>    file.<span class="hljs-built_in">write</span>(<span class="hljs-built_in">reinterpret_cast</span>&lt;<span class="hljs-type">const</span> <span class="hljs-type">char</span>*&gt;(&amp;ver_size), <span class="hljs-built_in">sizeof</span>(<span class="hljs-type">size_t</span>));<br>    file.<span class="hljs-built_in">write</span>(version.<span class="hljs-built_in">c_str</span>(), ver_size);<br><br>    <span class="hljs-comment">// 写入音频参数...</span><br>}<br></code></pre></td></tr></tbody></table></figure><h3 id="加载时（AudioModelIO）" data-id="加载时（AudioModelIO）" class="notion-h"><a href="#加载时（AudioModelIO）" class="headerlink" title="加载时（AudioModelIO）"></a>加载时（AudioModelIO）</h3><figure class="highlight cpp"><table><tbody><tr><td class="code"><pre><code class="hljs cpp"><span class="hljs-function"><span class="hljs-type">bool</span> <span class="hljs-title">AudioModelIO::CheckAudioModelVersion</span><span class="hljs-params">(<span class="hljs-type">const</span> lessConfigure &amp;configure)</span> </span>{<br>    <span class="hljs-comment">// 读取文件标识</span><br>    <span class="hljs-type">char</span> header[<span class="hljs-number">6</span>];<br>    file.<span class="hljs-built_in">read</span>(header, <span class="hljs-number">6</span>);<br>    <span class="hljs-keyword">if</span> (<span class="hljs-built_in">strcmp</span>(header, <span class="hljs-string">"shine"</span>) != <span class="hljs-number">0</span>) {<br>        <span class="hljs-keyword">throw</span> <span class="hljs-built_in">header_check_error</span>(<span class="hljs-string">"Header: ..."</span>);<br>    }<br><br>    <span class="hljs-comment">// 读取存储的版本校验码</span><br>    <span class="hljs-type">size_t</span> ver_size;<br>    file.<span class="hljs-built_in">read</span>(<span class="hljs-built_in">reinterpret_cast</span>&lt;<span class="hljs-type">char</span>*&gt;(&amp;ver_size), <span class="hljs-built_in">sizeof</span>(<span class="hljs-type">size_t</span>));<br>    <span class="hljs-function">std::string <span class="hljs-title">stored_version</span><span class="hljs-params">(ver_size, <span class="hljs-string">'\0'</span>)</span></span>;<br>    file.<span class="hljs-built_in">read</span>(&amp;stored_version[<span class="hljs-number">0</span>], ver_size);<br><br>    <span class="hljs-comment">// 计算当前配置的版本校验码</span><br>    std::string current_version = configure.<span class="hljs-built_in">get_version</span>();<br><br>    <span class="hljs-comment">// 比对</span><br>    <span class="hljs-keyword">if</span> (stored_version != current_version) {<br>        <span class="hljs-keyword">throw</span> <span class="hljs-built_in">file_version_error</span>(<span class="hljs-string">"Version Mismatch"</span>);<br>    }<br><br>    <span class="hljs-keyword">return</span> <span class="hljs-literal">true</span>;<br>}<br></code></pre></td></tr></tbody></table></figure><hr><h2 id="使用示例" data-id="使用示例" class="notion-h"><a href="#使用示例" class="headerlink" title="使用示例"></a>使用示例</h2><figure class="highlight cpp"><table><tbody><tr><td class="code"><pre><code class="hljs cpp"><span class="hljs-meta">#<span class="hljs-keyword">include</span> <span class="hljs-string">"ConfigUnit/ConfigUnit.h"</span></span><br><span class="hljs-meta">#<span class="hljs-keyword">include</span> <span class="hljs-string">"ConfigUnit/ConfigVoiceBank.h"</span></span><br><br><span class="hljs-comment">// 获取可执行文件路径</span><br>std::filesystem::path exec_path = std::filesystem::<span class="hljs-built_in">weakly_canonical</span>(argv[<span class="hljs-number">0</span>]).<span class="hljs-built_in">parent_path</span>();<br><br><span class="hljs-comment">// 加载全局配置</span><br><span class="hljs-function">ConfigUnit <span class="hljs-title">configUnit</span><span class="hljs-params">(exec_path)</span></span>;<br>lessConfigure configure = configUnit.<span class="hljs-built_in">GetConfig</span>();<br><br><span class="hljs-comment">// 加载音源库配置（可选）</span><br>std::filesystem::path voice_path = <span class="hljs-string">"path/to/voicebank"</span>;<br><span class="hljs-function">ConfigVoiceBank <span class="hljs-title">voiceBank</span><span class="hljs-params">(voice_path, configure)</span></span>;<br><span class="hljs-comment">// configure 现在包含音源库覆盖后的参数</span><br><br><span class="hljs-comment">// 获取版本校验码</span><br>std::string version = configure.<span class="hljs-built_in">get_version</span>();<br><span class="hljs-comment">// 如: "a1b2c3d4e5f6789012345678901234567890abcd"</span><br><br><span class="hljs-comment">// 验证音频模型版本</span><br><span class="hljs-function">AudioModelIO <span class="hljs-title">audioModelIO</span><span class="hljs-params">(input_file)</span></span>;<br><span class="hljs-keyword">if</span> (!audioModelIO.<span class="hljs-built_in">CheckAudioModel</span>(configure)) {<br>    <span class="hljs-comment">// 版本不匹配，需要重新生成</span><br>    <span class="hljs-built_in">GenerateAudioModel</span>(input_file, configure);<br>}<br></code></pre></td></tr></tbody></table></figure>]]>
    </content>
    <id>https://gloomyghost.com/live/2026-04-23-lessampler-configunit-module.aspx</id>
    <link href="https://gloomyghost.com/live/2026-04-23-lessampler-configunit-module.aspx"/>
    <published>2026-04-22T16:00:00.000Z</published>
    <summary>
      <![CDATA[<p>ConfigUnit 模块负责 lessampler 的配置管理，包括全局配置、音源库配置和版本校验。该模块采用 INI 文件格式存储配置，使用 inicpp 库进行解析，并通过 SHA-1 校验和确保配置参数的一致性。</p>
<p>配置系统是歌声合成器的重要组成部分，因]]>
    </summary>
    <title>lessampler: ConfigUnit 模块 - 配置系统的设计</title>
    <updated>2026-05-29T20:33:31.218Z</updated>
  </entry>
  <entry>
    <author>
      <name>柚木 鉉</name>
    </author>
    <category term="UTAU" scheme="https://gloomyghost.com/tags/UTAU/"/>
    <category term="lessampler" scheme="https://gloomyghost.com/tags/lessampler/"/>
    <content>
      <![CDATA[<p>Shine 模块是 lessampler 的合成管道协调器，负责将 UTAU 的参数传递机制转换为内部变换参数，并驱动整个合成流程。该模块是连接外部接口（UTAU）与内部处理模块（AudioProcess、Synthesis）的关键桥梁。</p><p>UTAU 是日本开发的歌声合成软件，使用「resoampler」插件进行音频重采样。lessampler 作为 UTAU 的 resampler 实现，需要解析 UTAU 传递的命令行参数，转换为内部可用的变换参数。</p><h2 id="模块结构" data-id="模块结构" class="notion-h"><a href="#模块结构" class="headerlink" title="模块结构"></a>模块结构</h2><figure class="highlight plaintext"><table><tbody><tr><td class="code"><pre><code class="hljs plaintext">Shine/<br>├── Shine.h/cpp           # 管道协调器<br>├── ShinePara.h           # 变换参数结构<br>└── Binding/<br>    └── libUTAU/<br>        ├── libUTAU.h/cpp           # UTAU 参数管理<br>        ├── UTAUParameterParser.h/cpp  # 命令行参数解析<br>        ├── PitchBendDecoder.h/cpp    # Pitch Bend 解码<br>        ├── ScaleConvert.h/cpp        # 音名转频率<br>        └── FlagsDecoder.h/cpp        # Flags 解码（预留）<br></code></pre></td></tr></tbody></table></figure><hr><h2 id="ShinePara-结构详解" data-id="ShinePara-结构详解" class="notion-h"><a href="#ShinePara-结构详解" class="headerlink" title="ShinePara 结构详解"></a>ShinePara 结构详解</h2><p><code>ShinePara.h</code> 定义了内部使用的变换参数：</p><figure class="highlight cpp"><table><tbody><tr><td class="code"><pre><code class="hljs cpp"><span class="hljs-keyword">class</span> <span class="hljs-title class_">ShinePara</span> {<br><span class="hljs-keyword">public</span>:<br>    <span class="hljs-comment">// 基础变换参数</span><br>    std::string input_file_name = {};<br>    std::string output_file_name = {};<br>    <span class="hljs-type">int</span> time_percent = <span class="hljs-number">0</span>;            <span class="hljs-comment">// UTAU 传递的时间百分比</span><br>    <span class="hljs-type">double</span> velocity = <span class="hljs-number">0.0</span>;           <span class="hljs-comment">// 计算后的速度系数</span><br>    <span class="hljs-type">double</span> offset = <span class="hljs-number">0.0</span>;             <span class="hljs-comment">// 音频偏移（毫秒）</span><br>    <span class="hljs-type">double</span> required_length = <span class="hljs-number">0.0</span>;    <span class="hljs-comment">// 目标长度（毫秒）</span><br>    <span class="hljs-type">int</span> required_frame = <span class="hljs-number">0</span>;          <span class="hljs-comment">// 目标帧数</span><br>    <span class="hljs-type">double</span> first_half_fixed_part = <span class="hljs-number">0.0</span>;  <span class="hljs-comment">// 固定部分长度</span><br>    <span class="hljs-type">double</span> last_unused_part = <span class="hljs-number">0.0</span>;   <span class="hljs-comment">// 空白部分长度</span><br>    <span class="hljs-type">double</span> volumes = <span class="hljs-number">0</span>;              <span class="hljs-comment">// 音量系数</span><br>    <span class="hljs-type">int</span> modulation = <span class="hljs-number">0</span>;              <span class="hljs-comment">// 调制系数（0-100）</span><br>    <span class="hljs-type">double</span> wave_length = <span class="hljs-number">0.0</span>;        <span class="hljs-comment">// 原音频长度</span><br>    <span class="hljs-type">double</span> pre_cross_length = <span class="hljs-number">0.0</span>;   <span class="hljs-comment">// 预交叉长度</span><br>    <span class="hljs-type">double</span> base_length = <span class="hljs-number">0.0</span>;        <span class="hljs-comment">// 基础长度</span><br>    <span class="hljs-type">double</span> cross_length = <span class="hljs-number">0.0</span>;       <span class="hljs-comment">// 交叉长度</span><br>    <span class="hljs-type">double</span> stretch_length = <span class="hljs-number">0.0</span>;     <span class="hljs-comment">// 拉伸系数</span><br>    <span class="hljs-type">int</span> output_samples = <span class="hljs-number">0</span>;          <span class="hljs-comment">// 输出采样数</span><br>    <span class="hljs-type">double</span> scale_num = <span class="hljs-number">0.0</span>;          <span class="hljs-comment">// 目标频率（Hz）</span><br>    <span class="hljs-type">int</span> tempo_num = <span class="hljs-number">0</span>;               <span class="hljs-comment">// BPM</span><br><br><span class="hljs-keyword">public</span>:<br>    <span class="hljs-comment">// Pitch Bend 参数</span><br>    std::vector&lt;<span class="hljs-type">int</span>&gt; pitch_bend = {};  <span class="hljs-comment">// 弯音数组（cents）</span><br>    <span class="hljs-type">int</span> pitch_length = <span class="hljs-number">0</span>;              <span class="hljs-comment">// 弯音长度</span><br>    <span class="hljs-type">int</span> pitch_step = <span class="hljs-number">256</span>;              <span class="hljs-comment">// 弯音步长</span><br><br><span class="hljs-keyword">public</span>:<br>    <span class="hljs-comment">// 扩展选项（预留）</span><br>    <span class="hljs-type">bool</span> is_custom_pitch = <span class="hljs-literal">false</span>;<br>    <span class="hljs-type">bool</span> is_gender = <span class="hljs-literal">false</span>;<br>    <span class="hljs-type">bool</span> is_breath = <span class="hljs-literal">false</span>;<br>    <span class="hljs-type">bool</span> is_opening = <span class="hljs-literal">false</span>;<br><br><span class="hljs-keyword">public</span>:<br>    <span class="hljs-comment">// 选项数据</span><br>    <span class="hljs-type">double</span> gender_value = <span class="hljs-number">0.0</span>;<br>    <span class="hljs-type">double</span> breath_value = <span class="hljs-number">0.0</span>;<br>    <span class="hljs-type">double</span> opening = <span class="hljs-number">0.0</span>;<br>};<br></code></pre></td></tr></tbody></table></figure><p><strong>参数分类</strong>：</p><table><thead><tr><th>类别</th><th>参数</th><th>来源</th></tr></thead><tbody><tr><td>输入输出</td><td>input_file_name, output_file_name</td><td>UTAU 命令行</td></tr><tr><td>时间控制</td><td>offset, required_length, fixed_part, blank_part</td><td>UTAU 命令行</td></tr><tr><td>音高控制</td><td>scale_num, pitch_bend, modulation</td><td>UTAU 命令行 + 音名转换</td></tr><tr><td>计算参数</td><td>velocity, stretch_length, output_samples</td><td>CheckPara 计算</td></tr><tr><td>音量控制</td><td>volumes</td><td>UTAU 命令行</td></tr></tbody></table><hr><h2 id="libUTAU-绑定模块" data-id="libUTAU-绑定模块" class="notion-h"><a href="#libUTAU-绑定模块" class="headerlink" title="libUTAU 绑定模块"></a>libUTAU 绑定模块</h2><h3 id="UTAUPara-结构" data-id="UTAUPara-结构" class="notion-h"><a href="#UTAUPara-结构" class="headerlink" title="UTAUPara 结构"></a>UTAUPara 结构</h3><p><code>libUTAU.h</code> 定义了直接对应 UTAU 参数的结构：</p><figure class="highlight cpp"><table><tbody><tr><td class="code"><pre><code class="hljs cpp"><span class="hljs-keyword">class</span> <span class="hljs-title class_">UTAUPara</span> {<br><span class="hljs-keyword">public</span>:<br>    <span class="hljs-comment">// 基础变换参数（直接对应 argv）</span><br>    std::string local_name;         <span class="hljs-comment">// argv[0] - 程序名</span><br>    std::string input_file_name;    <span class="hljs-comment">// argv[1] - 输入文件</span><br>    std::string output_file_name;   <span class="hljs-comment">// argv[2] - 输出文件</span><br>    std::string scale_name;         <span class="hljs-comment">// argv[3] - 音名（如 C4）</span><br>    <span class="hljs-type">int</span> time_percent;               <span class="hljs-comment">// argv[4] - 时间百分比</span><br>    <span class="hljs-type">double</span> velocity;                <span class="hljs-comment">// 计算：pow(2, percent/100 - 1)</span><br>    std::string flags;              <span class="hljs-comment">// argv[5] - Flags 字符串</span><br>    <span class="hljs-type">double</span> offset;                  <span class="hljs-comment">// argv[6] - 偏移（毫秒）</span><br>    <span class="hljs-type">double</span> required_length;         <span class="hljs-comment">// argv[7] - 目标长度（毫秒）</span><br>    <span class="hljs-type">double</span> first_half_fixed_part;   <span class="hljs-comment">// argv[8] - 固定部分</span><br>    <span class="hljs-type">double</span> last_unused_part;        <span class="hljs-comment">// argv[9] - 空白部分</span><br>    <span class="hljs-type">double</span> volumes;                 <span class="hljs-comment">// argv[10] - 音量（百分比）</span><br>    <span class="hljs-type">int</span> modulation;                 <span class="hljs-comment">// argv[11] - 调制</span><br>    std::string tempo;              <span class="hljs-comment">// argv[12] - BPM 字符串</span><br>    std::string pitch;              <span class="hljs-comment">// argv[13] - Pitch Bend 字符串</span><br><br><span class="hljs-keyword">public</span>:<br>    <span class="hljs-comment">// 计算参数</span><br>    <span class="hljs-type">double</span> wave_length = <span class="hljs-number">0.0</span>;<br>    <span class="hljs-type">double</span> pre_cross_length = <span class="hljs-number">0.0</span>;<br>    <span class="hljs-type">double</span> base_length = <span class="hljs-number">0.0</span>;<br>    <span class="hljs-type">double</span> cross_length = <span class="hljs-number">0.0</span>;<br>    <span class="hljs-type">double</span> stretch_length = <span class="hljs-number">0.0</span>;<br>    <span class="hljs-type">int</span> output_samples = <span class="hljs-number">0</span>;<br>    <span class="hljs-type">double</span> scale_num = <span class="hljs-number">0.0</span>;<br>    <span class="hljs-type">int</span> tempo_num = <span class="hljs-number">0</span>;<br>    <span class="hljs-type">bool</span> is_custom_pitch = <span class="hljs-literal">false</span>;<br>};<br></code></pre></td></tr></tbody></table></figure><h3 id="音频处理概念图" data-id="音频处理概念图" class="notion-h"><a href="#音频处理概念图" class="headerlink" title="音频处理概念图"></a>音频处理概念图</h3><p>代码注释中的示意图：</p><figure class="highlight plaintext"><table><tbody><tr><td class="code"><pre><code class="hljs plaintext">//  offset   fixed   pre_cross   blank<br>//|--------|--------|---------|---------| Original Signal<br>//         |        |          |<br>//         |   l1   |    l2     |<br>//         |--------|------------|        Output Signal<br>// l1  = fixed / velocity         -&gt; base_length<br>// l2  = pre_cross / stretch      -&gt; cross_length<br>// l1 + l2 = required_length      -&gt; required_length<br></code></pre></td></tr></tbody></table></figure><p>这是 UTAU 合成的经典模型：</p><ul><li><strong>固定部分</strong>：不进行拉伸，保持原音色</li><li><strong>预交叉部分</strong>：用于拉伸/压缩，连接下一音符</li><li><strong>空白部分</strong>：不使用的尾部</li></ul><hr><h2 id="UTAUParameterParser-类" data-id="UTAUParameterParser-类" class="notion-h"><a href="#UTAUParameterParser-类" class="headerlink" title="UTAUParameterParser 类"></a>UTAUParameterParser 类</h2><h3 id="命令行参数解析" data-id="命令行参数解析" class="notion-h"><a href="#命令行参数解析" class="headerlink" title="命令行参数解析"></a>命令行参数解析</h3><p>UTAU 通过命令行参数将合成请求传递给 resoampler：</p><figure class="highlight cpp"><table><tbody><tr><td class="code"><pre><code class="hljs cpp">UTAUParameterParser::<span class="hljs-built_in">UTAUParameterParser</span>(<span class="hljs-type">int</span> argc, <span class="hljs-type">char</span> *argv[]) {<br>    <span class="hljs-comment">// argv[0]: 程序名</span><br>    utauPara.local_name = argv[<span class="hljs-number">0</span>];<br><br>    <span class="hljs-comment">// argv[1]: 输入文件路径</span><br>    utauPara.input_file_name = argv[<span class="hljs-number">1</span>];<br><br>    <span class="hljs-comment">// argv[2]: 输出文件路径</span><br>    utauPara.output_file_name = argv[<span class="hljs-number">2</span>];<br><br>    <span class="hljs-comment">// argv[3]: 音名（如 C4、D#5）</span><br>    utauPara.scale_name = argv[<span class="hljs-number">3</span>];<br>    <span class="hljs-function">ScaleConvert <span class="hljs-title">scaleConvert</span><span class="hljs-params">(utauPara.scale_name)</span></span>;<br>    utauPara.scale_num = scaleConvert.<span class="hljs-built_in">GetScaleNum</span>();<br><br>    <span class="hljs-comment">// argv[4]: 时间百分比 → velocity</span><br>    <span class="hljs-keyword">if</span> (argc &gt; <span class="hljs-number">4</span>) {<br>        <span class="hljs-function">std::istringstream <span class="hljs-title">sstream</span><span class="hljs-params">(argv[<span class="hljs-number">4</span>])</span></span>;<br>        sstream &gt;&gt; utauPara.time_percent;<br>        utauPara.velocity = <span class="hljs-built_in">pow</span>(<span class="hljs-number">2</span>, utauPara.time_percent / <span class="hljs-number">100.0</span> - <span class="hljs-number">1.0</span>);<br>    }<br><br>    <span class="hljs-comment">// argv[5]: Flags 字符串</span><br>    <span class="hljs-keyword">if</span> (argc &gt; <span class="hljs-number">5</span>) {<br>        <span class="hljs-function">std::istringstream <span class="hljs-title">sstream</span><span class="hljs-params">(argv[<span class="hljs-number">5</span>])</span></span>;<br>        sstream &gt;&gt; utauPara.flags;<br>    }<br><br>    <span class="hljs-comment">// argv[6]: 偏移（毫秒）</span><br>    <span class="hljs-keyword">if</span> (argc &gt; <span class="hljs-number">6</span>) {<br>        <span class="hljs-function">std::istringstream <span class="hljs-title">sstream</span><span class="hljs-params">(argv[<span class="hljs-number">6</span>])</span></span>;<br>        sstream &gt;&gt; utauPara.offset;<br>    }<br><br>    <span class="hljs-comment">// argv[7]: 目标长度（毫秒）</span><br>    <span class="hljs-keyword">if</span> (argc &gt; <span class="hljs-number">7</span>) {<br>        <span class="hljs-function">std::istringstream <span class="hljs-title">sstream</span><span class="hljs-params">(argv[<span class="hljs-number">7</span>])</span></span>;<br>        sstream &gt;&gt; utauPara.required_length;<br>    }<br><br>    <span class="hljs-comment">// argv[8]: 固定部分长度</span><br>    <span class="hljs-keyword">if</span> (argc &gt; <span class="hljs-number">8</span>) {<br>        <span class="hljs-function">std::istringstream <span class="hljs-title">sstream</span><span class="hljs-params">(argv[<span class="hljs-number">8</span>])</span></span>;<br>        sstream &gt;&gt; utauPara.first_half_fixed_part;<br>    }<br><br>    <span class="hljs-comment">// argv[9]: 空白部分长度</span><br>    <span class="hljs-keyword">if</span> (argc &gt; <span class="hljs-number">9</span>) {<br>        <span class="hljs-function">std::istringstream <span class="hljs-title">sstream</span><span class="hljs-params">(argv[<span class="hljs-number">9</span>])</span></span>;<br>        sstream &gt;&gt; utauPara.last_unused_part;<br>    }<br><br>    <span class="hljs-comment">// argv[10]: 音量百分比</span><br>    <span class="hljs-keyword">if</span> (argc &gt; <span class="hljs-number">10</span>) {<br>        <span class="hljs-function">std::istringstream <span class="hljs-title">sstream</span><span class="hljs-params">(argv[<span class="hljs-number">10</span>])</span></span>;<br>        sstream &gt;&gt; utauPara.volumes;<br>        utauPara.volumes *= <span class="hljs-number">0.01</span>;  <span class="hljs-comment">// 转换为系数</span><br>    }<br><br>    <span class="hljs-comment">// argv[11]: 调制系数</span><br>    <span class="hljs-keyword">if</span> (argc &gt; <span class="hljs-number">11</span>) {<br>        <span class="hljs-function">std::istringstream <span class="hljs-title">sstream</span><span class="hljs-params">(argv[<span class="hljs-number">11</span>])</span></span>;<br>        sstream &gt;&gt; utauPara.modulation;<br>    }<br><br>    <span class="hljs-comment">// argv[12]: BPM（可能带 ! 前缀）</span><br>    <span class="hljs-keyword">if</span> (argc &gt; <span class="hljs-number">12</span>) {<br>        <span class="hljs-function">std::istringstream <span class="hljs-title">sstream</span><span class="hljs-params">(argv[<span class="hljs-number">12</span>])</span></span>;<br>        sstream &gt;&gt; utauPara.tempo;<br>        <span class="hljs-keyword">if</span> (utauPara.tempo.<span class="hljs-built_in">find</span>(<span class="hljs-string">'!'</span>) != std::string::npos) {<br>            utauPara.tempo_num = std::<span class="hljs-built_in">stoi</span>(utauPara.tempo.<span class="hljs-built_in">substr</span>(<span class="hljs-number">1</span>));<br>        } <span class="hljs-keyword">else</span> {<br>            utauPara.tempo_num = std::<span class="hljs-built_in">stoi</span>(utauPara.tempo.<span class="hljs-built_in">substr</span>(<span class="hljs-number">2</span>));<br>        }<br>    }<br><br>    <span class="hljs-comment">// argv[13]: Pitch Bend 字符串</span><br>    <span class="hljs-keyword">if</span> (argc &gt; <span class="hljs-number">13</span>) {<br>        utauPara.is_custom_pitch = <span class="hljs-literal">true</span>;<br>        <span class="hljs-function">std::istringstream <span class="hljs-title">sstream</span><span class="hljs-params">(argv[<span class="hljs-number">13</span>])</span></span>;<br>        sstream &gt;&gt; utauPara.pitch;<br>    }<br>}<br></code></pre></td></tr></tbody></table></figure><p><strong>参数对照表</strong>：</p><table><thead><tr><th>argv 索引</th><th>参数名</th><th>格式示例</th><th>处理方式</th></tr></thead><tbody><tr><td>0</td><td>程序名</td><td>lessampler.exe</td><td>直接存储</td></tr><tr><td>1</td><td>输入文件</td><td>input.wav</td><td>直接存储</td></tr><tr><td>2</td><td>输出文件</td><td>output.wav</td><td>直接存储</td></tr><tr><td>3</td><td>音名</td><td>C4, D#5</td><td>ScaleConvert 转 Hz</td></tr><tr><td>4</td><td>时间百分比</td><td>100</td><td>pow(2, value/100 - 1)</td></tr><tr><td>5</td><td>Flags</td><td>B0H10</td><td>待实现</td></tr><tr><td>6</td><td>偏移</td><td>50.0</td><td>直接解析</td></tr><tr><td>7</td><td>目标长度</td><td>200.0</td><td>直接解析</td></tr><tr><td>8</td><td>固定部分</td><td>50.0</td><td>直接解析</td></tr><tr><td>9</td><td>空白部分</td><td>20.0</td><td>直接解析</td></tr><tr><td>10</td><td>音量</td><td>100</td><td>× 0.01</td></tr><tr><td>11</td><td>调制</td><td>50</td><td>直接解析</td></tr><tr><td>12</td><td>BPM</td><td>!120 或 AA120</td><td>解析数字</td></tr><tr><td>13</td><td>Pitch Bend</td><td>AA#10#BB</td><td>PitchBendDecoder</td></tr></tbody></table><hr><h2 id="ScaleConvert-类：音名转频率" data-id="ScaleConvert-类：音名转频率" class="notion-h"><a href="#ScaleConvert-类：音名转频率" class="headerlink" title="ScaleConvert 类：音名转频率"></a>ScaleConvert 类：音名转频率</h2><h3 id="算法实现" data-id="算法实现" class="notion-h"><a href="#算法实现" class="headerlink" title="算法实现"></a>算法实现</h3><figure class="highlight cpp"><table><tbody><tr><td class="code"><pre><code class="hljs cpp"><span class="hljs-function"><span class="hljs-type">bool</span> <span class="hljs-title">ScaleConvert::ScaleConvertToDouble</span><span class="hljs-params">(std::string scaleName)</span> </span>{<br>    <span class="hljs-type">int</span> bias = <span class="hljs-number">0</span>;<br><br>    <span class="hljs-comment">// 检测是否为升号（#）</span><br>    <span class="hljs-keyword">if</span> (scaleName[<span class="hljs-number">1</span>] == <span class="hljs-string">'#'</span>) {<br>        bias = <span class="hljs-number">1</span>;<br>    }<br><br>    <span class="hljs-comment">// 音名偏移计算（相对于 A）</span><br>    <span class="hljs-type">int</span> scale;<br>    <span class="hljs-keyword">switch</span> (scaleName[<span class="hljs-number">0</span>]) {<br>        <span class="hljs-keyword">case</span> <span class="hljs-string">'C'</span>: scale = <span class="hljs-number">-9</span> + bias; <span class="hljs-keyword">break</span>;<br>        <span class="hljs-keyword">case</span> <span class="hljs-string">'D'</span>: scale = <span class="hljs-number">-7</span> + bias; <span class="hljs-keyword">break</span>;<br>        <span class="hljs-keyword">case</span> <span class="hljs-string">'E'</span>: scale = <span class="hljs-number">-5</span>; <span class="hljs-keyword">break</span>;        <span class="hljs-comment">// 无升号</span><br>        <span class="hljs-keyword">case</span> <span class="hljs-string">'F'</span>: scale = <span class="hljs-number">-4</span> + bias; <span class="hljs-keyword">break</span>;<br>        <span class="hljs-keyword">case</span> <span class="hljs-string">'G'</span>: scale = <span class="hljs-number">-2</span> + bias; <span class="hljs-keyword">break</span>;<br>        <span class="hljs-keyword">case</span> <span class="hljs-string">'A'</span>: scale = bias; <span class="hljs-keyword">break</span>;<br>        <span class="hljs-keyword">case</span> <span class="hljs-string">'B'</span>: scale = <span class="hljs-number">2</span>; <span class="hljs-keyword">break</span>;         <span class="hljs-comment">// 无升号</span><br>        <span class="hljs-keyword">default</span>: <span class="hljs-keyword">return</span> <span class="hljs-literal">false</span>;<br>    }<br><br>    <span class="hljs-comment">// 计算八度数（相对于 A4）</span><br>    <span class="hljs-type">double</span> octave = scaleName[<span class="hljs-number">1</span> + bias] - <span class="hljs-string">'0'</span> - <span class="hljs-number">4</span>;<br><br>    <span class="hljs-comment">// 频率计算公式：A4 = 440Hz</span><br>    scaleNum = <span class="hljs-built_in">pow</span>(<span class="hljs-number">2.0</span>, octave) * <span class="hljs-built_in">pow</span>(<span class="hljs-number">2.0</span>, scale / <span class="hljs-number">12.0</span>) * <span class="hljs-number">440.0</span>;<br><br>    <span class="hljs-keyword">return</span> <span class="hljs-literal">true</span>;<br>}<br></code></pre></td></tr></tbody></table></figure><p><strong>数学公式</strong>：</p><p>$$f = 440 \times 2^{octave} \times 2^{\frac{semitone}{12}}$$</p><p>其中：</p><ul><li>$440 \text{ Hz}$ = A4 的标准频率</li><li>$octave$ = 目标八度 - 4</li><li>$semitone$ = 目标音名相对于 A 的半音偏移</li></ul><p><strong>示例计算</strong>：</p><table><thead><tr><th>音名</th><th>$octave$</th><th>$semitone$</th><th>计算过程</th><th>结果</th></tr></thead><tbody><tr><td>C4</td><td>0</td><td>-9</td><td>$440 \times 2^0 \times 2^{-9/12}$</td><td>261.63 Hz</td></tr><tr><td>A4</td><td>0</td><td>0</td><td>$440 \times 2^0 \times 2^0$</td><td>440 Hz</td></tr><tr><td>C5</td><td>1</td><td>-9</td><td>$440 \times 2^1 \times 2^{-9/12}$</td><td>523.25 Hz</td></tr><tr><td>D#4</td><td>0</td><td>-6</td><td>$440 \times 2^0 \times 2^{-6/12}$</td><td>311.13 Hz</td></tr></tbody></table><hr><h2 id="PitchBendDecoder-类：弯音解码" data-id="PitchBendDecoder-类：弯音解码" class="notion-h"><a href="#PitchBendDecoder-类：弯音解码" class="headerlink" title="PitchBendDecoder 类：弯音解码"></a>PitchBendDecoder 类：弯音解码</h2><h3 id="UTAU-Pitch-Bend-编码格式" data-id="UTAU-Pitch-Bend-编码格式" class="notion-h"><a href="#UTAU-Pitch-Bend-编码格式" class="headerlink" title="UTAU Pitch Bend 编码格式"></a>UTAU Pitch Bend 编码格式</h3><p>UTAU 使用一种特殊的 Base64 变体编码 Pitch Bend：</p><p><strong>字符映射表</strong>：</p><table><thead><tr><th>字符范围</th><th>数值</th></tr></thead><tbody><tr><td>A-Z</td><td>0-25</td></tr><tr><td>a-z</td><td>26-51</td></tr><tr><td>0-9</td><td>52-61</td></tr><tr><td>+</td><td>62</td></tr><tr><td>/</td><td>63</td></tr></tbody></table><p>每个 Pitch Bend 值由两个字符编码：</p><p>$$value = char_1 \times 64 + char_2$$</p><p><strong>有符号转换</strong>：</p><ul><li>值 $\leq 2047$：正值</li><li>值 $&gt; 2047$：负值（$value - 4096$）</li></ul><h3 id="GetDataFromUTAU64-函数" data-id="GetDataFromUTAU64-函数" class="notion-h"><a href="#GetDataFromUTAU64-函数" class="headerlink" title="GetDataFromUTAU64() 函数"></a>GetDataFromUTAU64() 函数</h3><figure class="highlight cpp"><table><tbody><tr><td class="code"><pre><code class="hljs cpp"><span class="hljs-function"><span class="hljs-type">int</span> <span class="hljs-title">PitchBendDecoder::GetDataFromUTAU64</span><span class="hljs-params">(<span class="hljs-type">char</span> i)</span> </span>{<br>    <span class="hljs-keyword">if</span> (i &gt;= <span class="hljs-string">'0'</span> &amp;&amp; i &lt;= <span class="hljs-string">'9'</span>) {<br>        <span class="hljs-keyword">return</span> i - <span class="hljs-string">'0'</span> + <span class="hljs-number">52</span>;<br>    } <span class="hljs-keyword">else</span> <span class="hljs-keyword">if</span> (i &gt;= <span class="hljs-string">'A'</span> &amp;&amp; i &lt;= <span class="hljs-string">'Z'</span>) {<br>        <span class="hljs-keyword">return</span> i - <span class="hljs-string">'A'</span>;<br>    } <span class="hljs-keyword">else</span> <span class="hljs-keyword">if</span> (i &gt;= <span class="hljs-string">'a'</span> &amp;&amp; i &lt;= <span class="hljs-string">'z'</span>) {<br>        <span class="hljs-keyword">return</span> i - <span class="hljs-string">'a'</span> + <span class="hljs-number">26</span>;<br>    } <span class="hljs-keyword">else</span> <span class="hljs-keyword">if</span> (i == <span class="hljs-string">'+'</span>) {<br>        <span class="hljs-keyword">return</span> <span class="hljs-number">62</span>;<br>    } <span class="hljs-keyword">else</span> <span class="hljs-keyword">if</span> (i == <span class="hljs-string">'/'</span>) {<br>        <span class="hljs-keyword">return</span> <span class="hljs-number">63</span>;<br>    } <span class="hljs-keyword">else</span> {<br>        <span class="hljs-keyword">return</span> <span class="hljs-number">0</span>;<br>    }<br>}<br></code></pre></td></tr></tbody></table></figure><h3 id="PitchBendDecode-函数" data-id="PitchBendDecode-函数" class="notion-h"><a href="#PitchBendDecode-函数" class="headerlink" title="PitchBendDecode() 函数"></a>PitchBendDecode() 函数</h3><figure class="highlight cpp"><table><tbody><tr><td class="code"><pre><code class="hljs cpp"><span class="hljs-function"><span class="hljs-type">void</span> <span class="hljs-title">PitchBendDecoder::PitchBendDecode</span><span class="hljs-params">()</span> </span>{<br>    <span class="hljs-type">int</span> i, n = <span class="hljs-number">0</span>;<br>    <span class="hljs-type">int</span> k = <span class="hljs-number">0</span>, num, ii;<br>    std::stringstream ss;<br>    <span class="hljs-type">char</span> *str = <span class="hljs-built_in">const_cast</span>&lt;<span class="hljs-type">char</span> *&gt;(pitch.<span class="hljs-built_in">c_str</span>());<br><br>    <span class="hljs-keyword">for</span> (i = <span class="hljs-number">0</span>; i &lt; pitch_string_length; i += <span class="hljs-number">2</span>) {<br>        <span class="hljs-keyword">if</span> (str[i] == <span class="hljs-string">'#'</span>) {<br>            <span class="hljs-comment">// Run-Length Encoding: #N# 表示重复前值 N 次</span><br>            i++;<br>            ss &lt;&lt; pitch.<span class="hljs-built_in">substr</span>(pitch.<span class="hljs-built_in">find</span>(<span class="hljs-string">'#'</span>, i - <span class="hljs-number">1</span>) + <span class="hljs-number">1</span>,<br>                pitch.<span class="hljs-built_in">find</span>(<span class="hljs-string">'#'</span>, i + pitch.<span class="hljs-built_in">find</span>(<span class="hljs-string">'#'</span>)) - <span class="hljs-number">1</span>);<br>            ss &gt;&gt; num;<br>            <span class="hljs-keyword">for</span> (ii = <span class="hljs-number">0</span>; ii &lt; num &amp;&amp; k &lt; count; ii++) {<br>                pitch_bend[k++] = n;<br>            }<br>            <span class="hljs-keyword">while</span> (str[i] != <span class="hljs-string">'#'</span> &amp;&amp; str[i] != <span class="hljs-number">0</span>)<br>                i++;<br>            i--;<br>        } <span class="hljs-keyword">else</span> {<br>            <span class="hljs-comment">// 正常解码</span><br>            n = <span class="hljs-built_in">GetDataFromUTAU64</span>(str[i]) * <span class="hljs-number">64</span><br>                + <span class="hljs-built_in">GetDataFromUTAU64</span>(str[i + <span class="hljs-number">1</span>]);<br>            <span class="hljs-keyword">if</span> (n &gt; <span class="hljs-number">2047</span>)<br>                n -= <span class="hljs-number">4096</span>;  <span class="hljs-comment">// 转为负值</span><br>            <span class="hljs-keyword">if</span> (k &lt; count) {<br>                pitch_bend[k++] = n;<br>            }<br>        }<br>    }<br>}<br></code></pre></td></tr></tbody></table></figure><p><strong>解码示例</strong>：</p><table><thead><tr><th>字符串</th><th>解码过程</th><th>结果数组</th></tr></thead><tbody><tr><td>AA</td><td>0×64+0=0</td><td>[0]</td></tr><tr><td>BB</td><td>1×64+1=65</td><td>[65]</td></tr><tr><td>zz</td><td>51×64+51=3315 → 3315-4096=-781</td><td>[-781]</td></tr><tr><td>AA#10#AA</td><td>0, 重复10次, 0</td><td>[0,0,0,0,0,0,0,0,0,0,0,0]</td></tr></tbody></table><p><strong>Pitch Bend 值含义</strong>：</p><ul><li>单位：cents（音分）</li><li>10 cents = 1 半音</li><li>1200 cents = 1 倍频</li><li>0 = 无偏移（基准音高）</li></ul><hr><h2 id="libUTAU-CheckPara-参数验证" data-id="libUTAU-CheckPara-参数验证" class="notion-h"><a href="#libUTAU-CheckPara-参数验证" class="headerlink" title="libUTAU::CheckPara() 参数验证"></a>libUTAU::CheckPara() 参数验证</h2><figure class="highlight cpp"><table><tbody><tr><td class="code"><pre><code class="hljs cpp"><span class="hljs-function"><span class="hljs-type">void</span> <span class="hljs-title">libUTAU::CheckPara</span><span class="hljs-params">(<span class="hljs-type">const</span> lessAudioModel&amp; audioModel)</span> </span>{<br>    <span class="hljs-comment">// 计算原音频长度（毫秒）</span><br>    utauPara.wave_length = <span class="hljs-built_in">static_cast</span>&lt;<span class="hljs-type">double</span>&gt;(audioModel.x.<span class="hljs-built_in">size</span>())<br>        / <span class="hljs-built_in">static_cast</span>&lt;<span class="hljs-type">double</span>&gt;(audioModel.fs) * <span class="hljs-number">1000</span>;<br><br>    <span class="hljs-comment">// 处理负的 blank 值（从音频末尾计算）</span><br>    <span class="hljs-keyword">if</span> (utauPara.last_unused_part &lt; <span class="hljs-number">0</span>) {<br>        utauPara.last_unused_part = utauPara.wave_length<br>            - utauPara.offset + utauPara.last_unused_part;<br>        <span class="hljs-keyword">if</span> (utauPara.last_unused_part &lt; <span class="hljs-number">0</span>)<br>            utauPara.last_unused_part = <span class="hljs-number">0</span>;<br>    }<br><br>    <span class="hljs-comment">// 验证：offset + blank 不能超过音频长度</span><br>    <span class="hljs-keyword">if</span> (utauPara.offset + utauPara.last_unused_part &gt;= utauPara.wave_length)<br>        <span class="hljs-keyword">throw</span> <span class="hljs-built_in">parameter_error</span>(<span class="hljs-string">"音频偏移和空白超过音频长度"</span>);<br><br>    <span class="hljs-comment">// 验证：固定部分不能超过可用音频</span><br>    <span class="hljs-keyword">if</span> (utauPara.offset + utauPara.last_unused_part + utauPara.first_half_fixed_part &gt;= utauPara.wave_length)<br>        utauPara.first_half_fixed_part = utauPara.wave_length<br>            - utauPara.offset + utauPara.last_unused_part;<br><br>    <span class="hljs-comment">// 计算预交叉长度</span><br>    utauPara.pre_cross_length = utauPara.wave_length<br>        - utauPara.offset<br>        - utauPara.first_half_fixed_part<br>        - utauPara.last_unused_part;<br><br>    <span class="hljs-comment">// 计算基础长度（固定部分除以 velocity）</span><br>    utauPara.base_length = utauPara.first_half_fixed_part / utauPara.velocity;<br><br>    <span class="hljs-comment">// 计算交叉长度</span><br>    utauPara.cross_length = utauPara.required_length - utauPara.base_length;<br><br>    <span class="hljs-comment">// 验证：预交叉长度不能为负</span><br>    <span class="hljs-keyword">if</span> (utauPara.pre_cross_length &lt;= <span class="hljs-number">0</span> &amp;&amp; utauPara.cross_length &gt; <span class="hljs-number">0</span>)<br>        <span class="hljs-keyword">throw</span> <span class="hljs-built_in">parameter_error</span>(<span class="hljs-string">"输入音频长度不足以进行交叉变换"</span>);<br><br>    <span class="hljs-comment">// 计算拉伸系数</span><br>    utauPara.stretch_length = utauPara.pre_cross_length / utauPara.cross_length;<br><br>    <span class="hljs-comment">// 限制拉伸系数不超过 1.0</span><br>    <span class="hljs-keyword">if</span> (utauPara.stretch_length &gt; <span class="hljs-number">1.0</span>)<br>        utauPara.stretch_length = <span class="hljs-number">1.0</span>;<br><br>    <span class="hljs-comment">// 计算输出采样数</span><br>    utauPara.output_samples = <span class="hljs-built_in">static_cast</span>&lt;<span class="hljs-type">int</span>&gt;(utauPara.required_length * <span class="hljs-number">0.001</span> * audioModel.fs) + <span class="hljs-number">1</span>;<br>}<br></code></pre></td></tr></tbody></table></figure><p><strong>计算公式总结</strong>：</p><table><thead><tr><th>参数</th><th>公式</th><th>说明</th></tr></thead><tbody><tr><td>$wave\_length$</td><td>$\frac{x.size()}{fs} \times 1000$</td><td>音频长度（毫秒）</td></tr><tr><td>$pre\_cross$</td><td>$wave\_length - offset - fixed - blank$</td><td>可用于拉伸的部分</td></tr><tr><td>$base\_length$</td><td>$\frac{fixed}{velocity}$</td><td>固定部分的输出长度</td></tr><tr><td>$cross\_length$</td><td>$required - base\_length$</td><td>拉伸部分的输出长度</td></tr><tr><td>$stretch$</td><td>$\frac{pre\_cross}{cross\_length}$</td><td>拉伸系数</td></tr><tr><td>$output\_samples$</td><td>$required \times 0.001 \times fs + 1$</td><td>输出采样数</td></tr></tbody></table><hr><h2 id="Shine-类：管道协调" data-id="Shine-类：管道协调" class="notion-h"><a href="#Shine-类：管道协调" class="headerlink" title="Shine 类：管道协调"></a>Shine 类：管道协调</h2><h3 id="构造函数" data-id="构造函数" class="notion-h"><a href="#构造函数" class="headerlink" title="构造函数"></a>构造函数</h3><figure class="highlight cpp"><table><tbody><tr><td class="code"><pre><code class="hljs cpp">Shine::<span class="hljs-built_in">Shine</span>(<span class="hljs-type">int</span> argc, <span class="hljs-type">char</span> *argv[], <span class="hljs-type">const</span> lessAudioModel &amp;audioModel, SHINE_MODE mode) {<br>    <span class="hljs-keyword">if</span> (mode == SHINE_MODE::UTAU) {<br>        <span class="hljs-comment">// 解析 UTAU 参数</span><br>        <span class="hljs-function">libUTAU <span class="hljs-title">utau</span><span class="hljs-params">(argc, argv)</span></span>;<br><br>        <span class="hljs-comment">// 验证并计算参数</span><br>        utau.<span class="hljs-built_in">CheckPara</span>(audioModel);<br><br>        <span class="hljs-comment">// 设置 ShinePara</span><br>        <span class="hljs-built_in">SetShine</span>(utau.<span class="hljs-built_in">GetUTAUPara</span>(), utau.<span class="hljs-built_in">GetUTAUFlags</span>(), audioModel);<br>    }<br>}<br></code></pre></td></tr></tbody></table></figure><h3 id="SetShine-参数转换" data-id="SetShine-参数转换" class="notion-h"><a href="#SetShine-参数转换" class="headerlink" title="SetShine() 参数转换"></a>SetShine() 参数转换</h3><figure class="highlight cpp"><table><tbody><tr><td class="code"><pre><code class="hljs cpp"><span class="hljs-function"><span class="hljs-type">void</span> <span class="hljs-title">Shine::SetShine</span><span class="hljs-params">(<span class="hljs-type">const</span> UTAUPara &amp;utau_para, UTAUFlags utau_flags,</span></span><br><span class="hljs-params"><span class="hljs-function">    <span class="hljs-type">const</span> lessAudioModel &amp;audioModel)</span> </span>{<br>    <span class="hljs-comment">// 复制基础参数</span><br>    shine_para.input_file_name = utau_para.input_file_name;<br>    shine_para.output_file_name = utau_para.output_file_name;<br>    shine_para.time_percent = utau_para.time_percent;<br>    shine_para.velocity = utau_para.velocity;<br>    shine_para.offset = utau_para.offset;<br>    shine_para.required_length = utau_para.required_length;<br>    shine_para.first_half_fixed_part = utau_para.first_half_fixed_part;<br>    shine_para.last_unused_part = utau_para.last_unused_part;<br>    shine_para.volumes = utau_para.volumes;<br>    shine_para.modulation = utau_para.modulation;<br>    shine_para.wave_length = utau_para.wave_length;<br>    shine_para.pre_cross_length = utau_para.pre_cross_length;<br>    shine_para.base_length = utau_para.base_length;<br>    shine_para.cross_length = utau_para.cross_length;<br>    shine_para.stretch_length = utau_para.stretch_length;<br>    shine_para.output_samples = utau_para.output_samples;<br>    shine_para.scale_num = utau_para.scale_num;<br>    shine_para.tempo_num = utau_para.tempo_num;<br>    shine_para.is_custom_pitch = utau_para.is_custom_pitch;<br><br>    <span class="hljs-comment">// 解码 Pitch Bend</span><br>    <span class="hljs-built_in">DecodePitchBend</span>(audioModel.fs, audioModel.frame_period, utau_para.pitch);<br>}<br></code></pre></td></tr></tbody></table></figure><h3 id="DecodePitchBend-弯音处理" data-id="DecodePitchBend-弯音处理" class="notion-h"><a href="#DecodePitchBend-弯音处理" class="headerlink" title="DecodePitchBend() 弯音处理"></a>DecodePitchBend() 弯音处理</h3><figure class="highlight cpp"><table><tbody><tr><td class="code"><pre><code class="hljs cpp"><span class="hljs-function"><span class="hljs-type">void</span> <span class="hljs-title">Shine::DecodePitchBend</span><span class="hljs-params">(<span class="hljs-type">int</span> fs, <span class="hljs-type">double</span> frame_period, std::string pitch)</span> </span>{<br>    <span class="hljs-comment">// 默认 BPM</span><br>    <span class="hljs-keyword">if</span> (shine_para.tempo_num == <span class="hljs-number">0</span>)<br>        shine_para.tempo_num = <span class="hljs-number">120</span>;<br><br>    <span class="hljs-keyword">if</span> (shine_para.is_custom_pitch) {<br>        <span class="hljs-comment">// 计算 Pitch Bend 采样步长</span><br>        shine_para.pitch_step = <span class="hljs-built_in">static_cast</span>&lt;<span class="hljs-type">int</span>&gt;(<span class="hljs-built_in">lround</span>(<br>            <span class="hljs-number">60.0</span> / <span class="hljs-number">96.0</span> / shine_para.tempo_num * fs));<br><br>        <span class="hljs-comment">// 计算 Pitch Bend 长度</span><br>        shine_para.pitch_length = shine_para.output_samples / shine_para.pitch_step + <span class="hljs-number">1</span>;<br><br>        <span class="hljs-comment">// 解码 Pitch Bend 字符串</span><br>        <span class="hljs-function">PitchBendDecoder <span class="hljs-title">pitchBendDecoder</span><span class="hljs-params">(pitch, shine_para.pitch_length)</span></span>;<br>        shine_para.pitch_bend = std::<span class="hljs-built_in">move</span>(pitchBendDecoder.<span class="hljs-built_in">GetPitchBend</span>());<br>    } <span class="hljs-keyword">else</span> {<br>        <span class="hljs-comment">// 无自定义弯音：填充零</span><br>        shine_para.pitch_bend.<span class="hljs-built_in">resize</span>(shine_para.pitch_length + <span class="hljs-number">1</span>);<br>        std::<span class="hljs-built_in">fill</span>(shine_para.pitch_bend.<span class="hljs-built_in">begin</span>(), shine_para.pitch_bend.<span class="hljs-built_in">end</span>(), <span class="hljs-number">0</span>);<br>    }<br><br>    <span class="hljs-comment">// 计算目标帧数</span><br>    shine_para.required_frame = <span class="hljs-built_in">static_cast</span>&lt;<span class="hljs-type">int</span>&gt;(<br>        <span class="hljs-number">1000.0</span> * shine_para.output_samples / fs / frame_period) + <span class="hljs-number">1</span>;<br>}<br></code></pre></td></tr></tbody></table></figure><p><strong>Pitch Step 计算</strong>：</p><p>$$pitch\_step = \frac{60.0}{96.0 \times tempo} \times fs$$</p><p>其中：</p><ul><li>$60.0$：每分钟秒数</li><li>$96.0$：UTAU 的 Pitch Bend 采样密度（每拍 96 个点）</li><li>$tempo$：BPM</li><li>$fs$：采样率</li></ul><hr><h2 id="完整合成流程" data-id="完整合成流程" class="notion-h"><a href="#完整合成流程" class="headerlink" title="完整合成流程"></a>完整合成流程</h2><figure class="highlight plaintext"><table><tbody><tr><td class="code"><pre><code class="hljs plaintext">UTAU 命令行参数 (argc, argv)<br>         │<br>         ▼<br>    ┌────────────────┐<br>    │ libUTAU        │<br>    │  ├─ Parser     │ 解析 argv<br>    │  ├─ ScaleConv  │ 音名 → Hz<br>    │  └─ CheckPara  │ 计算参数<br>    └────────────────┘<br>         │<br>         ▼<br>    UTAUPara<br>         │<br>         ▼<br>    ┌────────────────┐<br>    │ Shine          │<br>    │  ├─ SetShine   │ 参数转换<br>    │  └─ DecodePitch│ Pitch Bend 解码<br>    └────────────────┘<br>         │<br>         ▼<br>    ShinePara<br>         │<br>         ▼<br>    ┌────────────────┐<br>    │ AudioProcess   │ 音频变换<br>    └────────────────┘<br>         │<br>         ▼<br>    ┌────────────────┐<br>    │ Synthesis      │ 合成波形<br>    └────────────────┘<br>         │<br>         ▼<br>    ┌────────────────┐<br>    │ AutoAMP        │ 振幅调整<br>    └────────────────┘<br>         │<br>         ▼<br>    输出 WAV 文件<br></code></pre></td></tr></tbody></table></figure><hr><h2 id="使用示例" data-id="使用示例" class="notion-h"><a href="#使用示例" class="headerlink" title="使用示例"></a>使用示例</h2><figure class="highlight cpp"><table><tbody><tr><td class="code"><pre><code class="hljs cpp"><span class="hljs-meta">#<span class="hljs-keyword">include</span> <span class="hljs-string">"Shine/Shine.h"</span></span><br><br><span class="hljs-comment">// UTAU 调用方式</span><br><span class="hljs-comment">// lessampler.exe input.wav output.wav C4 100 "" 0 200 50 20 100 50 !120 AA</span><br><br><span class="hljs-function"><span class="hljs-type">int</span> <span class="hljs-title">main</span><span class="hljs-params">(<span class="hljs-type">int</span> argc, <span class="hljs-type">char</span> *argv[])</span> </span>{<br>    <span class="hljs-comment">// 加载音频模型</span><br>    lessAudioModel audioModel = <span class="hljs-built_in">LoadAudioModel</span>(argv[<span class="hljs-number">1</span>]);<br><br>    <span class="hljs-comment">// 创建 Shine 管道</span><br>    <span class="hljs-function">Shine <span class="hljs-title">shine</span><span class="hljs-params">(argc, argv, audioModel, Shine::SHINE_MODE::UTAU)</span></span>;<br>    ShinePara params = shine.<span class="hljs-built_in">GetShine</span>();<br><br>    <span class="hljs-comment">// 使用参数进行音频处理</span><br>    <span class="hljs-function">AudioProcess <span class="hljs-title">processor</span><span class="hljs-params">(audioModel, params)</span></span>;<br>    lessAudioModel transformed = processor.<span class="hljs-built_in">GetTransAudioModel</span>();<br><br>    <span class="hljs-comment">// 合成并输出</span><br>    <span class="hljs-function">Synthesis <span class="hljs-title">synth</span><span class="hljs-params">(transformed, params.output_samples)</span></span>;<br>    <span class="hljs-function">AutoAMP <span class="hljs-title">amp</span><span class="hljs-params">(params, synth.GetWavData())</span></span>;<br><br>    WavIO::<span class="hljs-built_in">WriteWav</span>(params.output_file_name, amp.<span class="hljs-built_in">GetAMP</span>(),<br>        params.output_samples, audioModel.fs);<br>}<br></code></pre></td></tr></tbody></table></figure>]]>
    </content>
    <id>https://gloomyghost.com/live/2026-04-22-lessampler-shine-module.aspx</id>
    <link href="https://gloomyghost.com/live/2026-04-22-lessampler-shine-module.aspx"/>
    <published>2026-04-21T16:00:00.000Z</published>
    <summary>
      <![CDATA[<p>Shine 模块是 lessampler 的合成管道协调器，负责将 UTAU 的参数传递机制转换为内部变换参数，并驱动整个合成流程。该模块是连接外部接口（UTAU）与内部处理模块（AudioProcess、Synthesis）的关键桥梁。</p>
<p>UTAU 是日本开发]]>
    </summary>
    <title>lessampler: Shine 模块 - 合成管道与 UTAU 集成</title>
    <updated>2026-05-29T20:33:31.218Z</updated>
  </entry>
  <entry>
    <author>
      <name>柚木 鉉</name>
    </author>
    <category term="UTAU" scheme="https://gloomyghost.com/tags/UTAU/"/>
    <category term="lessampler" scheme="https://gloomyghost.com/tags/lessampler/"/>
    <content>
      <![CDATA[<p>AudioProcess 模块是 lessampler 的音频处理核心，负责对 AudioModel 分析出的参数进行变换处理。该模块实现了歌声合成中的两个关键技术：<strong>音高均衡化</strong>和<strong>时间拉伸</strong>。</p><p>在歌声合成场景中，用户指定的目标音高可能与原音频不同，同时目标音符的持续时间也与原音频不匹配。AudioProcess 模块通过精确的数学算法，将音频参数映射到目标音高和时间尺度，同时保留原音频的音色特征。</p><h2 id="模块结构" data-id="模块结构" class="notion-h"><a href="#模块结构" class="headerlink" title="模块结构"></a>模块结构</h2><figure class="highlight plaintext"><table><tbody><tr><td class="code"><pre><code class="hljs plaintext">AudioProcess/<br>├── AudioProcess.h/cpp   # 音频变换处理（音高均衡、时间拉伸）<br>└── AutoAMP.h/cpp        # 自动振幅调整<br></code></pre></td></tr></tbody></table></figure><hr><h2 id="AudioProcess-类详解" data-id="AudioProcess-类详解" class="notion-h"><a href="#AudioProcess-类详解" class="headerlink" title="AudioProcess 类详解"></a>AudioProcess 类详解</h2><h3 id="类定义" data-id="类定义" class="notion-h"><a href="#类定义" class="headerlink" title="类定义"></a>类定义</h3><figure class="highlight cpp"><table><tbody><tr><td class="code"><pre><code class="hljs cpp"><span class="hljs-keyword">class</span> <span class="hljs-title class_">AudioProcess</span> {<br><span class="hljs-keyword">public</span>:<br>    <span class="hljs-built_in">AudioProcess</span>(lessAudioModel audioModel, ShinePara shine);<br><br>    <span class="hljs-function">lessAudioModel <span class="hljs-title">GetTransAudioModel</span><span class="hljs-params">()</span></span>;<br><br><span class="hljs-keyword">private</span>:<br>    lessAudioModel audioModel{};      <span class="hljs-comment">// 原始音频模型</span><br>    lessAudioModel transAudioModel{}; <span class="hljs-comment">// 变换后的音频模型</span><br>    ShinePara shine;                  <span class="hljs-comment">// 变换参数</span><br><br><span class="hljs-keyword">private</span>:<br>    <span class="hljs-function"><span class="hljs-type">void</span> <span class="hljs-title">InitTransAudioModel</span><span class="hljs-params">()</span></span>;       <span class="hljs-comment">// 初始化变换模型</span><br>    <span class="hljs-function"><span class="hljs-type">void</span> <span class="hljs-title">PicthEqualizing</span><span class="hljs-params">()</span></span>;           <span class="hljs-comment">// 音高均衡化</span><br>    <span class="hljs-function"><span class="hljs-type">double</span> <span class="hljs-title">GetAvgFreq</span><span class="hljs-params">()</span> <span class="hljs-type">const</span></span>;        <span class="hljs-comment">// 计算平均频率</span><br>    <span class="hljs-function"><span class="hljs-type">void</span> <span class="hljs-title">TimeStretch</span><span class="hljs-params">()</span></span>;               <span class="hljs-comment">// 时间拉伸</span><br><br><span class="hljs-keyword">private</span>:<br>    <span class="hljs-function"><span class="hljs-type">static</span> <span class="hljs-type">void</span> <span class="hljs-title">interp1</span><span class="hljs-params">(...)</span></span>;         <span class="hljs-comment">// 一维插值（备用）</span><br>    <span class="hljs-function"><span class="hljs-type">static</span> <span class="hljs-type">void</span> <span class="hljs-title">histc</span><span class="hljs-params">(...)</span></span>;           <span class="hljs-comment">// 直方图计数（备用）</span><br>};<br></code></pre></td></tr></tbody></table></figure><h3 id="构造函数流程" data-id="构造函数流程" class="notion-h"><a href="#构造函数流程" class="headerlink" title="构造函数流程"></a>构造函数流程</h3><figure class="highlight cpp"><table><tbody><tr><td class="code"><pre><code class="hljs cpp">AudioProcess::<span class="hljs-built_in">AudioProcess</span>(lessAudioModel audioModel, ShinePara shine) {<br>    <span class="hljs-comment">// 1. 初始化变换模型（复制原始模型）</span><br>    YALL_DEBUG_ &lt;&lt; <span class="hljs-string">"Init TransAudioModel default data..."</span>;<br>    <span class="hljs-built_in">InitTransAudioModel</span>();<br><br>    <span class="hljs-comment">// 2. 音高均衡化（调整 F0 到目标音高）</span><br>    YALL_DEBUG_ &lt;&lt; <span class="hljs-string">"Equalizing Pitch..."</span>;<br>    <span class="hljs-built_in">PicthEqualizing</span>();<br><br>    <span class="hljs-comment">// 3. 时间拉伸（调整帧数到目标长度）</span><br>    YALL_DEBUG_ &lt;&lt; <span class="hljs-string">"Time Stretch..."</span>;<br>    <span class="hljs-built_in">TimeStretch</span>();<br>}<br></code></pre></td></tr></tbody></table></figure><hr><h2 id="音高均衡化（PitchEqualizing）" data-id="音高均衡化（PitchEqualizing）" class="notion-h"><a href="#音高均衡化（PitchEqualizing）" class="headerlink" title="音高均衡化（PitchEqualizing）"></a>音高均衡化（PitchEqualizing）</h2><h3 id="GetAvgFreq-函数" data-id="GetAvgFreq-函数" class="notion-h"><a href="#GetAvgFreq-函数" class="headerlink" title="GetAvgFreq() 函数"></a>GetAvgFreq() 函数</h3><p>音高均衡化首先需要计算原音频的平均频率，使用加权平均算法：</p><figure class="highlight cpp"><table><tbody><tr><td class="code"><pre><code class="hljs cpp"><span class="hljs-function"><span class="hljs-type">double</span> <span class="hljs-title">AudioProcess::GetAvgFreq</span><span class="hljs-params">()</span> <span class="hljs-type">const</span> </span>{<br>    <span class="hljs-type">double</span> freq_avg = <span class="hljs-number">0.0</span>, timePercent, r, p[<span class="hljs-number">6</span>], q, base_timePercent = <span class="hljs-number">0</span>;<br><br>    <span class="hljs-keyword">for</span> (<span class="hljs-type">int</span> i = <span class="hljs-number">0</span>; i &lt; audioModel.f<span class="hljs-number">0.</span><span class="hljs-built_in">size</span>(); ++i) {<br>        timePercent = audioModel.f0[i];<br><br>        <span class="hljs-comment">// 只处理有效频率范围（55Hz - 1000Hz）</span><br>        <span class="hljs-keyword">if</span> (timePercent &lt; <span class="hljs-number">1000.0</span> &amp;&amp; timePercent &gt; <span class="hljs-number">55.0</span>) {<br>            r = <span class="hljs-number">1.0</span>;<br>            <span class="hljs-comment">// 计算 6 个相邻帧的权重</span><br>            <span class="hljs-keyword">for</span> (<span class="hljs-type">int</span> j = <span class="hljs-number">0</span>; j &lt;= <span class="hljs-number">5</span>; ++j) {<br>                <span class="hljs-keyword">if</span> (i &gt; j) {<br>                    q = audioModel.f0[i - j - <span class="hljs-number">1</span>] - timePercent;<br>                    <span class="hljs-comment">// 权重公式：当前帧频率 / (当前频率 + 差值平方)</span><br>                    p[j] = timePercent / (timePercent + q * q);<br>                } <span class="hljs-keyword">else</span> {<br>                    p[j] = <span class="hljs-number">1</span> / (<span class="hljs-number">1</span> + timePercent);<br>                }<br>                r *= p[j];<br>            }<br>            <span class="hljs-comment">// 累加加权值</span><br>            freq_avg += timePercent * r;<br>            base_timePercent += r;<br>        }<br>    }<br><br>    <span class="hljs-comment">// 计算加权平均</span><br>    <span class="hljs-keyword">if</span> (base_timePercent &gt; <span class="hljs-number">0</span>)<br>        freq_avg /= base_timePercent;<br>    <span class="hljs-keyword">return</span> freq_avg;<br>}<br></code></pre></td></tr></tbody></table></figure><p><strong>算法解析</strong>：</p><p>这是一个<strong>自适应加权平均</strong>算法，特点如下：</p><ol><li><strong>频率范围过滤</strong>：只考虑 55Hz-1000Hz 范围内的 F0 值，排除异常值和静音帧</li><li><strong>相邻帧权重</strong>：计算当前帧与前后 6 帧的相似度权重</li><li><strong>平滑处理</strong>：权重公式 <code>p[j] = f0 / (f0 + diff²)</code> 使得相邻帧差异越大，权重越低</li><li><strong>抗噪声能力</strong>：能抵抗瞬时 F0 估计错误的影响</li></ol><p>权重公式的数学推导：</p><p>当 $q = f_0[i-j-1] - f_0[i]$ 时：</p><ul><li>$q = 0$（完全相同）：$p[j] = 1.0$（最大权重）</li><li>$q$ 很大（差异大）：$p[j] \to 0$（低权重）</li></ul><p>$$p[j] = \frac{f_0}{f_0 + q^2}$$</p><p>其中 $q = f_{0,i-j-1} - f_{0,i}$</p><h3 id="PicthEqualizing-函数" data-id="PicthEqualizing-函数" class="notion-h"><a href="#PicthEqualizing-函数" class="headerlink" title="PicthEqualizing() 函数"></a>PicthEqualizing() 函数</h3><figure class="highlight cpp"><table><tbody><tr><td class="code"><pre><code class="hljs cpp"><span class="hljs-function"><span class="hljs-type">void</span> <span class="hljs-title">AudioProcess::PicthEqualizing</span><span class="hljs-params">()</span> </span>{<br>    <span class="hljs-keyword">auto</span> freq_avg = <span class="hljs-built_in">GetAvgFreq</span>();<br>    YALL_DEBUG_ &lt;&lt; <span class="hljs-string">"The average frequency is "</span> + std::<span class="hljs-built_in">to_string</span>(freq_avg);<br><br>    <span class="hljs-keyword">if</span> (freq_avg == <span class="hljs-number">0.0</span>) {<br>        <span class="hljs-comment">// 特殊情况：全静音，直接设为目标频率</span><br>        <span class="hljs-keyword">for</span> (<span class="hljs-type">double</span> &amp;i: audioModel.f0) {<br>            <span class="hljs-keyword">if</span> (i != <span class="hljs-number">0.0</span>) {<br>                i = shine.scale_num;<br>            } <span class="hljs-keyword">else</span> {<br>                i = <span class="hljs-number">0</span>;<br>            }<br>        }<br>    } <span class="hljs-keyword">else</span> {<br>        <span class="hljs-comment">// 正常情况：应用调制公式</span><br>        <span class="hljs-keyword">for</span> (<span class="hljs-type">double</span> &amp;i: audioModel.f0) {<br>            <span class="hljs-keyword">if</span> (i != <span class="hljs-number">0.0</span>) {<br>                <span class="hljs-comment">// 调制公式</span><br>                i = ((i - freq_avg) * shine.modulation / <span class="hljs-number">100.0</span> + freq_avg)<br>                    * (shine.scale_num / freq_avg);<br>            } <span class="hljs-keyword">else</span> {<br>                i = <span class="hljs-number">0</span>;<br>            }<br>        }<br>    }<br>}<br></code></pre></td></tr></tbody></table></figure><p><strong>调制公式详解</strong>：</p><p>$$f_{new} = \left((f_0 - f_{avg}) \cdot \frac{modulation}{100} + f_{avg}\right) \cdot \frac{f_{target}}{f_{avg}}$$</p><p>这个公式由两部分组成：</p><ol><li><p><strong>局部调制</strong>：$(f_0 - f_{avg}) \cdot \frac{modulation}{100} + f_{avg}$</p><ul><li>$modulation$ 是保留原音色特性的程度（0-100）</li><li>$modulation = 100$：完全保留原 F0 轮廓</li><li>$modulation = 0$：所有 F0 变为平均值</li></ul></li><li><p><strong>全局缩放</strong>：$\cdot \frac{f_{target}}{f_{avg}}$</p><ul><li>将整体音高偏移到目标音高</li><li>$f_{target}$ 是目标音符的频率（如 C4 = 261.63Hz）</li></ul></li></ol><p><strong>公式展开</strong>：</p><p>$$f_{new} = f_0 \cdot \frac{modulation}{100} \cdot \frac{f_{target}}{f_{avg}} + f_{avg} \cdot \left(1 - \frac{modulation}{100}\right) \cdot \frac{f_{target}}{f_{avg}}$$</p><p>当 $modulation = 100$ 时：</p><p>$$f_{new} = f_0 \cdot \frac{f_{target}}{f_{avg}}$$</p><p>实现纯音高偏移，保留 F0 轮廓形状。</p><hr><h2 id="时间拉伸（TimeStretch）" data-id="时间拉伸（TimeStretch）" class="notion-h"><a href="#时间拉伸（TimeStretch）" class="headerlink" title="时间拉伸（TimeStretch）"></a>时间拉伸（TimeStretch）</h2><p>时间拉伸是该模块最复杂的部分，负责将原始帧映射到目标时间尺度。</p><h3 id="概念图示" data-id="概念图示" class="notion-h"><a href="#概念图示" class="headerlink" title="概念图示"></a>概念图示</h3><p>代码注释中给出了清晰的示意图：</p><figure class="highlight plaintext"><table><tbody><tr><td class="code"><pre><code class="hljs plaintext">//  offset   fixed   pre_cross   blank<br>//|--------|--------|---------|---------| Original Signal<br>//         |        |          |<br>//         |   l1   |    l2     |<br>//         |--------|------------|        Output Signal<br>// l1  = fixed / velocity              -&gt; base_length<br>// l2  = pre_cross / stretch           -&gt; cross_length<br>// l1 + l2 = required_length           -&gt; required_length<br></code></pre></td></tr></tbody></table></figure><p>这描述了 UTAU 合成的音频结构：</p><ul><li><strong>offset</strong>：原音频的起始偏移</li><li><strong>fixed</strong>：固定部分（不拉伸）</li><li><strong>pre_cross</strong>：预交叉部分（用于拉伸）</li><li><strong>blank</strong>：空白部分（不使用）</li></ul><h3 id="TimeStretch-函数核心实现" data-id="TimeStretch-函数核心实现" class="notion-h"><a href="#TimeStretch-函数核心实现" class="headerlink" title="TimeStretch() 函数核心实现"></a>TimeStretch() 函数核心实现</h3><figure class="highlight cpp"><table><tbody><tr><td class="code"><pre><code class="hljs cpp"><span class="hljs-function"><span class="hljs-type">void</span> <span class="hljs-title">AudioProcess::TimeStretch</span><span class="hljs-params">()</span> </span>{<br>    <span class="hljs-comment">// 分配目标帧内存</span><br>    transAudioModel.f<span class="hljs-number">0.</span><span class="hljs-built_in">resize</span>(shine.required_frame);<br>    transAudioModel.spectrogram.<span class="hljs-built_in">resize</span>(transAudioModel.f<span class="hljs-number">0.</span><span class="hljs-built_in">size</span>(),<br>        std::<span class="hljs-built_in">vector</span>&lt;<span class="hljs-type">double</span>&gt;(audioModel.w_length));<br>    transAudioModel.aperiodicity.<span class="hljs-built_in">resize</span>(transAudioModel.f<span class="hljs-number">0.</span><span class="hljs-built_in">size</span>(),<br>        std::<span class="hljs-built_in">vector</span>&lt;<span class="hljs-type">double</span>&gt;(audioModel.w_length));<br><br>    <span class="hljs-keyword">auto</span> avg_freq = <span class="hljs-built_in">GetAvgFreq</span>();<br><br>    <span class="hljs-keyword">for</span> (<span class="hljs-type">int</span> i = <span class="hljs-number">0</span>; i &lt; transAudioModel.f<span class="hljs-number">0.</span><span class="hljs-built_in">size</span>(); ++i) {<br>        <span class="hljs-comment">// 计算输出帧对应的时间位置</span><br>        _out_sample_index = audioModel.frame_period * i;<br><br>        <span class="hljs-comment">// 计算对应的输入时间位置（分段映射）</span><br>        <span class="hljs-keyword">if</span> (_out_sample_index &lt; shine.base_length) {<br>            <span class="hljs-comment">// 固定部分：直接映射，考虑 velocity</span><br>            _in_sample_index = shine.offset + _out_sample_index * shine.velocity;<br>        } <span class="hljs-keyword">else</span> {<br>            <span class="hljs-comment">// 拉伸部分：应用 stretch_length 缩放</span><br>            _in_sample_index = shine.offset + shine.first_half_fixed_part<br>                + (_out_sample_index - shine.base_length) * shine.stretch_length;<br>        }<br><br>        <span class="hljs-comment">// 计算帧索引和插值位置</span><br>        _sample_sp_trans_index = _in_sample_index / audioModel.frame_period;<br>        _sp_trans_index = <span class="hljs-built_in">static_cast</span>&lt;<span class="hljs-type">int</span>&gt;(<span class="hljs-built_in">floor</span>(_sample_sp_trans_index));<br>        _sample_sp_trans_index -= _sp_trans_index;<br><br>        <span class="hljs-comment">// F0 插值处理</span><br>        <span class="hljs-keyword">auto</span> temp_f0 = audioModel.f0[_sp_trans_index];<br>        <span class="hljs-keyword">if</span> (_sp_trans_index &lt; audioModel.f<span class="hljs-number">0.</span><span class="hljs-built_in">size</span>() - <span class="hljs-number">1</span>) {<br>            <span class="hljs-keyword">auto</span> temp_f0_next = audioModel.f0[_sp_trans_index + <span class="hljs-number">1</span>];<br>            <span class="hljs-keyword">if</span> (temp_f0 != <span class="hljs-number">0</span> || temp_f0_next != <span class="hljs-number">0</span>) {<br>                <span class="hljs-keyword">if</span> (temp_f0 == <span class="hljs-number">0</span>) temp_f0 = avg_freq;<br>                <span class="hljs-keyword">if</span> (temp_f0_next == <span class="hljs-number">0</span>) temp_f0_next = avg_freq;<br>                <span class="hljs-comment">// 线性插值</span><br>                temp_f0 = temp_f0 * (<span class="hljs-number">1.0</span> - _sample_sp_trans_index)<br>                    + temp_f0_next * _sample_sp_trans_index;<br>            }<br>        }<br><br>        <span class="hljs-comment">// 应用 Pitch Bend</span><br>        _sample_ap_trans_index = _out_sample_index * <span class="hljs-number">0.001</span> * audioModel.fs / shine.pitch_step;<br>        _ap_trans_index = <span class="hljs-built_in">static_cast</span>&lt;<span class="hljs-type">int</span>&gt;(<span class="hljs-built_in">floor</span>(_sample_ap_trans_index));<br>        _sample_ap_trans_index -= _ap_trans_index;<br><br>        <span class="hljs-comment">// Pitch Bend 插值</span><br>        <span class="hljs-keyword">auto</span> pitch_base = shine.scale_num * <span class="hljs-built_in">pow</span>(<span class="hljs-number">2</span>,<br>            (shine.pitch_bend[_ap_trans_index] * (<span class="hljs-number">1.0</span> - _sample_ap_trans_index) +<br>             shine.pitch_bend[_ap_trans_index + <span class="hljs-number">1</span>] * _sample_ap_trans_index) / <span class="hljs-number">1200.0</span>);<br><br>        <span class="hljs-comment">// 设置变换后的 F0</span><br>        transAudioModel.f0[i] = pitch_base;<br>        transAudioModel.f0[i] *= <span class="hljs-built_in">pow</span>(temp_f0 / avg_freq, shine.modulation * <span class="hljs-number">0.01</span>);<br><br>        <span class="hljs-comment">// 频谱包络插值</span><br>        <span class="hljs-keyword">for</span> (<span class="hljs-type">int</span> j = <span class="hljs-number">0</span>; j &lt; audioModel.w_length; ++j) {<br>            <span class="hljs-keyword">if</span> (_sp_trans_index &lt; audioModel.f<span class="hljs-number">0.</span><span class="hljs-built_in">size</span>() - <span class="hljs-number">1</span>) {<br>                transAudioModel.spectrogram[i][j] =<br>                    audioModel.spectrogram[_sp_trans_index][j] * (<span class="hljs-number">1.0</span> - _sample_sp_trans_index) +<br>                    audioModel.spectrogram[_sp_trans_index + <span class="hljs-number">1</span>][j] * _sample_sp_trans_index;<br>            } <span class="hljs-keyword">else</span> {<br>                transAudioModel.spectrogram[i][j] =<br>                    audioModel.spectrogram[audioModel.f<span class="hljs-number">0.</span><span class="hljs-built_in">size</span>() - <span class="hljs-number">1</span>][j];<br>            }<br>        }<br><br>        <span class="hljs-comment">// 非周期性（选择最近的帧）</span><br>        _ap_trans_index = _sp_trans_index;<br>        <span class="hljs-keyword">if</span> (_sample_sp_trans_index &gt; <span class="hljs-number">0.5</span>) ++_ap_trans_index;<br><br>        <span class="hljs-keyword">for</span> (<span class="hljs-type">int</span> j = <span class="hljs-number">0</span>; j &lt; audioModel.w_length; ++j) {<br>            <span class="hljs-keyword">if</span> (_ap_trans_index &lt; audioModel.f<span class="hljs-number">0.</span><span class="hljs-built_in">size</span>()) {<br>                transAudioModel.aperiodicity[i][j] = audioModel.aperiodicity[_ap_trans_index][j];<br>            } <span class="hljs-keyword">else</span> {<br>                transAudioModel.aperiodicity[i][j] = audioModel.aperiodicity[audioModel.f<span class="hljs-number">0.</span><span class="hljs-built_in">size</span>() - <span class="hljs-number">1</span>][j];<br>            }<br>        }<br>    }<br>}<br></code></pre></td></tr></tbody></table></figure><h3 id="时间映射详解" data-id="时间映射详解" class="notion-h"><a href="#时间映射详解" class="headerlink" title="时间映射详解"></a>时间映射详解</h3><p><strong>分段映射公式</strong>：</p><p>$$t_{in} = \begin{cases} offset + t_{out} \cdot velocity &amp; \text{if } t_{out} &lt; base\_length \\ offset + fixed + (t_{out} - base\_length) \cdot stretch &amp; \text{otherwise} \end{cases}$$</p><p>其中：</p><ul><li>$velocity = 2^{time\_percent/100 - 1}$：影响固定部分的时长</li><li>$stretch = \frac{pre\_cross\_length}{cross\_length}$：拉伸比例</li></ul><p><strong>参数关系</strong>：</p><p>$$base\_length = \frac{fixed}{velocity}$$</p><p>$$cross\_length = required\_length - base\_length$$</p><p>$$stretch = \frac{pre\_cross}{cross\_length}$$</p><h3 id="Pitch-Bend-应用" data-id="Pitch-Bend-应用" class="notion-h"><a href="#Pitch-Bend-应用" class="headerlink" title="Pitch Bend 应用"></a>Pitch Bend 应用</h3><p>Pitch Bend 是 UTAU 的弯音控制，以音分（cents）为单位，10 cents = 1 半音。</p><figure class="highlight cpp"><table><tbody><tr><td class="code"><pre><code class="hljs cpp"><span class="hljs-comment">// 计算弯音位置</span><br>_ap_trans_index = out_time * fs / pitch_step;<br><br><span class="hljs-comment">// Pitch Bend 插值得到弯音值</span><br>bend_value = pitch_bend[index] * (<span class="hljs-number">1</span> - frac) + pitch_bend[index<span class="hljs-number">+1</span>] * frac;<br><br><span class="hljs-comment">// 应用弯音（cents 转频率）</span><br>pitch_base = scale_num * <span class="hljs-built_in">pow</span>(<span class="hljs-number">2</span>, bend_value / <span class="hljs-number">1200.0</span>);<br></code></pre></td></tr></tbody></table></figure><p><strong>公式解释</strong>：</p><ul><li>$pitch\_step = \frac{60.0}{96.0 \cdot tempo} \cdot fs$：弯音采样步长</li><li>$\frac{bend\_value}{1200.0}$：cents 转半音（1200 cents = 12 半音 = 1 倍频）</li><li>$2^{(\cdot)}$：半音转频率比例</li></ul><p><strong>Pitch Bend 公式</strong>：</p><p>$$f_{bend} = f_{base} \cdot 2^{\frac{bend\_value}{1200}}$$</p><p>其中 $bend\_value$ 是线性插值后的弯音值：</p><p>$$bend\_value = bend_i \cdot (1 - \alpha) + bend_{i+1} \cdot \alpha$$</p><p>$\alpha$ 为插值系数。</p><hr><h2 id="AutoAMP-类详解" data-id="AutoAMP-类详解" class="notion-h"><a href="#AutoAMP-类详解" class="headerlink" title="AutoAMP 类详解"></a>AutoAMP 类详解</h2><p>AutoAMP 负责音频输出的振幅自动调整。</p><h3 id="类定义-1" data-id="类定义-1" class="notion-h"><a href="#类定义-1" class="headerlink" title="类定义"></a>类定义</h3><figure class="highlight cpp"><table><tbody><tr><td class="code"><pre><code class="hljs cpp"><span class="hljs-keyword">class</span> <span class="hljs-title class_">AutoAMP</span> {<br><span class="hljs-keyword">public</span>:<br>    <span class="hljs-built_in">AutoAMP</span>(ShinePara shine, <span class="hljs-type">double</span> *x);       <span class="hljs-comment">// 使用 Shine 参数</span><br>    <span class="hljs-built_in">AutoAMP</span>(<span class="hljs-type">double</span> *x, <span class="hljs-type">int</span> x_length, <span class="hljs-type">double</span> amp_val);  <span class="hljs-comment">// 仅 WAV</span><br>    <span class="hljs-function"><span class="hljs-type">double</span> *<span class="hljs-title">GetAMP</span><span class="hljs-params">()</span></span>;<br><br><span class="hljs-keyword">private</span>:<br>    ShinePara shine;<br>    <span class="hljs-type">int</span> x_length = <span class="hljs-number">0</span>;<br>    <span class="hljs-type">double</span> *x = <span class="hljs-literal">nullptr</span>;        <span class="hljs-comment">// 输入音频</span><br>    <span class="hljs-type">double</span> *x_out = <span class="hljs-literal">nullptr</span>;    <span class="hljs-comment">// 输出音频</span><br><br>    <span class="hljs-type">const</span> <span class="hljs-type">double</span> default_sample_value = <span class="hljs-number">0.86</span>;<br>    <span class="hljs-type">const</span> <span class="hljs-type">double</span> MaxValue = <span class="hljs-number">1.0</span>;<br>    <span class="hljs-type">const</span> <span class="hljs-type">double</span> MinValue = <span class="hljs-number">-1.0</span>;<br><br>    <span class="hljs-type">double</span> sample_value = <span class="hljs-number">0.0</span>;<br>    <span class="hljs-type">double</span> MaxAMP = <span class="hljs-number">0.0</span>;<br><br><span class="hljs-keyword">private</span>:<br>    <span class="hljs-function"><span class="hljs-type">void</span> <span class="hljs-title">GetMaxAMP</span><span class="hljs-params">()</span></span>;                      <span class="hljs-comment">// 计算最大振幅</span><br>    <span class="hljs-function"><span class="hljs-type">void</span> <span class="hljs-title">SetDefaultValue</span><span class="hljs-params">()</span></span>;                <span class="hljs-comment">// 设置默认值</span><br>    <span class="hljs-function"><span class="hljs-type">void</span> <span class="hljs-title">DiminishedConsonantFricative</span><span class="hljs-params">()</span></span>;   <span class="hljs-comment">// 辅音衰减</span><br>    <span class="hljs-function"><span class="hljs-type">void</span> <span class="hljs-title">LimitMaximumAmplitude</span><span class="hljs-params">()</span></span>;          <span class="hljs-comment">// 限幅</span><br>};<br></code></pre></td></tr></tbody></table></figure><h3 id="构造函数流程-1" data-id="构造函数流程-1" class="notion-h"><a href="#构造函数流程-1" class="headerlink" title="构造函数流程"></a>构造函数流程</h3><figure class="highlight cpp"><table><tbody><tr><td class="code"><pre><code class="hljs cpp">AutoAMP::<span class="hljs-built_in">AutoAMP</span>(ShinePara shine, <span class="hljs-type">double</span> *x) {<br>    <span class="hljs-keyword">this</span>-&gt;x_length = shine.output_samples;<br>    <span class="hljs-keyword">this</span>-&gt;x = x;<br>    <span class="hljs-keyword">this</span>-&gt;x_out = <span class="hljs-keyword">new</span> <span class="hljs-type">double</span>[x_length];<br><br>    <span class="hljs-comment">// 1. 计算最大振幅</span><br>    <span class="hljs-built_in">GetMaxAMP</span>();<br><br>    <span class="hljs-comment">// 2. 设置默认值</span><br>    <span class="hljs-built_in">SetDefaultValue</span>();<br><br>    <span class="hljs-comment">// 3. 辅音衰减处理</span><br>    <span class="hljs-built_in">DiminishedConsonantFricative</span>();<br><br>    <span class="hljs-comment">// 4. 限幅处理</span><br>    <span class="hljs-built_in">LimitMaximumAmplitude</span>();<br>}<br></code></pre></td></tr></tbody></table></figure><h3 id="GetMaxAMP-函数" data-id="GetMaxAMP-函数" class="notion-h"><a href="#GetMaxAMP-函数" class="headerlink" title="GetMaxAMP() 函数"></a>GetMaxAMP() 函数</h3><figure class="highlight cpp"><table><tbody><tr><td class="code"><pre><code class="hljs cpp"><span class="hljs-function"><span class="hljs-type">void</span> <span class="hljs-title">AutoAMP::GetMaxAMP</span><span class="hljs-params">()</span> </span>{<br>    <span class="hljs-keyword">for</span> (<span class="hljs-type">int</span> i = <span class="hljs-number">0</span>; i &lt; x_length - <span class="hljs-number">1</span>; ++i) {<br>        <span class="hljs-keyword">if</span> (!std::<span class="hljs-built_in">isnan</span>(x[i])) {<br>            <span class="hljs-keyword">if</span> (MaxAMP &lt; std::<span class="hljs-built_in">abs</span>(x[i])) {<br>                MaxAMP = std::<span class="hljs-built_in">abs</span>(x[i]);<br>            }<br>        }<br>    }<br>    <span class="hljs-keyword">if</span> (MaxAMP == <span class="hljs-number">0.0</span>) {<br>        YALL_WARN_ &lt;&lt; <span class="hljs-string">"Max AMP is Zero."</span>;<br>    }<br>}<br></code></pre></td></tr></tbody></table></figure><p>遍历所有采样点，找出绝对值最大的振幅，用于后续归一化。</p><h3 id="DiminishedConsonantFricative-函数" data-id="DiminishedConsonantFricative-函数" class="notion-h"><a href="#DiminishedConsonantFricative-函数" class="headerlink" title="DiminishedConsonantFricative() 函数"></a>DiminishedConsonantFricative() 函数</h3><figure class="highlight cpp"><table><tbody><tr><td class="code"><pre><code class="hljs cpp"><span class="hljs-function"><span class="hljs-type">void</span> <span class="hljs-title">AutoAMP::DiminishedConsonantFricative</span><span class="hljs-params">()</span> </span>{<br>    <span class="hljs-keyword">for</span> (<span class="hljs-type">int</span> i = <span class="hljs-number">0</span>; i &lt; x_length; ++i) {<br>        <span class="hljs-comment">// 处理 NaN 值（静音）</span><br>        <span class="hljs-keyword">if</span> (std::<span class="hljs-built_in">isnan</span>(x[i])) {<br>            x_out[i] = <span class="hljs-number">0.0</span>;<br>        } <span class="hljs-keyword">else</span> {<br>            <span class="hljs-comment">// 归一化并应用音量</span><br>            x_out[i] = x[i] * <span class="hljs-number">0.5</span> * shine.volumes / MaxAMP;<br>        }<br>    }<br>}<br></code></pre></td></tr></tbody></table></figure><p><strong>算法要点</strong>：</p><ul><li><code>* 0.5</code>：衰减系数，防止输出过大</li><li><code>shine.volumes</code>：用户指定的音量百分比（已乘 0.01）</li><li><code>/ MaxAMP</code>：归一化，使最大振幅映射到目标值</li></ul><h3 id="LimitMaximumAmplitude-函数" data-id="LimitMaximumAmplitude-函数" class="notion-h"><a href="#LimitMaximumAmplitude-函数" class="headerlink" title="LimitMaximumAmplitude() 函数"></a>LimitMaximumAmplitude() 函数</h3><figure class="highlight cpp"><table><tbody><tr><td class="code"><pre><code class="hljs cpp"><span class="hljs-function"><span class="hljs-type">void</span> <span class="hljs-title">AutoAMP::LimitMaximumAmplitude</span><span class="hljs-params">()</span> </span>{<br>    <span class="hljs-keyword">for</span> (<span class="hljs-type">int</span> i = <span class="hljs-number">0</span>; i &lt; x_length; ++i) {<br>        <span class="hljs-keyword">if</span> (x_out[i] &gt; MaxValue) {<br>            x_out[i] = MaxValue;  <span class="hljs-comment">// 限制到 1.0</span><br>        } <span class="hljs-keyword">else</span> <span class="hljs-keyword">if</span> (x_out[i] &lt; MinValue) {<br>            x_out[i] = MinValue;  <span class="hljs-comment">// 限制到 -1.0</span><br>        }<br>    }<br>}<br></code></pre></td></tr></tbody></table></figure><p>硬限幅，防止音频超过 [-1.0, 1.0] 范围，避免播放时的削波失真。</p><hr><h2 id="辅助函数" data-id="辅助函数" class="notion-h"><a href="#辅助函数" class="headerlink" title="辅助函数"></a>辅助函数</h2><h3 id="interp1-一维插值" data-id="interp1-一维插值" class="notion-h"><a href="#interp1-一维插值" class="headerlink" title="interp1() 一维插值"></a>interp1() 一维插值</h3><figure class="highlight cpp"><table><tbody><tr><td class="code"><pre><code class="hljs cpp"><span class="hljs-function"><span class="hljs-type">void</span> <span class="hljs-title">AudioProcess::interp1</span><span class="hljs-params">(<span class="hljs-type">const</span> <span class="hljs-type">double</span> *x, <span class="hljs-type">const</span> <span class="hljs-type">double</span> *y, <span class="hljs-type">int</span> x_length,</span></span><br><span class="hljs-params"><span class="hljs-function">    <span class="hljs-type">const</span> <span class="hljs-type">double</span> *xi, <span class="hljs-type">int</span> xi_length, <span class="hljs-type">double</span> *yi)</span> </span>{<br>    <span class="hljs-comment">// 计算步长 h</span><br>    <span class="hljs-keyword">auto</span> *h = <span class="hljs-keyword">new</span> <span class="hljs-type">double</span>[x_length - <span class="hljs-number">1</span>];<br>    <span class="hljs-keyword">for</span> (<span class="hljs-type">int</span> i = <span class="hljs-number">0</span>; i &lt; x_length - <span class="hljs-number">1</span>; ++i) {<br>        h[i] = x[i + <span class="hljs-number">1</span>] - x[i];<br>    }<br><br>    <span class="hljs-comment">// 找到每个 xi 对应的区间</span><br>    <span class="hljs-type">int</span> *k = <span class="hljs-keyword">new</span> <span class="hljs-type">int</span>[xi_length];<br>    <span class="hljs-built_in">histc</span>(x, x_length, xi, xi_length, k);<br><br>    <span class="hljs-comment">// 线性插值</span><br>    <span class="hljs-keyword">for</span> (<span class="hljs-type">int</span> i = <span class="hljs-number">0</span>; i &lt; xi_length; ++i) {<br>        <span class="hljs-type">double</span> s = (xi[i] - x[k[i] - <span class="hljs-number">1</span>]) / h[k[i] - <span class="hljs-number">1</span>];<br>        yi[i] = y[k[i] - <span class="hljs-number">1</span>] + s * (y[k[i]] - y[k[i] - <span class="hljs-number">1</span>]);<br>    }<br><br>    <span class="hljs-keyword">delete</span>[] k;<br>    <span class="hljs-keyword">delete</span>[] h;<br>}<br></code></pre></td></tr></tbody></table></figure><h3 id="histc-直方图计数" data-id="histc-直方图计数" class="notion-h"><a href="#histc-直方图计数" class="headerlink" title="histc() 直方图计数"></a>histc() 直方图计数</h3><figure class="highlight cpp"><table><tbody><tr><td class="code"><pre><code class="hljs cpp"><span class="hljs-function"><span class="hljs-type">void</span> <span class="hljs-title">AudioProcess::histc</span><span class="hljs-params">(<span class="hljs-type">const</span> <span class="hljs-type">double</span> *x, <span class="hljs-type">int</span> x_length,</span></span><br><span class="hljs-params"><span class="hljs-function">    <span class="hljs-type">const</span> <span class="hljs-type">double</span> *edges, <span class="hljs-type">int</span> edges_length, <span class="hljs-type">int</span> *index)</span> </span>{<br>    <span class="hljs-type">int</span> count = <span class="hljs-number">1</span>;<br><br>    <span class="hljs-type">int</span> i = <span class="hljs-number">0</span>;<br>    <span class="hljs-keyword">for</span> (; i &lt; edges_length; ++i) {<br>        index[i] = <span class="hljs-number">1</span>;<br>        <span class="hljs-keyword">if</span> (edges[i] &gt;= x[<span class="hljs-number">0</span>]) <span class="hljs-keyword">break</span>;<br>    }<br><br>    <span class="hljs-keyword">for</span> (; i &lt; edges_length; ++i) {<br>        <span class="hljs-keyword">if</span> (edges[i] &lt; x[count]) {<br>            index[i] = count;<br>        } <span class="hljs-keyword">else</span> {<br>            index[i--] = count++;<br>        }<br>        <span class="hljs-keyword">if</span> (count == x_length) <span class="hljs-keyword">break</span>;<br>    }<br><br>    count--;<br>    <span class="hljs-keyword">for</span> (i++; i &lt; edges_length; ++i)<br>        index[i] = count;<br>}<br></code></pre></td></tr></tbody></table></figure><p>这两个函数是 MATLAB <code>interp1</code> 和 <code>histc</code> 的 C++ 实现，用于高级插值场景，当前主要逻辑中未使用。</p><hr><h2 id="数据流图" data-id="数据流图" class="notion-h"><a href="#数据流图" class="headerlink" title="数据流图"></a>数据流图</h2><figure class="highlight plaintext"><table><tbody><tr><td class="code"><pre><code class="hljs plaintext">lessAudioModel (原始)<br>     │<br>     ▼<br>┌─────────────────┐<br>│ GetAvgFreq      │ 计算加权平均频率<br>└─────────────────┘<br>     │<br>     ▼<br>┌─────────────────┐<br>│ PicthEqualizing │ F0 调制 → 目标音高<br>└─────────────────┘<br>     │<br>     ▼<br>┌─────────────────┐<br>│ TimeStretch     │ 帧映射 + Pitch Bend<br>│  - 分段映射     │<br>│  - F0 插值      │<br>│  - SP 插值      │<br>│  - AP 选择      │<br>└─────────────────┘<br>     │<br>     ▼<br>lessAudioModel (变换后)<br>     │<br>     ▼<br>┌─────────────────┐<br>│ Synthesis       │ 合成波形<br>└─────────────────┘<br>     │<br>     ▼<br>┌─────────────────┐<br>│ AutoAMP         │ 振幅调整<br>│  - GetMaxAMP    │<br>│  - Diminish     │<br>│  - Limit        │<br>└─────────────────┘<br>     │<br>     ▼<br>输出 PCM<br></code></pre></td></tr></tbody></table></figure><hr><h2 id="使用示例" data-id="使用示例" class="notion-h"><a href="#使用示例" class="headerlink" title="使用示例"></a>使用示例</h2><figure class="highlight cpp"><table><tbody><tr><td class="code"><pre><code class="hljs cpp"><span class="hljs-meta">#<span class="hljs-keyword">include</span> <span class="hljs-string">"AudioProcess/AudioProcess.h"</span></span><br><span class="hljs-meta">#<span class="hljs-keyword">include</span> <span class="hljs-string">"AudioProcess/AutoAMP.h"</span></span><br><br><span class="hljs-comment">// 假设已有原始音频模型和变换参数</span><br>lessAudioModel originalModel = audioModel.<span class="hljs-built_in">GetAudioModel</span>();<br>ShinePara shineParams = shine.<span class="hljs-built_in">GetShine</span>();<br><br><span class="hljs-comment">// 执行音频变换</span><br><span class="hljs-function">AudioProcess <span class="hljs-title">processor</span><span class="hljs-params">(originalModel, shineParams)</span></span>;<br>lessAudioModel transformedModel = processor.<span class="hljs-built_in">GetTransAudioModel</span>();<br><br><span class="hljs-comment">// 合成波形</span><br><span class="hljs-function">Synthesis <span class="hljs-title">synth</span><span class="hljs-params">(transformedModel, shineParams.output_samples)</span></span>;<br><span class="hljs-type">double</span> *rawOutput = synth.<span class="hljs-built_in">GetWavData</span>();<br><br><span class="hljs-comment">// 振幅调整</span><br><span class="hljs-function">AutoAMP <span class="hljs-title">amp</span><span class="hljs-params">(shineParams, rawOutput)</span></span>;<br><span class="hljs-type">double</span> *finalOutput = amp.<span class="hljs-built_in">GetAMP</span>();<br><br><span class="hljs-comment">// 写入 WAV</span><br>WavIO::<span class="hljs-built_in">WriteWav</span>(shineParams.output_file_name, finalOutput,<br>    shineParams.output_samples, transformedModel.fs);<br></code></pre></td></tr></tbody></table></figure><hr><h2 id="数学公式总结" data-id="数学公式总结" class="notion-h"><a href="#数学公式总结" class="headerlink" title="数学公式总结"></a>数学公式总结</h2><table><thead><tr><th>操作</th><th>公式</th></tr></thead><tbody><tr><td>加权平均权重</td><td>$$p[j] = \frac{f0}{f0 + q^2}$$, 其中 $q = f0_{i-j-1} - f0_i$</td></tr><tr><td>F0 调制</td><td>$$f_{new} = \left((f_0 - f_{avg}) \cdot \frac{mod}{100} + f_{avg}\right) \cdot \frac{f_{target}}{f_{avg}}$$</td></tr><tr><td>时间映射（固定）</td><td>$$t_{in} = offset + t_{out} \cdot velocity$$</td></tr><tr><td>时间映射（拉伸）</td><td>$$t_{in} = offset + fixed + (t_{out} - base) \cdot stretch$$</td></tr><tr><td>Pitch Bend</td><td>$$pitch = base \cdot 2^{\frac{bend\_{cents}}{1200}}$$</td></tr><tr><td>振幅归一化</td><td>$$x_{out} = x \cdot 0.5 \cdot \frac{volume}{max\_amp}$$</td></tr></tbody></table>]]>
    </content>
    <id>https://gloomyghost.com/live/2026-04-21-lessampler-audioprocess-module.aspx</id>
    <link href="https://gloomyghost.com/live/2026-04-21-lessampler-audioprocess-module.aspx"/>
    <published>2026-04-20T16:00:00.000Z</published>
    <summary>
      <![CDATA[<p>AudioProcess 模块是 lessampler 的音频处理核心，负责对 AudioModel 分析出的参数进行变换处理。该模块实现了歌声合成中的两个关键技术：<strong>音高均衡化</strong>和<strong>时间拉伸</strong>。</p>
<p>在]]>
    </summary>
    <title>lessampler: AudioProcess 模块 - 音频变换的艺术</title>
    <updated>2026-05-29T20:33:31.218Z</updated>
  </entry>
</feed>
