diff --git a/SystemReady-devicetree-band/README.md b/SystemReady-devicetree-band/README.md
index cf1ffb73..f2c072ee 100644
--- a/SystemReady-devicetree-band/README.md
+++ b/SystemReady-devicetree-band/README.md
@@ -129,7 +129,8 @@ This image comprises of 2 FAT file system partition recognized by UEFI:
│ │ ├── CapsuleApp.efi
│ │ ├── https_boot.nsh
│ │ ├── ledge.efi
-│ │ └── UpdateVars.efi
+│ │ ├── UpdateVars.efi
+│ │ └── UefiDump.efi
│ ├── bbr
│ │ ├── SCT
│ │ ├── ScrtStartup.nsh
@@ -174,7 +175,7 @@ This image comprises of 2 FAT file system partition recognized by UEFI:
- grub.cfg - grub config file
- startup.nsh - uefi automation run startup file
- acs_tests contains executable files and configs related for test suites
- - app directory contains CapsuleApp.efi
+ - app directory contains CapsuleApp.efi and UefiDump.efi
- app/capsule_update.nsh is uefi script for capsule update
- bbr directory contains SCT related binaries and sequence files
- bbsr-keys contains cryptographic keys for secure boot and testing secure firmware updates
diff --git a/SystemReady-devicetree-band/Yocto/meta-woden/recipes-acs/uefi-dump/uefi-dump.bb b/SystemReady-devicetree-band/Yocto/meta-woden/recipes-acs/uefi-dump/uefi-dump.bb
new file mode 100644
index 00000000..3f332ca5
--- /dev/null
+++ b/SystemReady-devicetree-band/Yocto/meta-woden/recipes-acs/uefi-dump/uefi-dump.bb
@@ -0,0 +1,28 @@
+SUMMARY = "UEFI Dump Application"
+DESCRIPTION = "Fetch bbr-acs from GitHub and build UefiDump.efi from the fetched source"
+
+require recipes-acs/edk2-firmware/edk2-firmware-rev.bb
+
+PROVIDES:remove = "virtual/uefi-firmware"
+PROVIDES:remove = "virtual/bootloader"
+
+LICENSE = "CLOSED"
+COMPATIBLE_MACHINE:genericarm64 = "genericarm64"
+COMPATIBLE_HOST = "aarch64.*-linux"
+
+EDK2_ARCH = "AARCH64"
+EDK2_PLATFORM = "MdeModule"
+EDK2_PLATFORM_DSC = "${WORKDIR}/bbr-acs/ebbr/uefi_app/UefiDump.dsc"
+
+SRC_URI += "git://github.com/ARM-software/bbr-acs;destsuffix=bbr-acs;protocol=https;branch=main;name=bbr-acs"
+SRCREV_bbr-acs = "${AUTOREV}"
+
+BBR_ACS_DIR = "${WORKDIR}/bbr-acs"
+PACKAGES_PATH .= ":${BBR_ACS_DIR}"
+
+EDK2_EXTRA_BUILD = ""
+
+do_install() {
+ install -d ${D}/firmware
+ install ${B}/Build/${EDK2_PLATFORM}/${EDK2_BUILD_MODE}_${EDK_COMPILER}/${EDK2_ARCH}/UefiDump.efi ${D}/firmware/
+}
diff --git a/SystemReady-devicetree-band/Yocto/meta-woden/recipes-images/images/woden-image.bb b/SystemReady-devicetree-band/Yocto/meta-woden/recipes-images/images/woden-image.bb
index 9f51ada7..f0fae05b 100644
--- a/SystemReady-devicetree-band/Yocto/meta-woden/recipes-images/images/woden-image.bb
+++ b/SystemReady-devicetree-band/Yocto/meta-woden/recipes-images/images/woden-image.bb
@@ -24,6 +24,7 @@ EXTRA_IMAGEDEPENDS += "bsa-acs \
uefi-apps \
ledge-efi \
update-vars \
+ uefi-dump \
ebbr-sct \
bootfs-files \
pfdi-acs \
@@ -46,6 +47,7 @@ IMAGE_EFI_BOOT_FILES += "Bsa.efi;acs_tests/bsa/Bsa.efi \
bbsr_SctStartup.nsh;acs_tests/bbr/bbsr_SctStartup.nsh \
CapsuleApp.efi;acs_tests/app/CapsuleApp.efi \
UpdateVars.efi;acs_tests/app/UpdateVars.efi \
+ UefiDump.efi;acs_tests/app/UefiDump.efi \
Shell.efi;EFI/BOOT/Shell.efi \
"
SYSTEMREADY_COMMIT_LOG ?= "${TOPDIR}/../recipes-acs/bootfs-files/files/systemready-commit.log"
@@ -78,6 +80,10 @@ do_sign_images() {
echo "WARNING: ledge.efi not found for signing (skipping)"
fi
+ if [ -f DO_SIGN/acs_tests/app/UefiDump.efi ]; then
+ sbsign --key $TEST_DB1_KEY --cert $TEST_DB1_CRT DO_SIGN/acs_tests/app/UefiDump.efi --output DO_SIGN/acs_tests/app/UefiDump.efi
+ fi
+
echo "Signing images complete."
wic cp DO_SIGN/EFI ${DEPLOY_DIR_IMAGE}/${IMAGE_LINK_NAME}.wic:1/
wic cp DO_SIGN/Image ${DEPLOY_DIR_IMAGE}/${IMAGE_LINK_NAME}.wic:1/
diff --git a/common/linux_scripts/capsule_ondisk_reporting_vars_check.py b/common/linux_scripts/capsule_ondisk_reporting_vars_check.py
index 5fe5069e..1f7204f4 100755
--- a/common/linux_scripts/capsule_ondisk_reporting_vars_check.py
+++ b/common/linux_scripts/capsule_ondisk_reporting_vars_check.py
@@ -77,6 +77,9 @@
# Regular expression to match valid capsule variable names (CapsuleNNNN where NNNN is 4 hex digits)
CAPSULE_NAME_RE = re.compile(r"^Capsule[0-9A-Fa-f]{4}$")
+# Path where UEFI app writes the EBBR profile table hexdump (on host this is mounted under /mnt)
+EBBR_PROFILE_TABLE_DUMP = "/mnt/acs_results_template/acs_results/uefi_dump/ebbr_profile_table.log"
+
def _ensure_log_dir():
"""Ensure the log directory exists, creating it if necessary."""
try:
@@ -101,17 +104,31 @@ def log(msg=""):
except (OSError, IOError):
pass
-def status(ok: bool):
+def debug(msg=""):
+ """
+ Emit a debug message to both stdout and the log file.
+
+ Args:
+ msg (str): Debug message to emit.
+ """
+ print(msg)
+ log(msg)
+
+def result_status(ok: bool, test_level: str = "RECOMMENDED"):
"""
Convert a boolean test result to a status string.
Args:
ok (bool): True if test passed, False if failed
+ test_level (str): "MANDATORY" or "RECOMMENDED"
Returns:
- str: "PASSED" if ok is True, "WARNING" otherwise
+ str: "PASSED" if ok is True, "FAILED" for mandatory failures,
+ and "WARNING" for recommended failures.
"""
- return "PASSED" if ok else "WARNING"
+ if ok:
+ return "PASSED"
+ return "FAILED" if test_level == "MANDATORY" else "WARNING"
def read_efi_var(var_name, guid=CapsuleReportGuid):
"""
@@ -178,7 +195,89 @@ def os_indications_supports_ondisk():
indications = struct.unpack(", Status - WARNING")
+ f"actual attributes Value:, Status - {result_status(False, test_level)}")
return False
ok = (actual == expected)
log(f"RESULTS: {var_name} Variable Attribute Test: expected attributed value:0x{expected:X}, "
- f"actual attributes Value:0x{actual:X}, Status - {status(ok)}")
+ f"actual attributes Value:0x{actual:X}, Status - {result_status(ok, test_level)}")
return ok
-def check_capsulemax():
+def check_capsulemax(test_level: str):
"""
Validate the CapsuleMax EFI variable.
@@ -226,8 +325,8 @@ def check_capsulemax():
attrs, value = read_efi_var("CapsuleMax")
if attrs is None:
log("INFO: CapsuleMax Variable Test: CapsuleMax Variable - Not Found or Not Accessible")
- log("RESULTS: CapsuleMax Variable Test: WARNING")
- log_attr_test("CapsuleMax", EXPECTED_ATTR_CAPSULE_MAX, None)
+ log(f"RESULTS: CapsuleMax Variable Test: {result_status(False, test_level)}")
+ log_attr_test("CapsuleMax", EXPECTED_ATTR_CAPSULE_MAX, None, test_level)
return False
val = decode_char16_11_no_nul(value)
@@ -236,12 +335,12 @@ def check_capsulemax():
ok_len = (len(value) >= 22)
ok_fmt = bool(CAPSULE_NAME_RE.fullmatch(val))
ok_val = ok_len and ok_fmt
- log(f"RESULTS: CapsuleMax Variable Test: {status(ok_val)}")
+ log(f"RESULTS: CapsuleMax Variable Test: {result_status(ok_val, test_level)}")
- ok_attr = log_attr_test("CapsuleMax", EXPECTED_ATTR_CAPSULE_MAX, attrs)
+ ok_attr = log_attr_test("CapsuleMax", EXPECTED_ATTR_CAPSULE_MAX, attrs, test_level)
return ok_val and ok_attr
-def check_capsulelast():
+def check_capsulelast(test_level: str):
"""
Validate the CapsuleLast EFI variable.
@@ -261,8 +360,8 @@ def check_capsulelast():
attrs, value = read_efi_var("CapsuleLast")
if attrs is None:
log("INFO: CapsuleLast Variable Test: CapsuleLast Variable - Not Found or Not Accessible")
- log("RESULTS: CapsuleLast Variable Test: WARNING")
- log_attr_test("CapsuleLast", EXPECTED_ATTR_CAPSULE_LAST, None)
+ log(f"RESULTS: CapsuleLast Variable Test: {result_status(False, test_level)}")
+ log_attr_test("CapsuleLast", EXPECTED_ATTR_CAPSULE_LAST, None, test_level)
return False
val = decode_char16_11_no_nul(value)
@@ -271,12 +370,12 @@ def check_capsulelast():
ok_len = (len(value) >= 22)
ok_fmt = bool(CAPSULE_NAME_RE.fullmatch(val))
ok_val = ok_len and ok_fmt
- log(f"RESULTS: CapsuleLast Variable Test: {status(ok_val)}")
+ log(f"RESULTS: CapsuleLast Variable Test: {result_status(ok_val, test_level)}")
- ok_attr = log_attr_test("CapsuleLast", EXPECTED_ATTR_CAPSULE_LAST, attrs)
+ ok_attr = log_attr_test("CapsuleLast", EXPECTED_ATTR_CAPSULE_LAST, attrs, test_level)
return ok_val and ok_attr
-def check_capsule_nnnn():
+def check_capsule_nnnn(test_level: str):
"""
Validate all CapsuleNNNN EFI variables in the Capsule Report GUID namespace.
@@ -292,19 +391,19 @@ def check_capsule_nnnn():
The check iterates through all variables in the Capsule Report GUID namespace,
filters for those matching the CapsuleNNNN pattern, and validates each one.
- If no CapsuleNNNN variables are found at all, the check passes (firmware may
- not have processed any capsules yet).
+ If no CapsuleNNNN variables are found at all, the check fails so the overall
+ result reflects the missing reporting variables.
Returns:
- bool: True if all found CapsuleNNNN variables are valid, or if none are
- found. False if any CapsuleNNNN variables fail validation.
+ bool: True if all found CapsuleNNNN variables are valid.
+ False if none are found or if any CapsuleNNNN variables fail validation.
"""
suffix = "-" + CapsuleReportGuid
try:
entries = os.listdir(EFIVAR_PATH)
except OSError:
- log("RESULTS: CapsuleNNNN Variable Test: efivarfs not accessible - WARNING")
+ log(f"RESULTS: CapsuleNNNN Variable Test: efivarfs not accessible - {result_status(False, test_level)}")
return False
any_failed = False
@@ -326,20 +425,20 @@ def check_capsule_nnnn():
attrs, _value = read_efi_var(var)
if attrs is None:
log(f"INFO: CapsuleNNNN Variable Test: {var} - Not Accessible")
- log(f"RESULTS: CapsuleNNNN Variable Test: {var} Variable Test: WARNING")
- if not log_attr_test(var, EXPECTED_ATTR_CAPSULE_NNNN, None):
+ log(f"RESULTS: CapsuleNNNN Variable Test: {var} Variable Test: {result_status(False, test_level)}")
+ if not log_attr_test(var, EXPECTED_ATTR_CAPSULE_NNNN, None, test_level):
any_failed = True
continue
log(f"INFO: CapsuleNNNN Variable Test: {var} - Found")
log(f"RESULTS: CapsuleNNNN Variable Test: {var} Variable Test: PASSED")
- if not log_attr_test(var, EXPECTED_ATTR_CAPSULE_NNNN, attrs):
+ if not log_attr_test(var, EXPECTED_ATTR_CAPSULE_NNNN, attrs, test_level):
any_failed = True
if not found_any:
- log("RESULTS: CapsuleNNNN Variable Test: No CapsuleNNNN reporting variables found - WARNING")
- return True
+ log(f"RESULTS: CapsuleNNNN Variable Test: No CapsuleNNNN reporting variables found - {result_status(False, test_level)}")
+ return False
return not any_failed
@@ -384,25 +483,80 @@ def main():
log("RESULTS: Overall Capsule On-Disk Update Reporting Variables Result: SKIPPED")
return 2
- log("INFO: OsIndicationsSupported indicates capsule on-disk support; running recommended checks")
+ # Determine whether the test should be treated as MANDATORY or RECOMMENDED.
+ # Decision rule:
+ # - MANDATORY when the parsed table contains EBBR >= 2.2
+ # - MANDATORY when a valid parsed table contains zero profiles
+ # - RECOMMENDED when the table is missing, invalid, or contains only non-EBBR profiles
+ parse_status, version, number_of_profiles, guids = parse_ebbr_profile_table()
+ log(f"INFO: EBBR profile table path: {EBBR_PROFILE_TABLE_DUMP}")
+ if parse_status == "missing":
+ log("INFO: EBBR profile table dump file not found")
+ elif parse_status == "parse_failed":
+ log("INFO: EBBR profile table dump could not be parsed")
+ else:
+ log(f"INFO: EBBR profile table Version: {version}")
+ log(f"INFO: EBBR profile table NumberOfProfiles: {number_of_profiles}")
+ # Known EBBR GUIDs encoded as raw 16-byte EFI_GUID in-memory layout (little-endian for Data1/2/3)
+ EBBR_2_1 = bytes.fromhex('353ce3ccac748740bce78b29b02eeb27')
+ EBBR_2_2 = bytes.fromhex('d4ee73900de5ee11b8b08b68da62fc80')
+ EBBR_2_3 = bytes.fromhex('77fc217724a7ef118eaaf7c9b194ba75')
+ EBBR_VERSION_BY_GUID = {
+ EBBR_2_1: (2, 1),
+ EBBR_2_2: (2, 2),
+ EBBR_2_3: (2, 3),
+ }
+
+ log(f"INFO: Extracted {len(guids)} GUID(s) from EBBR profile table")
+ for index, guid in enumerate(guids):
+ log(f"INFO: EBBR profile table GUID[{index}]: {guid.hex()}")
+ version_tuple = EBBR_VERSION_BY_GUID.get(guid)
+ if version_tuple is not None:
+ log(f"INFO: EFI Conformance Profile Table indicates platform compliance with EBBR {version_tuple[0]}.{version_tuple[1]}")
+
+ found_versions = []
+ for guid in guids:
+ version_tuple = EBBR_VERSION_BY_GUID.get(guid)
+ if version_tuple is not None:
+ found_versions.append(version_tuple)
+
+ has_ebbr = len(found_versions) > 0
+ has_ge_2_2 = any(v >= (2, 2) for v in found_versions)
+ valid_zero_profiles = (parse_status == "ok" and number_of_profiles == 0)
+ has_only_non_ebbr_profiles = (parse_status == "ok" and number_of_profiles and not has_ebbr)
+
+ if on_disk_supported and (has_ge_2_2 or valid_zero_profiles):
+ test_level = "MANDATORY"
+ else:
+ test_level = "RECOMMENDED"
+
+ log(f"INFO: OsIndicationsSupported indicates capsule on-disk support; running {test_level} checks")
+ if parse_status == "missing":
+ log("INFO: Test level is RECOMMENDED because the EBBR profile table dump is missing")
+ elif parse_status == "parse_failed":
+ log("INFO: Test level is RECOMMENDED because the EBBR profile table dump is invalid")
+ elif has_only_non_ebbr_profiles:
+ log("INFO: Test level is RECOMMENDED because the EBBR profile table contains only non-EBBR profiles")
failed = False
- if not check_capsulemax():
+ if not check_capsulemax(test_level):
failed = True
log()
- if not check_capsulelast():
+ if not check_capsulelast(test_level):
failed = True
log()
- if not check_capsule_nnnn():
+ if not check_capsule_nnnn(test_level):
failed = True
log()
exit_code = 3 if failed else 0
if exit_code == 0:
log("RESULTS: Overall Capsule On-Disk Update Reporting Variables Result: PASSED")
+ elif test_level == "MANDATORY":
+ log("RESULTS: Overall Capsule On-Disk Update Reporting Variables Result: FAILED")
else:
log("RESULTS: Overall Capsule On-Disk Update Reporting Variables Result: WARNING")
return exit_code
diff --git a/common/uefi_scripts/debug_dump.nsh b/common/uefi_scripts/debug_dump.nsh
index 432e4abd..d87e3fd4 100644
--- a/common/uefi_scripts/debug_dump.nsh
+++ b/common/uefi_scripts/debug_dump.nsh
@@ -48,6 +48,13 @@ else
bcfg boot dump -v > bcfg.log
devtree > devtree.log
ver > uefi_version.log
+ if exist FS%m:\yocto_image.flag then
+ if exist FS%m:\acs_tests\app\UefiDump.efi then
+ FS%m:\acs_tests\app\UefiDump.efi
+ else
+ echo "UefiDump.efi not present"
+ endif
+ endif
dmem > dmem.log
sermode > sermode.log
mode > mode.log