元件韌體更新 (CFU) 韌體實作指南
元件韌體更新 (CFU) 是通訊協定,以及提交要安裝在目標裝置上的新韌體映射的程式。
注意
Windows 10 2004 版 (Windows 10 2020 年 5 月更新) 和更新版本提供 CFU。
對常駐韌體提交的 CFU 是檔案組,一個檔案是供應專案元件,另一個檔案是內容元件。 每個 CFU 提交 (每個供應專案和內容組) 必須先離線建立,才能將提交傳送至實作 CFU 程式的韌體。
在 GitHub 上 CFU 存放庫中的範例 韌 體原始碼中,CFU 的一般實作與 CFU 無關的一般程式代碼包含在 中 ComponentFwUpdate.c
。 所有其他檔案都是可更新或修改開發人員唯一實作的協助程序檔案。
目錄
供應專案和內容元件
供應項目和內容構成 CFU 架構中的一對檔案。
供應專案元件只是 16 位元組長檔案,其對應至下面所述的FWUPDATE_OFFER_COMMAND結構。
要更新的實際韌體內容部分是以用戶開發人員所指定的格式。 提供的 CFU 範例程式代碼會針對韌體內容使用 SREC 檔案。
供應專案是16位元組的序列。 此供應項目結構會放入供應項目檔案中。 它基本上是二進位數據,而不是文字,因為供應專案包含特定意義的位字段。
檔案中表示的供應項目會對應至此 C 結構:
typedef struct
{
struct
{
UINT8 segmentNumber;
UINT8 reserved0 : 6;
UINT8 forceImmediateReset : 1;
UINT8 forceIgnoreVersion : 1;
UINT8 componentId;
UINT8 token;
} componentInfo;
UINT32 version;
UINT32 hwVariantMask;
struct
{
UINT8 protocolRevision : 4;
UINT8 bank : 2;
UINT8 reserved0 : 2;
UINT8 milestone : 3;
UINT8 reserved1 : 5;
UINT16 productId;
} productInfo;
} FWUPDATE_OFFER_COMMAND;
從低位址到高位址,供應專案的第一個字節是區段號碼。
<------- 4 bytes -----------> <-- 8 bytes --> <-------- 4 bytes --------->
+================================-=============================================+
| 15:0 7:3 2:0 7:6 5:4 3:0 31:0 31:0 7:0 7:0 7:7 6:6 5:0 7:0 |
| PI | R1 | MS | R0 | BK | PR | VM | VN | TK | CI | FV | FR | R0 | SN |
+================================-=============================================+
從高位址到低位址:
Byte(s) Value
---------------------------------------------------------
15:14 | (PI) Product ID is 2 bytes
13 | (R1) Reserved1 5-bit register
| (MS) Milestone 3-bit register
12 | (R2) Reserved2 2-bit register
| (BK) Bank 2-bit register
| (PR) Protocol Revision 2-bit register
11:8 | (VM) Hardware Variant Mask 32-bit register
7:4 | (VN) Version 32-bit register
3 | (TK) Token 8-bit register
2 | (CI) Component ID 8-bit register
1 | (FV) Force Ignore Version 1-bit register
| (FR) Force Immediate Reset 1-bit register
| (R0) Reserved0 6-bit register
0 | (SN) Segment Number 8-bit register
---------------------------------------------------------
供應專案註冊詳細數據
產品標識碼。 此 CFU 映像的唯一產品標識碼可以套用至此欄位。
UINT16 productID;
供應項目內容所代表的韌體里程碑。 里程碑可能是 HW 組建的不同版本,例如 EV1 組建、EV2 組建等等。 里程碑定義和價值指派會保留給開發人員。
UINT8 milestone : 3;
如果韌體適用於特定銀行 ,則 2 位字段支援四個銀行。 銀行快取器的使用會包含在供應專案的格式中,因為有目標裝置使用銀行韌體區域的實例。
如果是這種情況,而且供應專案是要更新使用中的銀行,則目標上實作 CFU 的韌體可能會拒絕供應專案。 否則,實作 CFU 之目標上的韌體可以採取其他動作,
如果韌體映像的銀行不在使用者韌體的設計中,則請忽略此字段 (設定為方便的任何值,但銀行字段中的值是選擇性的,而且取決於目標韌體實作 CFU 的方式) 。
UINT8 bank : 2;
使用的 CFU 通訊協定通訊協定版本是 4 位。
UINT8 protocolRevision : 4;
對應至此韌體映像可運作之所有唯一 HW 的位掩碼。 例如,供應專案可能表示它可以在 HW 的 verX 上執行,但不能在 HW 的 verY 上執行。 位定義和值指派會保留給開發人員。
UINT32 hwVariantMask;
提供的韌體版本。
UINT32 version;
位元組令牌,用來識別發出供應專案的使用者特定軟體。 這是為了區分可能兩者都嘗試更新相同執行韌體的驅動程式和工具。 例如,CFU 更新驅動程式可能會指派令牌0xA,而開發更新程式工具可能會指派0xB。 現在,執行中的韌體可以選擇性地根據嘗試更新它的程式來接受或忽略命令。
UINT8 token;
裝置中要套用韌體更新的元件。
UINT8 componentId;
供應專案解譯旗標:如果我們想要讓原位韌體忽略較新版本 (較舊版本) ,請將位設定為強制忽略版本。
UINT8 forceIgnoreVersion: 1;
強制立即重設會使用一個位來判斷提示。 如果判斷提示該位,主機軟體預期本機韌體會導致裝置執行重設。 重設的動作是平臺特定的。 裝置的韌體可以選擇採取動作來交換銀行,讓全新更新的韌體成為使用中的原位韌體。 或者不是。 它保留至韌體實作。 預期通常是判斷提示強制立即重設時,裝置會執行任何必要動作,讓新的銀行更新成為目標裝置上執行的作用中韌體。
UINT8 forceImmediateReset : 1;
如果供應項目與內容群組的內容部分涉及多個內容部分, 則為 。
UINT8 segmentNumber;
處理供應專案
ProcessCFWUOffer API 接受兩個自變數:
void ProcessCFWUOffer(FWUPDATE_OFFER_COMMAND* pCommand,
FWUPDATE_OFFER_RESPONSE* pResponse)
在此使用案例中,假設使用者軟體會將數據位元組傳送至執行中的韌體,然後第一則訊息是供應專案訊息。
供應專案訊息是上述的16位元組訊息, (FWUPDATE_OFFER_COMMAND 結構) 。
該供應專案訊息是執行中韌體用來處置供應項目的數據。
在供應專案的處置期間,執行中的韌體會填入 結構中的 FWUPDATE_OFFER_RESPONSE
字段來通知發件者。
解譯供應專案
執行中的韌體應該會追蹤其在 CFU 程式中的狀態。 它可能已就緒/等候接受供應專案、在 CFU 交易中間,或等候在作用中/非使用中韌體之間交換銀行。
如果執行中的韌體位於 CFU 交易的中間, 請勿接受/處理此供應專案,並據以通知主機。
if (s_currentOffer.updateInProgress)
{
memset(pResponse, 0, sizeof (FWUPDATE_OFFER_RESPONSE));
pResponse->status = FIRMWARE_UPDATE_OFFER_BUSY;
pResponse->rejectReasonCode = FIRMWARE_UPDATE_OFFER_BUSY;
pResponse->token = token;
return;
}
供應專案的元件標識符字段可用來向執行中的韌體發出來自執行中韌體要求的特殊動作。 在 CFU 程式代碼範例中,主機會使用特殊供應專案命令來擷取 CFU 引擎的狀態 - 執行中的軟體是否能夠且準備好接受 CFU 供應專案。
else if (componentId == CFU_SPECIAL_OFFER_CMD)
{
FWUPDATE_SPECIAL_OFFER_COMMAND* pSpecialCommand =
(FWUPDATE_SPECIAL_OFFER_COMMAND*)pCommand;
if (pSpecialCommand->componentInfo.commandCode == CFU_SPECIAL_OFFER_GET_STATUS)
{
memset(pResponse, 0, sizeof (FWUPDATE_OFFER_RESPONSE));
pResponse->status = FIRMWARE_UPDATE_OFFER_COMMAND_READY;
pResponse->token = token;
return;
}
}
最後,如果銀行交換擱置,就會進行檢查。 銀行交換是指韌體保存資訊的韌體,指出它是否仍在從執行中作用中應用程式切換至新下載映像。
執行銀行切換的方式和位置是內嵌韌體實作特定工作。 CFU 通訊協定和程式可讓您在執行 CFU 的遠端使用者應用程式與執行中的位置韌體之間交換資訊。
else if (s_bankSwapPending)
{
memset(pResponse, 0, sizeof (FWUPDATE_OFFER_RESPONSE));
pResponse->status = FIRMWARE_UPDATE_OFFER_REJECT;
pResponse->rejectReasonCode = FIRMWARE_UPDATE_OFFER_SWAP_PENDING;
pResponse->token = token;
return;
}
最後,如果執行中的韌體狀態未忙碌,且 componentId 不是特殊命令,而且沒有擱置的銀行交換 - 然後我們可以處理此供應專案。
處理供應專案牽涉到但不限於下列四個步驟:
步驟 1 - 檢查銀行
在供應專案中,檢查執行中應用程式的銀行。 它們是否相同或不同?
如果相同,則拒絕供應專案 (我們不想覆寫執行/使用中映射) 。
否則,請繼續。
步驟 2 - 檢查 hwVariantMask
執行中的韌體會根據供應專案的 HW 檢查 hwVariantMask
其執行位置。 這可讓內嵌韌體在供應專案對目標無效時拒絕供應專案。 (例如,如果執行中的韌體位於舊的 HW 組建上,而新的提供韌體適用於較新的 HW 組建,則執行中的韌體應該拒絕此供應專案)
如果無效,則拒絕供應專案。
否則,請繼續。
步驟 3 - 檢查韌體版本
檢查提供的韌體內容版本是否比目前的應用程式韌體還舊或更新版本。
將保留給用戶實作,以決定要如何檢查哪些韌體大於另一個韌體,以及是否允許供應專案中的 『forceIgnoreVersion』 字段使用。 一般韌體開發允許在軟體開發期間使用 『forceIgnoreVersion』 欄位,並在韌體偵錯版本中使用,但不允許 (不允許在產品/版本韌體中更新較舊的韌體) 。
如果此檢查失敗,則拒絕供應專案。
否則,請繼續。
步驟 4 - 接受供應專案
供應專案很好。 接受供應專案,其回應是針對韌體將訊息和狀態傳回給遠端使用者應用程式的方式量身訂做。 所謂的「回應」是數據 (封裝的數據結構,如示範頭檔) 所示,而且此數據會以適當的裝置方式寫出給用戶應用程式。
處理內容
內容的處理通常是多步驟程式。 多個步驟是指韌體在元件中接受韌體映像的功能,也稱為「區塊」數據。 一次將整個映射傳送至內嵌韌體並不可行,因此,預期 CFU 通訊協議的實作和程式在小片段中接受內容並不可行。
此討論會在描述 CFU 內容的程式時使用假設。
內容處理的狀態機器包含三種狀態。
處理第一個區塊的狀態。
處理最後一個區塊的狀態。
處理第一個和最後一個之間任何區塊的狀態。
內容命令的結構
如同供應專案,內容具有結構,其中包含示範中 CFU 演算法所使用的欄位。
typedef struct
{
UINT8 flags;
UINT8 length;
UINT16 sequenceNumber;
UINT32 address;
UINT8 pData[MAX_UINT8];
} FWUPDATE_CONTENT_COMMAND;
內容命令的結構比供應項目結構更簡單。 內容定義為要寫入記憶體中的位元組序列。 內容的前置詞是這個結構的欄位:
UINT8 flags
表示內容 「block」 是否為第一個、最後一個或其他內容。UINT8 length
標記欄位的pData
長度。 在 CFU 的示範程式代碼中,的大小pData
限制為 255 個字節。 其他實作可能會改變「區塊」的大小上限。UINT16 sequenceNumber
將區塊提交為內容的索引計數器標示。UINT32 address
區塊的位址位移。 在此版本的 CFU 示範中,實作已預先定義每個應用程式區域實體地址的相關信息。 例如,兩個銀行韌體實作的開頭可能是App1位址,而App2則從地址0x9000
0xA0000
開始。 因此,根據韌體映像的備妥方式, (S-Records) SREC 中的位址可能是實體位址或位移。 在任何情況下,在準備內容和 CFU 內容處理的實作特定例程之間必須有共用的瞭解,以判斷在記憶體中寫入區塊的位置真正的實體位址。 韌體開發人員必須採用最佳做法,並檢查每個內容部落格的有效位址範圍。 例如,CFU 程式代碼會示範如果 App1 (適用於0x9000
) 的位址重疊至 App2 等等,則 CFU 程式代碼會示範所做的檢查。UINT8 pData[MAX_UINT8]
- 這是韌體映像區塊的原始位元組。 使用者應用程式只會將位元組放入length
內容區塊的完整位元組數據流中。
根據所提供的程序代碼中的 CFU 示範,內容結構中沒有使用位欄位字段。
第一個區塊
第一個區塊會開始下載韌體內容。 執行中的韌體會嘗試將區塊寫入非變動性記憶體。 當然,內容 「block」 包含應該寫入記憶體中區塊的位置、要寫入的數據量和其他欄位的相關信息。
每個 componentID 目標裝置都不同,而且有多個方法可將數據保存到記憶體中。 例如,一個 componentId 可能需要寫入內部快閃,另一個 componentId 可能會寫入外部 SPI 快閃,或另一個元件可能會利用另一個 IC 的 I2C 通訊協定來更新其映像。 本檔隨附的示範會強調使用稱為 ICompFwUpdateBspWrite
的函式,每個唯一韌體都必須實作,並瞭解其所設計目標的基礎非揮發性記憶體 I/O 函式。
除了第一個或最後一個以外的任何其他區塊
當使用者應用程式傳遞另一個區塊時,接受新區塊的程式會繼續,同樣地,在訊息中包含應寫入區塊的位址、包含多少位元組和其他欄位的元數據。
就地韌體會將這視為第一個區塊案例。
不過,應該注意,系統隨時無法擷取並保存區塊到記憶體中,其會由內部韌體回應失敗碼。
最後一個區塊
只有在內部韌體需要執行工作來驗證剛寫入記憶體的映射時,最後一個區塊才會提出挑戰。
首先,最後一個區塊會寫入記憶體。
然後,相較於最後一個區塊中的CRC欄位,至少應該對已經寫入記憶體的數據進行CRC檢查, (從第一個區塊到最後一個區塊) 。 每個實作韌體都會保留下來,以瞭解如何取得所下載映像的CRC。
請記住,執行CRC檢查需要一段時間。 不同於針對供應項目和封鎖提交執行 CFU 的一般流程。 如果最後一個區塊提交包含CRC檢查,則只會涉及一些延遲,因為CRC檢查可能會檢查大量的記憶體區域。 視目標裝置和其他因素而定,這可能不是問題。
重要
傳入影像的CRC檢查是選擇性的,而且可能會加上批注。不過,最佳做法應該就地至少採用這項檢查。 強烈建議目前在 CFU 程式中採取其他動作,以確保已下載映像的完整性。 其中一些動作可能包括驗證映像的「已簽署」部分,以及/或檢查信任的憑證鏈結或其他確保韌體映射的安全最佳做法方法。 這些會保留給韌體開發人員。
在最後一個區塊之後清除
既然最後一個區塊已寫入,而且CRC檢查已完成,如果驗證有任何部分失敗,韌體可能會回應失敗。
否則,預期韌體中的 CFU 程式會以成功的狀態回應。
已檢查強制重設
供應專案中的強制重設旗標是用來判斷目標 MCU 是否應該經過重設 (使用者定義重設) 。
一般而言,強制重設時,意圖是讓MCU執行重設,以便讓App Bank切換。 更新永續性變數,以表示在重設時要開機的韌體映射會保留給韌體開發人員。