Tim Wawrzynczak has submitted this change. ( https://review.coreboot.org/c/coreboot/+/59685 )
Change subject: soc/intel/common: Add support for CSE IOM/NPHY sub-parition update ......................................................................
soc/intel/common: Add support for CSE IOM/NPHY sub-parition update
This patch adds the following support to coreboot 1. Kconfig to add IOM/NPHY in the COREBOOT/FW_MAIN_A/FW_MAIN_B partition of BIOS 2. Helper functions to support update.
Pre-requisites to enable IOM/NPHY FW Update: 1. NPHY and IOM blobs have to be added to added COREBOOT, FW_MAIN_A and FW_MAIN_B through board configuration files. CONFIG_SOC_INTEL_CSE_IOM_CBFS_FILE: IOM blob Path SOC_INTEL_CSE_NPHY_CBFS_FILE: NPHY blob path
2. Enable CONFIG_CSE_SUB_PARTITION_UPDATE to enable CSE sub-partition NPHY/IOM update.
coreboot follows below procedure to update NPHY and IOM: NPHY Update: 1. coreboot will navigate through the CSE region, identify the CSE’s NPHY FW version and BIOS NPHY version. 2. Compare both versions, if there is a difference, CSE will trigger an NPHY FW update. Otherwise, skips the NPHY FW update.
IOM Update: 1. coreboot will navigate through the CSE region, identify CSE's IOM FW version and BIOS IOM version. 2. Compares both versions, if there is a difference, coreboot will trigger an IOM FW update.Otherwise, skip IOM FW update.
Before coreboot triggers update of NPHY/IOM, BIOS sends SET BOOT PARTITION INFO(RO) to CSE and issues GLOBAL RESET commands if CSE boots from RW. coreboot updates CSE's NPHY and IOM sub-partition only if CSE boots from CSE RO Boot partition.
Once CSE boots from RO, BIOS sends HMRFPO command to CSE, then triggers update of NPHY and IOM FW in the CSE Region(RO and RW).
coreboot triggers NPHY/IOM update procedure in all ChromeOS boot modes(Normal and Recovery).
BUG=b:202143532 BRANCH=None TEST=Build and verify CSE sub-partitions IOM and NPHY are getting updated with CBFS IOM and NPHY blobs. Verified TBT, type-C display, NVMe, SD card, WWAN, Wifi working after the update.
Change-Id: I7c0cda51314c4f722f5432486a43e19b46f4b240 Signed-off-by: Krishna Prasad Bhat krishna.p.bhat.d@intel.com Reviewed-on: https://review.coreboot.org/c/coreboot/+/59685 Tested-by: build bot (Jenkins) no-reply@coreboot.org Reviewed-by: Tim Wawrzynczak twawrzynczak@chromium.org --- M src/soc/intel/common/block/cse/Kconfig M src/soc/intel/common/block/cse/Makefile.inc M src/soc/intel/common/block/cse/cse_lite.c M src/soc/intel/common/block/include/intelblocks/cse.h A src/soc/intel/common/block/include/intelblocks/cse_layout.h 5 files changed, 454 insertions(+), 4 deletions(-)
Approvals: build bot (Jenkins): Verified Tim Wawrzynczak: Looks good to me, approved
diff --git a/src/soc/intel/common/block/cse/Kconfig b/src/soc/intel/common/block/cse/Kconfig index d1f4d33..420b511 100644 --- a/src/soc/intel/common/block/cse/Kconfig +++ b/src/soc/intel/common/block/cse/Kconfig @@ -86,6 +86,38 @@ just prior to loading the payload. This is a security feature so the CSE will no longer respond to Pre-Boot commands.
+config SOC_INTEL_CSE_SUB_PART_UPDATE + bool "Enable the CSE sub-partition update Feature" + default n + depends on SOC_INTEL_CSE_LITE_SKU + help + This config will enable CSE sub-partition firmware update feature and also will be used ensure + all the required configs are provided by mainboard. + +config SOC_INTEL_CSE_IOM_CBFS_NAME + string "CBFS name for CSE sub-partition IOM binary" if SOC_INTEL_CSE_SUB_PART_UPDATE + default "cse_iom" + help + CBFS entry name for Intel CSE sub-partition IOM binary + +config SOC_INTEL_CSE_IOM_CBFS_FILE + string "Intel CBFS path and file name for CSE sub-partition IOM binary" if SOC_INTEL_CSE_SUB_PART_UPDATE + default "" + help + CBFS path and file name for Intel CSE sub-partition IOM binary + +config SOC_INTEL_CSE_NPHY_CBFS_NAME + string "CBFS name for CSE sub-partition NPHY binary" if SOC_INTEL_CSE_SUB_PART_UPDATE + default "cse_nphy" + help + CBFS entry name for Intel CSE sub-partition NPHY binary + +config SOC_INTEL_CSE_NPHY_CBFS_FILE + string "Intel CBFS path and file name for CSE sub-partition NPHY binary" if SOC_INTEL_CSE_SUB_PART_UPDATE + default "" + help + CBFS path and file name for Intel CSE sub-partition NPHY binary + if STITCH_ME_BIN
config CSE_COMPONENTS_PATH diff --git a/src/soc/intel/common/block/cse/Makefile.inc b/src/soc/intel/common/block/cse/Makefile.inc index eac7d90..a0dc780 100644 --- a/src/soc/intel/common/block/cse/Makefile.inc +++ b/src/soc/intel/common/block/cse/Makefile.inc @@ -104,5 +104,23 @@ $(CSE_RW_HASH)-file := $(obj)/cse_rw.hash $(CSE_RW_HASH)-name := $(CSE_RW_HASH) $(CSE_RW_HASH)-type := raw +endif
+ifeq ($(CONFIG_SOC_INTEL_CSE_SUB_PART_UPDATE),y) + +CSE_IOM_FILE = $(call strip_quotes,$(CONFIG_SOC_INTEL_CSE_IOM_CBFS_FILE)) +CSE_IOM = $(call strip_quotes,$(CONFIG_SOC_INTEL_CSE_IOM_CBFS_NAME)) +regions-for-file-$(CSE_IOM) = FW_MAIN_A,FW_MAIN_B,COREBOOT +cbfs-files-y += $(CSE_IOM) +$(CSE_IOM)-file := $(CSE_IOM_FILE) +$(CSE_IOM)-name := $(CSE_IOM) +$(CSE_IOM)-type := raw + +CSE_NPHY_FILE = $(call strip_quotes,$(CONFIG_SOC_INTEL_CSE_NPHY_CBFS_FILE)) +CSE_NPHY = $(call strip_quotes,$(CONFIG_SOC_INTEL_CSE_NPHY_CBFS_NAME)) +regions-for-file-$(CSE_NPHY) = FW_MAIN_A,FW_MAIN_B,COREBOOT +cbfs-files-y += $(CSE_NPHY) +$(CSE_NPHY)-file := $(CSE_NPHY_FILE) +$(CSE_NPHY)-name := $(CSE_NPHY) +$(CSE_NPHY)-type := raw endif diff --git a/src/soc/intel/common/block/cse/cse_lite.c b/src/soc/intel/common/block/cse/cse_lite.c index 051172f..6edbd84 100644 --- a/src/soc/intel/common/block/cse/cse_lite.c +++ b/src/soc/intel/common/block/cse/cse_lite.c @@ -5,9 +5,17 @@ #include <commonlib/region.h> #include <fmap.h> #include <intelblocks/cse.h> +#include <intelblocks/cse_layout.h> #include <security/vboot/vboot_common.h> #include <security/vboot/misc.h> #include <soc/intel/common/reset.h> +#include <arch/cpu.h> + +#define BPDT_HEADER_SZ sizeof(struct bpdt_header) +#define BPDT_ENTRY_SZ sizeof(struct bpdt_entry) +#define SUBPART_HEADER_SZ sizeof(struct subpart_hdr) +#define SUBPART_ENTRY_SZ sizeof(struct subpart_entry) +#define SUBPART_MANIFEST_HDR_SZ sizeof(struct subpart_entry_manifest_header)
/* Converts bp index to boot partition string */ #define GET_BP_STR(bp_index) (bp_index ? "RW" : "RO") @@ -627,8 +635,8 @@ struct region_device *target_rdev) { if (region_device_sz(target_rdev) < cse_blob_sz) { - printk(BIOS_ERR, "RW update does not fit. CSE RW flash region size: %zx, Update blob size:%zx\n", - region_device_sz(target_rdev), cse_blob_sz); + printk(BIOS_ERR, "RW update does not fit. CSE RW flash region size: %zx," + "Update blob size:%zx\n", region_device_sz(target_rdev), cse_blob_sz); return CSE_LITE_SKU_LAYOUT_MISMATCH_ERROR; }
@@ -727,11 +735,248 @@ return cse_trigger_fw_update(cse_bp_info, status, &target_rdev); }
+static const char *cse_sub_part_str(enum bpdt_entry_type type) +{ + switch (type) { + case IOM_FW: + return "IOM"; + case NPHY_FW: + return "NPHY"; + default: + return "Unknown"; + } +} + +static bool cse_sub_part_get_target_rdev(struct region_device *target_rdev, + const char *region_name, enum bpdt_entry_type type) +{ + struct bpdt_header bpdt_hdr; + struct region_device cse_rdev; + struct bpdt_entry bpdt_entries[MAX_SUBPARTS]; + uint8_t i; + + if (fmap_locate_area_as_rdev_rw(region_name, &cse_rdev) < 0) { + printk(BIOS_ERR, "cse_lite: Failed to locate %s in the FMAP\n", region_name); + return false; + } + + if ((rdev_readat(&cse_rdev, &bpdt_hdr, 0, BPDT_HEADER_SZ)) != BPDT_HEADER_SZ) { + printk(BIOS_ERR, "cse_lite: Failed to read BPDT header from CSE region\n"); + return false; + } + + if ((rdev_readat(&cse_rdev, bpdt_entries, BPDT_HEADER_SZ, + (bpdt_hdr.descriptor_count * BPDT_ENTRY_SZ))) != + (bpdt_hdr.descriptor_count * BPDT_ENTRY_SZ)) { + printk(BIOS_ERR, "cse_lite: Failed to read BPDT entries from CSE region\n"); + return false; + } + + /* walk through BPDT entries to identify sub-partition's payload offset and size */ + for (i = 0; i < bpdt_hdr.descriptor_count; i++) { + if (bpdt_entries[i].type == type) { + printk(BIOS_INFO, "cse_lite: Sub-partition %s- offset = 0x%x," + "size = 0x%x\n", cse_sub_part_str(type), bpdt_entries[i].offset, + bpdt_entries[i].size); + + if (rdev_chain(target_rdev, &cse_rdev, bpdt_entries[i].offset, + bpdt_entries[i].size)) + return false; + else + return true; + } + } + + printk(BIOS_ERR, "cse_lite: Sub-partition %s is not found\n", cse_sub_part_str(type)); + return false; +} + +static bool cse_get_sub_part_fw_version(enum bpdt_entry_type type, + const struct region_device *rdev, + struct fw_version *fw_ver) +{ + struct subpart_entry subpart_entry; + struct subpart_entry_manifest_header man_hdr; + + if ((rdev_readat(rdev, &subpart_entry, SUBPART_HEADER_SZ, SUBPART_ENTRY_SZ)) + != SUBPART_ENTRY_SZ) { + printk(BIOS_ERR, "cse_lite: Failed to read %s sub partition entry\n", + cse_sub_part_str(type)); + return false; + } + + if ((rdev_readat(rdev, &man_hdr, subpart_entry.offset_bytes, SUBPART_MANIFEST_HDR_SZ)) + != SUBPART_MANIFEST_HDR_SZ) { + printk(BIOS_ERR, "cse_lite: Failed to read %s Sub part entry #0 manifest\n", + cse_sub_part_str(type)); + return false; + } + + fw_ver->major = man_hdr.binary_version.major; + fw_ver->minor = man_hdr.binary_version.minor; + fw_ver->hotfix = man_hdr.binary_version.hotfix; + fw_ver->build = man_hdr.binary_version.build; + + return true; +} + +static void cse_sub_part_get_source_fw_version(void *subpart_cbfs_rw, struct fw_version *fw_ver) +{ + uint8_t *ptr = (uint8_t *)subpart_cbfs_rw; + struct subpart_entry *subpart_entry; + struct subpart_entry_manifest_header *man_hdr; + + subpart_entry = (struct subpart_entry *) (ptr + SUBPART_HEADER_SZ); + man_hdr = (struct subpart_entry_manifest_header *) (ptr + subpart_entry->offset_bytes); + + fw_ver->major = man_hdr->binary_version.major; + fw_ver->minor = man_hdr->binary_version.minor; + fw_ver->hotfix = man_hdr->binary_version.hotfix; + fw_ver->build = man_hdr->binary_version.build; +} + +static bool cse_prep_for_component_update(const struct cse_bp_info *cse_bp_info) +{ + /* + * To set CSE's operation mode to HMRFPO mode: + * 1. Ensure CSE to boot from RO(BP1) + * 2. Send HMRFPO_ENABLE command to CSE + */ + if (!cse_boot_to_ro(cse_bp_info)) + return false; + + return cse_hmrfpo_enable(); +} + +static uint8_t cse_sub_part_trigger_update(enum bpdt_entry_type type, uint8_t bp, + const void *subpart_cbfs_rw, const size_t blob_sz, + struct region_device *target_rdev) +{ + if (region_device_sz(target_rdev) < blob_sz) { + printk(BIOS_ERR, "cse_lite: %s Target sub-partition size: %zx, " + "smaller than blob size:%zx, abort update\n", + cse_sub_part_str(type), region_device_sz(target_rdev), blob_sz); + return CSE_LITE_SKU_SUB_PART_LAYOUT_MISMATCH_ERROR; + } + + /* Erase CSE Lite sub-partition */ + if (!cse_erase_rw_region(target_rdev)) + return CSE_LITE_SKU_SUB_PART_UPDATE_FAIL; + + /* Update CSE Lite sub-partition */ + if (!cse_copy_rw(target_rdev, (void *)subpart_cbfs_rw, 0, blob_sz)) + return CSE_LITE_SKU_SUB_PART_UPDATE_FAIL; + + printk(BIOS_INFO, "cse_lite: CSE %s %s Update successful\n", GET_BP_STR(bp), + cse_sub_part_str(type)); + + return CSE_LITE_SKU_PART_UPDATE_SUCCESS; +} + +static uint8_t handle_cse_sub_part_fw_update_rv(uint8_t rv) +{ + switch (rv) { + case CSE_LITE_SKU_PART_UPDATE_SUCCESS: + case CSE_LITE_SKU_SUB_PART_UPDATE_NOT_REQ: + return rv; + default: + cse_trigger_vboot_recovery(rv); + } + /* Control never reaches here */ + return rv; +} + +static enum csme_failure_reason cse_sub_part_fw_component_update(enum bpdt_entry_type type, + const struct cse_bp_info *cse_bp_info, const char *name) +{ + struct region_device target_rdev; + struct fw_version target_fw_ver, source_fw_ver; + enum csme_failure_reason rv; + size_t size; + static const char * const cse_regions[] = {"CSE_RO", "CSE_RW"}; + + void *subpart_cbfs_rw = cbfs_map(name, &size); + if (!subpart_cbfs_rw) { + printk(BIOS_ERR, "cse_lite: Not able to map %s CBFS file\n", + cse_sub_part_str(type)); + return CSE_LITE_SKU_SUB_PART_BLOB_ACCESS_ERR; + } + + cse_sub_part_get_source_fw_version(subpart_cbfs_rw, &source_fw_ver); + printk(BIOS_INFO, "cse_lite: CBFS %s FW Version: %x.%x.%x.%x\n", cse_sub_part_str(type), + source_fw_ver.major, source_fw_ver.minor, source_fw_ver.hotfix, + source_fw_ver.build); + + /* Trigger sub-partition update in CSE RO and CSE RW */ + for (size_t bp = 0; bp < ARRAY_SIZE(cse_regions); bp++) { + if (!cse_sub_part_get_target_rdev(&target_rdev, cse_regions[bp], type)) { + rv = CSE_LITE_SKU_SUB_PART_ACCESS_ERR; + goto error_exit; + } + + if (!cse_get_sub_part_fw_version(type, &target_rdev, &target_fw_ver)) { + rv = CSE_LITE_SKU_SUB_PART_ACCESS_ERR; + goto error_exit; + } + + printk(BIOS_INFO, "cse_lite: %s %s FW Version: %x.%x.%x.%x\n", cse_regions[bp], + cse_sub_part_str(type), target_fw_ver.major, + target_fw_ver.minor, target_fw_ver.hotfix, target_fw_ver.build); + + if (!cse_compare_sub_part_version(&target_fw_ver, &source_fw_ver)) { + printk(BIOS_INFO, "cse_lite: %s %s update is not required\n", + cse_regions[bp], cse_sub_part_str(type)); + rv = CSE_LITE_SKU_SUB_PART_UPDATE_NOT_REQ; + continue; + } + + printk(BIOS_INFO, "CSE %s %s Update initiated\n", GET_BP_STR(bp), + cse_sub_part_str(type)); + + if (!cse_prep_for_component_update(cse_bp_info)) { + rv = CSE_LITE_SKU_SUB_PART_ACCESS_ERR; + goto error_exit; + } + + rv = cse_sub_part_trigger_update(type, bp, subpart_cbfs_rw, + size, &target_rdev); + + if (rv != CSE_LITE_SKU_PART_UPDATE_SUCCESS) + goto error_exit; + } +error_exit: + cbfs_unmap(subpart_cbfs_rw); + return rv; +} + +static uint8_t cse_sub_part_fw_update(const struct cse_bp_info *cse_bp_info) +{ + if (skip_cse_sub_part_update()) { + printk(BIOS_INFO, "CSE Sub-partition update not required\n"); + return CSE_LITE_SKU_SUB_PART_UPDATE_NOT_REQ; + } + + int rv; + rv = cse_sub_part_fw_component_update(IOM_FW, cse_bp_info, + CONFIG_SOC_INTEL_CSE_IOM_CBFS_NAME); + + handle_cse_sub_part_fw_update_rv(rv); + + rv = cse_sub_part_fw_component_update(NPHY_FW, cse_bp_info, + CONFIG_SOC_INTEL_CSE_NPHY_CBFS_NAME); + + return handle_cse_sub_part_fw_update_rv(rv); +} + void cse_fw_sync(void) { static struct get_bp_info_rsp cse_bp_info;
- if (vboot_recovery_mode_enabled()) { + /* + * If system is in recovery mode, skip CSE Lite update if CSE sub-partition update + * is not enabled and continue to update CSE sub-partitions. + */ + if (vboot_recovery_mode_enabled() && !CONFIG(SOC_INTEL_CSE_SUB_PART_UPDATE)) { printk(BIOS_DEBUG, "cse_lite: Skip switching to RW in the recovery path\n"); return; } @@ -744,7 +989,31 @@
if (!cse_get_bp_info(&cse_bp_info)) { printk(BIOS_ERR, "cse_lite: Failed to get CSE boot partition info\n"); - cse_trigger_vboot_recovery(CSE_COMMUNICATION_ERROR); + + /* If system is in recovery mode, don't trigger recovery again */ + if (!vboot_recovery_mode_enabled()) { + cse_trigger_vboot_recovery(CSE_COMMUNICATION_ERROR); + } else { + printk(BIOS_ERR, "cse_lite: System is already in Recovery Mode, " + "so no action\n"); + return; + } + } + + /* + * If system is in recovery mode, CSE Lite update has to be skipped but CSE + * sub-partitions like NPHY and IOM have to to be updated. If CSE sub-parition update + * fails during recovery, just continue to boot. + */ + if (CONFIG(SOC_INTEL_CSE_SUB_PART_UPDATE) && vboot_recovery_mode_enabled()) { + if (cse_sub_part_fw_update(&cse_bp_info.bp_info) == + CSE_LITE_SKU_PART_UPDATE_SUCCESS) { + cse_board_reset(); + do_global_reset(); + die("ERROR: GLOBAL RESET Failed to reset the system\n"); + } + + return; }
if (!cse_fix_data_failure_err(&cse_bp_info.bp_info)) @@ -761,6 +1030,9 @@ cse_trigger_vboot_recovery(rv); }
+ if (CONFIG(SOC_INTEL_CSE_SUB_PART_UPDATE)) + cse_sub_part_fw_update(&cse_bp_info.bp_info); + if (!cse_is_rw_bp_status_valid(&cse_bp_info.bp_info)) cse_trigger_vboot_recovery(CSE_LITE_SKU_RW_JUMP_ERROR);
diff --git a/src/soc/intel/common/block/include/intelblocks/cse.h b/src/soc/intel/common/block/include/intelblocks/cse.h index 4184b17..68f1d3c 100644 --- a/src/soc/intel/common/block/include/intelblocks/cse.h +++ b/src/soc/intel/common/block/include/intelblocks/cse.h @@ -143,6 +143,24 @@
/* Error sending EOP to CSE */ CSE_EOP_FAIL = 12, + + /* CSE Sub-partition update fail */ + CSE_LITE_SKU_SUB_PART_UPDATE_FAIL = 13, + + /* CSE sub-partition access failure */ + CSE_LITE_SKU_SUB_PART_ACCESS_ERR = 14, + + /* CSE CBFS sub-partition access error */ + CSE_LITE_SKU_SUB_PART_BLOB_ACCESS_ERR = 15, + + /* CSE Lite sub-partition update is not required */ + CSE_LITE_SKU_SUB_PART_UPDATE_NOT_REQ = 16, + + /* CSE Lite sub-partition layout mismatch error */ + CSE_LITE_SKU_SUB_PART_LAYOUT_MISMATCH_ERROR = 17, + + /* CSE Lite sub-partition update success */ + CSE_LITE_SKU_PART_UPDATE_SUCCESS = 18, };
/* set up device for use in early boot enviroument with temp bar */ @@ -320,4 +338,9 @@ /* Function that put the CSE into desired state based on `requested_state` */ bool set_cse_device_state(unsigned int devfn, enum cse_device_state requested_state);
+/* + * Check if cse sub-parition update is required or not. + * Returns true if cse sub-parition update is required otherwise false. + */ +bool skip_cse_sub_part_update(void); #endif // SOC_INTEL_COMMON_CSE_H diff --git a/src/soc/intel/common/block/include/intelblocks/cse_layout.h b/src/soc/intel/common/block/include/intelblocks/cse_layout.h new file mode 100644 index 0000000..4c88cc5 --- /dev/null +++ b/src/soc/intel/common/block/include/intelblocks/cse_layout.h @@ -0,0 +1,105 @@ +/* BPDT version 1.7 support */ +/* SPDX-License-Identifier: GPL-2.0-only */ + +#include <sys/types.h> + +enum bpdt_entry_type { + SMIP = 0, + CSE_RBE = 1, + CSE_BUP = 2, + UCODE = 3, + IBB = 4, + S_BPDT = 5, + OBB = 6, + CSE_MAIN = 7, + ISH = 8, + CSE_IDLM = 9, + IFP_OVERRIDE = 10, + UTOK = 11, + UFS_PHY = 12, + UFS_GPP = 13, + PMC = 14, + IUNIT = 15, + NVM_CFG = 16, + UEP = 17, + OEM_KM = 20, + PAVP = 22, + IOM_FW = 23, + NPHY_FW = 24, + TBT_FW = 25, + ICC = 32, + + MAX_SUBPARTS, +}; + +struct bpdt_header { + uint32_t signature; /* BPDT_SIGNATURE */ + uint16_t descriptor_count; + uint8_t version; /* Layout 1.7 = 2 */ + uint8_t flags; + uint32_t checksum; + uint32_t ifwi_version; + struct { + uint16_t major; + uint16_t minor; + uint16_t build; + uint16_t hotfix; + } fit_tool_version; +} __packed; + +struct cse_layout { + uint8_t rom_bypass[16]; + uint16_t size; + uint16_t redundancy; + uint32_t checksum; + uint32_t data_offset; + uint32_t data_size; + uint32_t bp1_offset; + uint32_t bp1_size; + uint32_t bp2_offset; + uint32_t bp2_size; + uint32_t bp3_offset; + uint32_t bp3_size; + uint32_t bp4_offset; + uint32_t bp4_size; + uint32_t bp5_offset; + uint32_t bp5_size; + uint32_t temp_base_addr; + uint32_t temp_base_size; + uint32_t flog_offset; + uint32_t flog_size; +} __packed; + +struct bpdt_entry { + uint32_t type; + uint32_t offset; + uint32_t size; +} __packed; + +struct subpart_hdr { + uint32_t signature; /* SUBPART_SIGNATURE */ + uint32_t count; + uint8_t hdr_version; /* Header version = 2 */ + uint8_t entry_version; /* Entry version = 1 */ + uint8_t length; + uint8_t reserved; + uint8_t name[4]; + uint32_t checksum; +} __packed; + +struct subpart_entry { + uint8_t name[12]; + uint32_t offset_bytes; + uint32_t length; + uint32_t rsvd2; +} __packed; + +struct subpart_entry_manifest_header { + uint8_t reserved[36]; + struct { + uint16_t major; + uint16_t minor; + uint16_t build; + uint16_t hotfix; + } binary_version; +} __packed;