Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
84 changes: 70 additions & 14 deletions SetupDataPkg/Tools/BoardMiscInfo.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@

import ctypes
import struct
import platform
from edk2toollib.os.uefivariablesupport import UefiVariable


Expand Down Expand Up @@ -52,20 +53,59 @@ def get_schema_xml_hash_from_bios():


def locate_smbios_data():
# Define constants
FIRMWARE_TABLE_ID = 0x52534D42 # 'RSMB' ascii signature for smbios table
SMBIOS_TABLE = 0x53
"""
Locate and return SMBIOS data.

# Load the kernel32.dll library
kernel32 = ctypes.windll.kernel32
This function works on both Windows and Linux platforms:
- Windows: Uses GetSystemFirmwareTable API via kernel32.dll
- Linux: Reads from /sys/firmware/dmi/tables/DMI

buffer_size = kernel32.GetSystemFirmwareTable(FIRMWARE_TABLE_ID, SMBIOS_TABLE, None, 0)
buffer = ctypes.create_string_buffer(buffer_size)
kernel32.GetSystemFirmwareTable(FIRMWARE_TABLE_ID, SMBIOS_TABLE, buffer, buffer_size)
Returns:
bytes: Raw SMBIOS data

# Convert the buffer to bytes for easier manipulation
smbios_data = buffer.raw
return smbios_data
Raises:
Exception: If SMBIOS data cannot be retrieved
"""
system_platform = platform.system()

if system_platform == "Windows":
try:
# Define constants
FIRMWARE_TABLE_ID = 0x52534D42 # 'RSMB' ascii signature for smbios table
SMBIOS_TABLE = 0x53

# Load the kernel32.dll library
kernel32 = ctypes.windll.kernel32

buffer_size = kernel32.GetSystemFirmwareTable(FIRMWARE_TABLE_ID, SMBIOS_TABLE, None, 0)
if buffer_size == 0:
raise Exception("Failed to get SMBIOS table size on Windows")

buffer = ctypes.create_string_buffer(buffer_size)
kernel32.GetSystemFirmwareTable(FIRMWARE_TABLE_ID, SMBIOS_TABLE, buffer, buffer_size)

# Convert the buffer to bytes for easier manipulation
smbios_data = buffer.raw
return smbios_data
except Exception as e:
raise Exception(f"Failed to retrieve SMBIOS data on Windows: {e}")

elif system_platform == "Linux":
try:
# On Linux, SMBIOS data is available in /sys/firmware/dmi/tables/DMI
dmi_path = "/sys/firmware/dmi/tables/DMI"
with open(dmi_path, "rb") as dmi_file:
smbios_data = dmi_file.read()
return smbios_data
except PermissionError:
raise Exception("Permission denied reading SMBIOS data. Root/sudo access may be required on Linux.")
except FileNotFoundError:
raise Exception("SMBIOS data not available at /sys/firmware/dmi/tables/DMI")
except Exception as e:
raise Exception(f"Failed to retrieve SMBIOS data on Linux: {e}")

else:
raise Exception(f"SMBIOS data retrieval not supported on platform: {system_platform}")


# Helper function to calculate the total string data of an SMBIOS entry
Expand All @@ -81,11 +121,27 @@ def calc_smbios_string_len(smbios_data, string_data_offset):


def locate_smbios_entry(smbios_type):
"""
Locate SMBIOS entries of a specific type.

Args:
smbios_type: The SMBIOS structure type to search for

Returns:
list: List of SMBIOS entries of the specified type, or None if not found or on error
"""
found_smbios_entry = []
smbios_data = locate_smbios_data()

# Offset the first 8 bytes of SMBIOS entry data
offset = 8
try:
smbios_data = locate_smbios_data()
except Exception as e:
print(f"Warning: Could not retrieve SMBIOS data: {e}")
return None

# Offset the first 8 bytes of SMBIOS entry data on Windows
# Linux DMI file doesn't have this header, so detect based on platform
system_platform = platform.system()
offset = 8 if system_platform == "Windows" else 0

# Iterate over all SMBIOS structures until we find given smbios_type
while offset < len(smbios_data):
Expand Down
71 changes: 71 additions & 0 deletions SetupDataPkg/Tools/BoardMiscInfo_test.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
## @ BoardMiscInfo_test.py
# BoardMiscInfo unit test script
#
# Copyright (c) Microsoft Corporation.
# SPDX-License-Identifier: BSD-2-Clause-Patent
#
##

##
# Import Modules
#
import unittest
import platform
import BoardMiscInfo


class BoardMiscInfoTest(unittest.TestCase):

def test_locate_smbios_data_does_not_crash(self):
"""Test that locate_smbios_data handles errors gracefully."""
# This test verifies the function doesn't crash, even if it can't read SMBIOS
# On systems without proper permissions or SMBIOS support, it should raise an exception
system_platform = platform.system()

if system_platform in ["Windows", "Linux"]:
try:
result = BoardMiscInfo.locate_smbios_data()
# If we get here, SMBIOS data was successfully retrieved
self.assertIsInstance(result, bytes)
self.assertGreater(len(result), 0)
except Exception as e:
# Expected on systems without permissions or SMBIOS support
# Just verify the error message is informative
self.assertIn("SMBIOS", str(e))

def test_locate_smbios_entry_handles_missing_data(self):
"""Test that locate_smbios_entry returns None when SMBIOS is unavailable."""
# This should not crash even if SMBIOS data is unavailable
result = BoardMiscInfo.locate_smbios_entry(0)
# Result should be either None (no access) or a list (has access)
self.assertTrue(result is None or isinstance(result, list))

def test_get_mfci_policy_returns_value(self):
"""Test that get_mfci_policy returns a string."""
result = BoardMiscInfo.get_mfci_policy()
self.assertIsInstance(result, str)
# Should return "Unknown" if the UEFI variable is not available
self.assertTrue(len(result) > 0)

def test_get_schema_xml_hash_from_bios_returns_value(self):
"""Test that get_schema_xml_hash_from_bios returns None or string."""
result = BoardMiscInfo.get_schema_xml_hash_from_bios()
# Should return None if variable is not available, or a string if it is
self.assertTrue(result is None or isinstance(result, str))

def test_calc_smbios_string_len(self):
"""Test the calc_smbios_string_len helper function."""
# Create test SMBIOS string data with double-zero terminator
test_data = bytearray(b"String1\x00String2\x00\x00Extra")
result = BoardMiscInfo.calc_smbios_string_len(test_data, 0)
# Should find the double-zero at positions 15-16
self.assertEqual(result, 17) # "String1\x00String2\x00\x00" = 17 bytes

# Test with immediate double-zero
test_data2 = bytearray(b"\x00\x00Extra")
result2 = BoardMiscInfo.calc_smbios_string_len(test_data2, 0)
self.assertEqual(result2, 2) # Just the two zeros


if __name__ == '__main__':
unittest.main()
15 changes: 10 additions & 5 deletions SetupDataPkg/Tools/ConfigEditor.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,9 @@
get_xml_full_hash
)

# SMBIOS BIOS Information structure constants
SMBIOS_BIOS_CHAR_EXT2_OFFSET = 0x13 # Offset to Characteristics Extension Byte 2


def ask_yes_no(prompt):
result = messagebox.askyesno("Question", prompt)
Expand Down Expand Up @@ -586,13 +589,15 @@ def __init__(self, master=None):
root.config(menu=menubar)

# Checking if we are in Manufacturing mode
Manufacturing_enabled = "Unknown"
bios_info_smbios_data = BoardMiscInfo.locate_smbios_entry(0)
# Check if we have the SMBIOS data in the first entry
bios_info_smbios_data = bios_info_smbios_data[0]
if (bios_info_smbios_data != []):
char_ext2_data = bios_info_smbios_data[0x13]
Manufacturing_enabled = (char_ext2_data & (0x1 << 6)) >> 6
print(f"Manufacturing : {Manufacturing_enabled:02X}")
if bios_info_smbios_data is not None and len(bios_info_smbios_data) > 0:
bios_info_smbios_data = bios_info_smbios_data[0]
if (bios_info_smbios_data != []) and len(bios_info_smbios_data) > SMBIOS_BIOS_CHAR_EXT2_OFFSET:
char_ext2_data = bios_info_smbios_data[SMBIOS_BIOS_CHAR_EXT2_OFFSET]
Manufacturing_enabled = (char_ext2_data & (0x1 << 6)) >> 6
print(f"Manufacturing : {Manufacturing_enabled:02X}")

self.bios_schema_xml_hash = BoardMiscInfo.get_schema_xml_hash_from_bios()

Expand Down
Loading