scripts: Add a tool to convert CHIDs to devicetree
Add a Python script to convert Hardware ID files (as produced by 'fwupd hwids') into a devicetree format suitable for use within U-Boot. Provide a simple test as well. Co-developed-by: Claude <noreply@anthropic.com> Signed-off-by: Simon Glass <sjg@chromium.org>
This commit is contained in:
795
scripts/hwids_to_dtsi.py
Executable file
795
scripts/hwids_to_dtsi.py
Executable file
@@ -0,0 +1,795 @@
|
||||
#!/usr/bin/env python3
|
||||
# SPDX-License-Identifier: GPL-2.0+
|
||||
"""
|
||||
Convert HWIDS txt files to devicetree source (.dtsi) format
|
||||
|
||||
This script converts hardware ID files from board/efi/hwids/ into devicetree
|
||||
source files. The output includes SMBIOS computer information as properties
|
||||
and Hardware IDs as binary GUID arrays.
|
||||
"""
|
||||
|
||||
import argparse
|
||||
import glob
|
||||
from io import StringIO
|
||||
import os
|
||||
import re
|
||||
import sys
|
||||
import traceback
|
||||
import uuid
|
||||
from enum import IntEnum
|
||||
|
||||
|
||||
DTSI_HEADER = """// SPDX-License-Identifier: GPL-2.0+
|
||||
|
||||
// Computer Hardware IDs for multiple boards
|
||||
// Generated from source_path
|
||||
|
||||
/ {
|
||||
\tchid: chid {};
|
||||
};
|
||||
|
||||
&chid {
|
||||
"""
|
||||
|
||||
DTSI_FOOTER = """};
|
||||
"""
|
||||
|
||||
# Constants for magic numbers
|
||||
GUID_LENGTH = 36 # Length of GUID string without braces (8-4-4-4-12 format)
|
||||
HWIDS_SECTION_HEADER_LINES = 2 # Lines for 'Hardware IDs' header and dashes
|
||||
CHID_BINARY_LENGTH = 16 # Length of CHID binary data in bytes
|
||||
HEX_BASE = 16 # Base for hexadecimal conversions
|
||||
|
||||
# Field types for special handling
|
||||
VERSION_FIELDS = {
|
||||
'BiosMajorRelease', 'BiosMinorRelease',
|
||||
'FirmwareMajorRelease', 'FirmwareMinorRelease'
|
||||
}
|
||||
HEX_ENCLOSURE_FIELD = 'EnclosureKind'
|
||||
|
||||
class CHIDField(IntEnum):
|
||||
"""CHID field types matching the C enum chid_field_t."""
|
||||
MANUF = 0
|
||||
FAMILY = 1
|
||||
PRODUCT_NAME = 2
|
||||
PRODUCT_SKU = 3
|
||||
BOARD_MANUF = 4
|
||||
BOARD_PRODUCT = 5
|
||||
BIOS_VENDOR = 6
|
||||
BIOS_VERSION = 7
|
||||
BIOS_MAJOR = 8
|
||||
BIOS_MINOR = 9
|
||||
ENCLOSURE_TYPE = 10
|
||||
|
||||
|
||||
class CHIDVariant(IntEnum):
|
||||
"""CHID variant IDs matching the Microsoft specification."""
|
||||
CHID_00 = 0 # Most specific
|
||||
CHID_01 = 1
|
||||
CHID_02 = 2
|
||||
CHID_03 = 3
|
||||
CHID_04 = 4
|
||||
CHID_05 = 5
|
||||
CHID_06 = 6
|
||||
CHID_07 = 7
|
||||
CHID_08 = 8
|
||||
CHID_09 = 9
|
||||
CHID_10 = 10
|
||||
CHID_11 = 11
|
||||
CHID_12 = 12
|
||||
CHID_13 = 13
|
||||
CHID_14 = 14 # Least specific
|
||||
|
||||
|
||||
# Field mapping from HWIDS file field names to CHIDField bits and devicetree
|
||||
# properties
|
||||
# Format: 'HWIDSFieldName': (CHIDField.BIT or None, 'devicetree-property-name')
|
||||
# Note: Firmware fields don't map to CHIDField bits
|
||||
FIELD_MAP = {
|
||||
'Manufacturer': (CHIDField.MANUF, 'manufacturer'),
|
||||
'Family': (CHIDField.FAMILY, 'family'),
|
||||
'ProductName': (CHIDField.PRODUCT_NAME, 'product-name'),
|
||||
'ProductSku': (CHIDField.PRODUCT_SKU, 'product-sku'),
|
||||
'BaseboardManufacturer': (CHIDField.BOARD_MANUF, 'baseboard-manufacturer'),
|
||||
'BaseboardProduct': (CHIDField.BOARD_PRODUCT, 'baseboard-product'),
|
||||
'BiosVendor': (CHIDField.BIOS_VENDOR, 'bios-vendor'),
|
||||
'BiosVersion': (CHIDField.BIOS_VERSION, 'bios-version'),
|
||||
'BiosMajorRelease': (CHIDField.BIOS_MAJOR, 'bios-major-release'),
|
||||
'BiosMinorRelease': (CHIDField.BIOS_MINOR, 'bios-minor-release'),
|
||||
'EnclosureKind': (CHIDField.ENCLOSURE_TYPE, 'enclosure-kind'),
|
||||
'FirmwareMajorRelease': (None, 'firmware-major-release'),
|
||||
'FirmwareMinorRelease': (None, 'firmware-minor-release'),
|
||||
}
|
||||
|
||||
|
||||
# CHID variants table matching the C code variants array
|
||||
CHID_VARIANTS = {
|
||||
CHIDVariant.CHID_00: {
|
||||
'name': 'HardwareID-00',
|
||||
'fields': (1 << CHIDField.MANUF) | (1 << CHIDField.FAMILY) |
|
||||
(1 << CHIDField.PRODUCT_NAME) | (1 << CHIDField.PRODUCT_SKU) |
|
||||
(1 << CHIDField.BIOS_VENDOR) | (1 << CHIDField.BIOS_VERSION) |
|
||||
(1 << CHIDField.BIOS_MAJOR) | (1 << CHIDField.BIOS_MINOR)
|
||||
},
|
||||
CHIDVariant.CHID_01: {
|
||||
'name': 'HardwareID-01',
|
||||
'fields': (1 << CHIDField.MANUF) | (1 << CHIDField.FAMILY) |
|
||||
(1 << CHIDField.PRODUCT_NAME) | (1 << CHIDField.BIOS_VENDOR) |
|
||||
(1 << CHIDField.BIOS_VERSION) | (1 << CHIDField.BIOS_MAJOR) |
|
||||
(1 << CHIDField.BIOS_MINOR)
|
||||
},
|
||||
CHIDVariant.CHID_02: {
|
||||
'name': 'HardwareID-02',
|
||||
'fields': (1 << CHIDField.MANUF) | (1 << CHIDField.PRODUCT_NAME) |
|
||||
(1 << CHIDField.BIOS_VENDOR) | (1 << CHIDField.BIOS_VERSION) |
|
||||
(1 << CHIDField.BIOS_MAJOR) | (1 << CHIDField.BIOS_MINOR)
|
||||
},
|
||||
CHIDVariant.CHID_03: {
|
||||
'name': 'HardwareID-03',
|
||||
'fields': (1 << CHIDField.MANUF) | (1 << CHIDField.FAMILY) |
|
||||
(1 << CHIDField.PRODUCT_NAME) | (1 << CHIDField.PRODUCT_SKU) |
|
||||
(1 << CHIDField.BOARD_MANUF) | (1 << CHIDField.BOARD_PRODUCT)
|
||||
},
|
||||
CHIDVariant.CHID_04: {
|
||||
'name': 'HardwareID-04',
|
||||
'fields': (1 << CHIDField.MANUF) | (1 << CHIDField.FAMILY) |
|
||||
(1 << CHIDField.PRODUCT_NAME) | (1 << CHIDField.PRODUCT_SKU)
|
||||
},
|
||||
CHIDVariant.CHID_05: {
|
||||
'name': 'HardwareID-05',
|
||||
'fields': (1 << CHIDField.MANUF) | (1 << CHIDField.FAMILY) |
|
||||
(1 << CHIDField.PRODUCT_NAME)
|
||||
},
|
||||
CHIDVariant.CHID_06: {
|
||||
'name': 'HardwareID-06',
|
||||
'fields': (1 << CHIDField.MANUF) | (1 << CHIDField.PRODUCT_SKU) |
|
||||
(1 << CHIDField.BOARD_MANUF) | (1 << CHIDField.BOARD_PRODUCT)
|
||||
},
|
||||
CHIDVariant.CHID_07: {
|
||||
'name': 'HardwareID-07',
|
||||
'fields': (1 << CHIDField.MANUF) | (1 << CHIDField.PRODUCT_SKU)
|
||||
},
|
||||
CHIDVariant.CHID_08: {
|
||||
'name': 'HardwareID-08',
|
||||
'fields': (1 << CHIDField.MANUF) | (1 << CHIDField.PRODUCT_NAME) |
|
||||
(1 << CHIDField.BOARD_MANUF) | (1 << CHIDField.BOARD_PRODUCT)
|
||||
},
|
||||
CHIDVariant.CHID_09: {
|
||||
'name': 'HardwareID-09',
|
||||
'fields': (1 << CHIDField.MANUF) | (1 << CHIDField.PRODUCT_NAME)
|
||||
},
|
||||
CHIDVariant.CHID_10: {
|
||||
'name': 'HardwareID-10',
|
||||
'fields': (1 << CHIDField.MANUF) | (1 << CHIDField.FAMILY) |
|
||||
(1 << CHIDField.BOARD_MANUF) | (1 << CHIDField.BOARD_PRODUCT)
|
||||
},
|
||||
CHIDVariant.CHID_11: {
|
||||
'name': 'HardwareID-11',
|
||||
'fields': (1 << CHIDField.MANUF) | (1 << CHIDField.FAMILY)
|
||||
},
|
||||
CHIDVariant.CHID_12: {
|
||||
'name': 'HardwareID-12',
|
||||
'fields': (1 << CHIDField.MANUF) | (1 << CHIDField.ENCLOSURE_TYPE)
|
||||
},
|
||||
CHIDVariant.CHID_13: {
|
||||
'name': 'HardwareID-13',
|
||||
'fields': (1 << CHIDField.MANUF) | (1 << CHIDField.BOARD_MANUF) |
|
||||
(1 << CHIDField.BOARD_PRODUCT)
|
||||
},
|
||||
CHIDVariant.CHID_14: {
|
||||
'name': 'HardwareID-14',
|
||||
'fields': (1 << CHIDField.MANUF)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
def load_compatible_map(hwids_dir):
|
||||
"""Load the compatible string mapping from compatible-map file
|
||||
|
||||
Args:
|
||||
hwids_dir (str): Directory containing the compatible-map file
|
||||
|
||||
Returns:
|
||||
dict: Mapping from filename to compatible string, empty if there is no
|
||||
map
|
||||
"""
|
||||
compatible_map = {}
|
||||
map_file = os.path.join(hwids_dir, 'compatible-map')
|
||||
|
||||
if not os.path.exists(map_file):
|
||||
return compatible_map
|
||||
|
||||
with open(map_file, 'r', encoding='utf-8') as f:
|
||||
for line in f:
|
||||
line = line.strip()
|
||||
if line and not line.startswith('#'):
|
||||
parts = line.split(': ', 1)
|
||||
if len(parts) == 2:
|
||||
compatible_map[parts[0]] = parts[1]
|
||||
|
||||
return compatible_map
|
||||
|
||||
|
||||
def parse_variant_description(description):
|
||||
"""Parse variant description to determine CHID variant ID and fields mask
|
||||
|
||||
Args:
|
||||
description (str): Description text after '<-' in HWIDS file
|
||||
|
||||
Returns:
|
||||
tuple: (variant_id, fields_mask) where variant_id is int (0-14) or None,
|
||||
and fields_mask is the bitmask or None if not parseable
|
||||
|
||||
Examples:
|
||||
>>> parse_variant_description('Manufacturer + Family + ProductName')
|
||||
(5, 7) # HardwareID-05 with fields 0x1|0x2|0x4 = 0x7
|
||||
|
||||
>>> parse_variant_description('Manufacturer')
|
||||
(14, 1) # HardwareID-14 with field 0x1
|
||||
|
||||
>>> parse_variant_description('Invalid + Field')
|
||||
(None, 0) # Unknown fields, no variant match
|
||||
"""
|
||||
# Parse field names and match to variants
|
||||
desc = description.strip()
|
||||
|
||||
# Parse the field list
|
||||
fields_mask = 0
|
||||
field_parts = desc.split(' + ')
|
||||
for part in field_parts:
|
||||
part = part.strip()
|
||||
if part in FIELD_MAP:
|
||||
# Get CHIDField, ignore dt property name
|
||||
chid_field, _ = FIELD_MAP[part]
|
||||
if chid_field is not None: # Only add to mask if it's a CHIDField
|
||||
fields_mask |= (1 << chid_field)
|
||||
|
||||
# If no fields were parsed, return None for both
|
||||
if not fields_mask:
|
||||
return None, None
|
||||
|
||||
# Match against known variants
|
||||
for variant_id, variant_info in CHID_VARIANTS.items():
|
||||
if variant_info['fields'] == fields_mask:
|
||||
return int(variant_id), fields_mask
|
||||
|
||||
# Fields were parsed but don't match a known variant
|
||||
return None, fields_mask
|
||||
|
||||
|
||||
def _parse_hardware_ids_section(content, filepath):
|
||||
"""Parse the Hardware IDs section from HWIDS file content
|
||||
|
||||
Args:
|
||||
content (str): Full file content
|
||||
filepath (str): Path to the file for error reporting
|
||||
|
||||
Returns:
|
||||
list: List of (guid_string, variant_id, bitmask) tuples
|
||||
"""
|
||||
hardware_ids = []
|
||||
ids_pattern = r'Hardware IDs\n-+\n(.*?)(?:\n\n|$)'
|
||||
ids_section = re.search(ids_pattern, content, re.DOTALL)
|
||||
if not ids_section:
|
||||
raise ValueError(f'{filepath}: Missing "Hardware IDs" section')
|
||||
|
||||
for linenum, line in enumerate(ids_section.group(1).strip().split('\n'), 1):
|
||||
if not line.strip():
|
||||
continue
|
||||
|
||||
# Extract GUID and variant info from line like:
|
||||
# '{810e34c6-cc69-5e36-8675-2f6e354272d3}' <- HardwareID-00
|
||||
guid_match = re.search(rf'\{{([0-9a-f-]{{{GUID_LENGTH}}})\}}', line)
|
||||
if not guid_match:
|
||||
continue
|
||||
|
||||
guid = guid_match.group(1)
|
||||
|
||||
# The '<-' separator is required for valid HWIDS files
|
||||
if '<-' not in line:
|
||||
# Calculate actual line number in file
|
||||
# (need to account for lines before Hardware IDs section)
|
||||
before = content[:content.find('Hardware IDs')].count('\n')
|
||||
# +2 for header and dashes
|
||||
actual_line = before + linenum + HWIDS_SECTION_HEADER_LINES
|
||||
raise ValueError(
|
||||
f"{filepath}:{actual_line}: Missing '<-' separator in "
|
||||
f'Hardware ID line: {line.strip()}')
|
||||
|
||||
description = line.split('<-', 1)[1].strip()
|
||||
variant_id, bitmask = parse_variant_description(description)
|
||||
|
||||
hardware_ids.append((guid, variant_id, bitmask))
|
||||
|
||||
return hardware_ids
|
||||
|
||||
|
||||
def parse_hwids_file(filepath):
|
||||
"""Parse a HWIDS txt file and return computer info and hardware IDs
|
||||
|
||||
Args:
|
||||
filepath (str): Path to the HWIDS txt file
|
||||
|
||||
Returns:
|
||||
tuple: (computer_info dict, hardware_ids list of tuples)
|
||||
hardware_ids contains (guid_string, variant_id, bitmask) tuples
|
||||
"""
|
||||
info = {}
|
||||
hardware_ids = []
|
||||
|
||||
with open(filepath, 'r', encoding='utf-8') as f:
|
||||
content = f.read()
|
||||
|
||||
# Extract computer information section
|
||||
info_section = re.search(r'Computer Information\n-+\n(.*?)\nHardware IDs',
|
||||
content, re.DOTALL)
|
||||
if not info_section:
|
||||
raise ValueError(f'{filepath}: Missing "Computer Information" section')
|
||||
|
||||
for line in info_section.group(1).strip().split('\n'):
|
||||
if ':' in line:
|
||||
key, value = line.split(':', 1)
|
||||
info[key.strip()] = value.strip()
|
||||
|
||||
# Extract hardware IDs with variant information
|
||||
hardware_ids = _parse_hardware_ids_section(content, filepath)
|
||||
|
||||
return info, hardware_ids
|
||||
|
||||
|
||||
def _add_header(out, basename, source_path=None):
|
||||
"""Add header section to devicetree source
|
||||
|
||||
Args:
|
||||
out (StringIO): StringIO object to write to
|
||||
basename (str): Base filename for the header comment
|
||||
source_path (str): Path to the source file or directory
|
||||
"""
|
||||
out.write('// SPDX-License-Identifier: GPL-2.0+\n')
|
||||
out.write('\n')
|
||||
out.write(f'// Computer Hardware IDs for {basename}\n')
|
||||
if source_path:
|
||||
out.write(f'// Generated from {source_path}\n')
|
||||
else:
|
||||
out.write('// Generated from board/efi/hwids/\n')
|
||||
out.write('\n')
|
||||
|
||||
|
||||
def _add_computer_info(out, computer_info, indent=2):
|
||||
"""Add computer information properties to devicetree source
|
||||
|
||||
Args:
|
||||
out (StringIO): StringIO object to write to
|
||||
computer_info (dict): Dictionary of computer information fields
|
||||
indent (int): Number of tab indentations (default 2 for &chid
|
||||
structure)
|
||||
"""
|
||||
indent = '\t' * indent
|
||||
out.write(f'{indent}// SMBIOS Computer Information\n')
|
||||
for key, value in computer_info.items():
|
||||
# Look up the devicetree property name from FIELD_MAP
|
||||
if key in FIELD_MAP:
|
||||
_, prop_name = FIELD_MAP[key]
|
||||
else:
|
||||
# Fallback for fields not in FIELD_MAP (e.g. FirmwareMajorRelease,
|
||||
# FirmwareMinorRelease)
|
||||
prop_name = key.lower().replace('release', '-release')
|
||||
|
||||
# Handle numeric values vs strings
|
||||
if key in VERSION_FIELDS and value.isdigit():
|
||||
out.write(f'{indent}{prop_name} = <{value}>;\n')
|
||||
elif key == HEX_ENCLOSURE_FIELD:
|
||||
# Value is already a hex string, convert directly
|
||||
hex_val = int(value, HEX_BASE)
|
||||
out.write(f'{indent}{prop_name} = <0x{hex_val:x}>;\n')
|
||||
else:
|
||||
out.write(f'{indent}{prop_name} = "{value}";\n')
|
||||
|
||||
|
||||
def _add_hardware_ids(out, hardware_ids, indent=2):
|
||||
"""Add hardware IDs as subnodes to devicetree source
|
||||
|
||||
Args:
|
||||
out (StringIO): StringIO object to write to
|
||||
hardware_ids (list): List of (guid_string, variant_id, bitmask) tuples
|
||||
indent (int): Number of tab indentations (default 2 for &chid
|
||||
structure)
|
||||
"""
|
||||
indent = '\t' * indent
|
||||
out.write(f'{indent}// Hardware IDs (CHIDs)\n')
|
||||
|
||||
extra_counter = 0
|
||||
for guid, variant_id, bitmask in hardware_ids:
|
||||
# Convert GUID string to binary array for devicetree
|
||||
guid_obj = uuid.UUID(guid)
|
||||
binary_data = guid_obj.bytes # Raw 16 bytes, no endian conversion
|
||||
hex_bytes = ' '.join(f'{b:02x}' for b in binary_data)
|
||||
hex_array = f'[{hex_bytes}]'
|
||||
|
||||
# Create node name - use variant number if available, otherwise extra-N
|
||||
if variant_id is not None:
|
||||
node_name = f'hardware-id-{variant_id:02d}'
|
||||
variant_info = CHID_VARIANTS.get(variant_id, {})
|
||||
comment = variant_info.get('name', f'Unknown-{variant_id}')
|
||||
else:
|
||||
node_name = f'extra-{extra_counter}'
|
||||
comment = 'unknown variant'
|
||||
extra_counter += 1
|
||||
|
||||
out.write('\n')
|
||||
out.write(f'{indent}{node_name} {{ // {comment}\n')
|
||||
|
||||
# Add variant property if known
|
||||
if variant_id is not None:
|
||||
out.write(f'{indent}\tvariant = <{variant_id}>;\n')
|
||||
|
||||
# Add fields property if bitmask is known
|
||||
if bitmask is not None:
|
||||
out.write(f'{indent}\tfields = <0x{bitmask:x}>;\n')
|
||||
|
||||
# Add CHID bytes
|
||||
out.write(f'{indent}\tchid = {hex_array};\n')
|
||||
|
||||
out.write(f'{indent}}};\n')
|
||||
|
||||
|
||||
def generate_dtsi(basename, compatible, computer_info, hardware_ids,
|
||||
source_path=None):
|
||||
"""Generate devicetree source content
|
||||
|
||||
Args:
|
||||
basename (str): Base filename for comments and node name
|
||||
compatible (str): Compatible string for the node
|
||||
computer_info (dict): Dictionary of computer information
|
||||
hardware_ids (list): List of (guid_string, variant_id, bitmask) tuples
|
||||
source_path (str): Path to the source file or directory
|
||||
|
||||
Returns:
|
||||
str: Complete devicetree source content
|
||||
|
||||
Examples:
|
||||
>>> info = {'Manufacturer': 'ACME', 'ProductName': 'Device'}
|
||||
>>> hwids = [('12345678-1234-5678-9abc-123456789abc', 14, 1)]
|
||||
>>> dtsi = generate_dtsi('acme-device', 'acme,device', info, hwids)
|
||||
>>> '// Computer Hardware IDs for acme-device' in dtsi
|
||||
True
|
||||
>>> 'compatible = "acme,device"' in dtsi
|
||||
True
|
||||
"""
|
||||
out = StringIO()
|
||||
|
||||
_add_header(out, basename, source_path)
|
||||
|
||||
# Start root node with chid declaration
|
||||
out.write('/ {\n')
|
||||
out.write('\tchid: chid {};\n')
|
||||
out.write('};\n')
|
||||
out.write('\n')
|
||||
|
||||
# Add device content to chid node using reference
|
||||
out.write('&chid {\n')
|
||||
node_name = basename.replace('.', '-')
|
||||
out.write(f'\t{node_name} {{\n')
|
||||
out.write(f'\t\tcompatible = "{compatible}";\n')
|
||||
out.write('\n')
|
||||
|
||||
_add_computer_info(out, computer_info, indent=2)
|
||||
out.write('\n')
|
||||
|
||||
_add_hardware_ids(out, hardware_ids, indent=2)
|
||||
|
||||
out.write('\t};\n')
|
||||
out.write('};\n')
|
||||
|
||||
return out.getvalue()
|
||||
|
||||
|
||||
def parse_arguments():
|
||||
"""Parse command line arguments
|
||||
|
||||
Returns:
|
||||
argparse.Namespace: Parsed command line arguments
|
||||
"""
|
||||
parser = argparse.ArgumentParser(
|
||||
description='Convert HWIDS txt files to devicetree source (.dtsi)'
|
||||
)
|
||||
group = parser.add_mutually_exclusive_group(required=True)
|
||||
group.add_argument(
|
||||
'input_file',
|
||||
nargs='?',
|
||||
help='Path to HWIDS txt file (e.g., board/efi/hwids/filename.txt)'
|
||||
)
|
||||
group.add_argument(
|
||||
'-m', '--map-file',
|
||||
help='compatible.hwidmap file (processes all .txt files in same dir)'
|
||||
)
|
||||
parser.add_argument(
|
||||
'-o', '--output',
|
||||
help='Output file (default: basename.dtsi or hwids.dtsi for dir mode)'
|
||||
)
|
||||
parser.add_argument(
|
||||
'-v', '--verbose',
|
||||
action='store_true',
|
||||
help='Show verbose output with conversion details'
|
||||
)
|
||||
parser.add_argument(
|
||||
'-D', '--debug',
|
||||
action='store_true',
|
||||
help='Show debug traceback on errors'
|
||||
)
|
||||
|
||||
return parser.parse_args()
|
||||
|
||||
|
||||
def _process_board_file(out, compatible, txt_file, boards_processed, verbose):
|
||||
"""Process a single board HWIDS file and add to output
|
||||
|
||||
Args:
|
||||
out (StringIO): StringIO object to write to
|
||||
compatible (str): Compatible string for the board
|
||||
txt_file (str): Full path to the HWIDS txt file
|
||||
boards_processed (int): Number of boards processed so far
|
||||
verbose (bool): Whether to show verbose output
|
||||
|
||||
Returns:
|
||||
bool: True if board was successfully processed, False otherwise
|
||||
"""
|
||||
basename = os.path.splitext(os.path.basename(txt_file))[0]
|
||||
if verbose:
|
||||
print(f'Processing {basename}...')
|
||||
|
||||
computer, hardware_ids = parse_hwids_file(txt_file)
|
||||
|
||||
if not hardware_ids:
|
||||
if verbose:
|
||||
print(f' Warning: No hardware IDs found in {basename}')
|
||||
return False
|
||||
|
||||
# Add blank line between boards
|
||||
if boards_processed > 0:
|
||||
out.write('\n')
|
||||
|
||||
# Generate board node directly (combining all boards)
|
||||
node_name = basename.replace('.', '-')
|
||||
out.write(f'\t{node_name} {{\n')
|
||||
out.write(f'\t\tcompatible = "{compatible}";\n')
|
||||
out.write('\n')
|
||||
|
||||
# Add computer info and hardware IDs
|
||||
_add_computer_info(out, computer, indent=2)
|
||||
out.write('\n')
|
||||
|
||||
_add_hardware_ids(out, hardware_ids, indent=2)
|
||||
|
||||
out.write('\t};\n')
|
||||
|
||||
if verbose:
|
||||
print(f' Added {len(hardware_ids)} hardware IDs')
|
||||
|
||||
return True
|
||||
|
||||
|
||||
def _load_and_validate_map_file(map_file_path, verbose=False):
|
||||
"""Load and validate compatible map file
|
||||
|
||||
Args:
|
||||
map_file_path (str): Path to the compatible.hwidmap file
|
||||
verbose (bool): Show verbose output
|
||||
|
||||
Returns:
|
||||
tuple: (hwids_dir, compatible_map)
|
||||
"""
|
||||
# Get directory containing the map file
|
||||
hwids_dir = os.path.dirname(map_file_path)
|
||||
|
||||
# Load compatible string mapping from the specified file
|
||||
compatible_map = {}
|
||||
if os.path.exists(map_file_path):
|
||||
with open(map_file_path, 'r', encoding='utf-8') as f:
|
||||
for line in f:
|
||||
line = line.strip()
|
||||
if line and not line.startswith('#'):
|
||||
parts = line.split(': ', 1)
|
||||
if len(parts) == 2:
|
||||
compatible_map[parts[0]] = parts[1]
|
||||
|
||||
if not compatible_map:
|
||||
raise ValueError(f'No valid mappings found in {map_file_path}')
|
||||
|
||||
# Find all .txt files in the same directory
|
||||
txt_files = glob.glob(os.path.join(hwids_dir, '*.txt'))
|
||||
txt_basenames = {os.path.splitext(os.path.basename(f))[0]
|
||||
for f in txt_files}
|
||||
|
||||
# Check for files in map that don't exist
|
||||
map_files = set(compatible_map.keys())
|
||||
missing_files = map_files - txt_basenames
|
||||
if missing_files:
|
||||
raise ValueError('Files in map but not found in directory: '
|
||||
f"{', '.join(sorted(missing_files))}")
|
||||
|
||||
# Check for files in directory that aren't in map
|
||||
extra_files = txt_basenames - map_files
|
||||
if extra_files:
|
||||
file_list = ', '.join(sorted(extra_files))
|
||||
raise ValueError(f'Files in directory but not in map: {file_list}')
|
||||
|
||||
if verbose:
|
||||
print(f'Using compatible map: {map_file_path}')
|
||||
print(f'Processing {len(compatible_map)} HWIDs files from map')
|
||||
print()
|
||||
|
||||
return hwids_dir, compatible_map
|
||||
|
||||
|
||||
def _finalise_combined_dtsi(out, hwids_dir, processed, skipped, verbose):
|
||||
"""Finalize the combined DTSI output with validation and reporting
|
||||
|
||||
Args:
|
||||
out (StringIO): StringIO object containing the main content
|
||||
hwids_dir (str): Directory path for header generation
|
||||
processed (int): Number of successfully processed files
|
||||
skipped (list): List of skipped board names
|
||||
verbose (bool): Whether to show verbose output
|
||||
|
||||
Returns:
|
||||
str: Final DTSI content with header and footer
|
||||
"""
|
||||
if not processed:
|
||||
raise ValueError('No valid HWIDS files could be processed')
|
||||
|
||||
if verbose:
|
||||
print(f'\nProcessed {processed} boards successfully')
|
||||
|
||||
# Print warning about skipped boards
|
||||
if skipped:
|
||||
print(f'Warning: Skipped {len(skipped)} unmapped boards: '
|
||||
f"{', '.join(skipped)}")
|
||||
|
||||
header = DTSI_HEADER.replace('source_path', hwids_dir)
|
||||
return ''.join([header, out.getvalue(), DTSI_FOOTER])
|
||||
|
||||
|
||||
def process_map_file(map_file_path, verbose=False):
|
||||
"""Process HWIDS files specified in the map file and generate combined DTSI
|
||||
|
||||
Args:
|
||||
map_file_path (str): Path to the compatible.hwidmap file
|
||||
verbose (bool): Show verbose output
|
||||
|
||||
Returns:
|
||||
str: Combined DTSI content for all boards
|
||||
"""
|
||||
hwids_dir, compatible_map = _load_and_validate_map_file(map_file_path,
|
||||
verbose)
|
||||
|
||||
out = StringIO()
|
||||
processed = 0
|
||||
skipped = []
|
||||
for basename in sorted(compatible_map.keys()):
|
||||
compatible = compatible_map[basename]
|
||||
|
||||
# Skip files with 'none' mapping
|
||||
if compatible == 'none':
|
||||
skipped.append(basename)
|
||||
if verbose:
|
||||
print(f'Skipping {basename} (mapping: none)')
|
||||
continue
|
||||
|
||||
# Process this board file
|
||||
txt_file = os.path.join(hwids_dir, f'{basename}.txt')
|
||||
if _process_board_file(out, compatible, txt_file, processed, verbose):
|
||||
processed += 1
|
||||
|
||||
return _finalise_combined_dtsi(out, hwids_dir, processed, skipped,
|
||||
verbose)
|
||||
|
||||
|
||||
def handle_map_file_mode(args):
|
||||
"""Handle map file mode processing
|
||||
|
||||
Args:
|
||||
args (argparse.Namespace): Parsed command line arguments
|
||||
|
||||
Returns:
|
||||
str: Generated DTSI content
|
||||
"""
|
||||
if not os.path.exists(args.map_file):
|
||||
raise FileNotFoundError(f'Map file {args.map_file} not found')
|
||||
|
||||
dtsi_content = process_map_file(args.map_file, args.verbose)
|
||||
outfile = args.output or 'hwids.dtsi'
|
||||
|
||||
if args.verbose:
|
||||
print(f'Generated combined DTSI -> {outfile}')
|
||||
print()
|
||||
|
||||
return dtsi_content
|
||||
|
||||
|
||||
def handle_single_file_mode(args):
|
||||
"""Handle single file mode processing
|
||||
|
||||
Args:
|
||||
args (argparse.Namespace): Parsed command line arguments
|
||||
|
||||
Returns:
|
||||
str: Generated DTSI content
|
||||
"""
|
||||
if not args.input_file:
|
||||
raise ValueError('input_file is required when not using --map-file')
|
||||
|
||||
if not os.path.exists(args.input_file):
|
||||
raise FileNotFoundError(f"File '{args.input_file}' not found")
|
||||
|
||||
# Get the directory and basename
|
||||
hwids_dir = os.path.dirname(args.input_file)
|
||||
basename = os.path.splitext(os.path.basename(args.input_file))[0]
|
||||
|
||||
# Load compatible string mapping
|
||||
compatible_map = load_compatible_map(hwids_dir)
|
||||
compatible = compatible_map.get(basename, f'unknown,{basename}')
|
||||
|
||||
# Parse the input file
|
||||
info, hardware_ids = parse_hwids_file(args.input_file)
|
||||
|
||||
if not hardware_ids and args.verbose:
|
||||
print(f"Warning: No hardware IDs found in '{args.input_file}'")
|
||||
|
||||
# Generate devicetree source
|
||||
content = generate_dtsi(basename, compatible, info,
|
||||
hardware_ids, args.input_file)
|
||||
|
||||
outfile = args.output or f'{basename}.dtsi'
|
||||
if args.verbose:
|
||||
print(f'Converting {args.input_file} -> {outfile}')
|
||||
print(f'Compatible: {compatible}')
|
||||
print(f'Computer info fields: {len(info)}')
|
||||
print(f'Hardware IDs: {len(hardware_ids)}')
|
||||
print()
|
||||
|
||||
return content
|
||||
|
||||
|
||||
def main():
|
||||
"""Main function
|
||||
|
||||
Returns:
|
||||
int: Exit code (0 for success, 1 for error)
|
||||
"""
|
||||
args = parse_arguments()
|
||||
try:
|
||||
# Choose processing mode and handle
|
||||
if args.map_file:
|
||||
content = handle_map_file_mode(args)
|
||||
outfile = args.output or 'hwids.dtsi'
|
||||
else:
|
||||
content = handle_single_file_mode(args)
|
||||
basename = os.path.splitext(os.path.basename(args.input_file))[0]
|
||||
outfile = args.output or f'{basename}.dtsi'
|
||||
|
||||
# Write to file if output specified, otherwise print to stdout
|
||||
if args.output:
|
||||
try:
|
||||
with open(outfile, 'w', encoding='utf-8') as f:
|
||||
f.write(content)
|
||||
if args.verbose:
|
||||
print(f'Written to {outfile}')
|
||||
except (IOError, OSError) as e:
|
||||
print(f'Error writing to {outfile}: {e}')
|
||||
return 1
|
||||
else:
|
||||
print(content, end='')
|
||||
|
||||
return 0
|
||||
|
||||
except (ValueError, FileNotFoundError, IOError, OSError) as e:
|
||||
if args.debug:
|
||||
traceback.print_exc()
|
||||
else:
|
||||
print(f'Error: {e}')
|
||||
return 1
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
sys.exit(main())
|
||||
306
test/scripts/test_hwids_to_dtsi.py
Normal file
306
test/scripts/test_hwids_to_dtsi.py
Normal file
@@ -0,0 +1,306 @@
|
||||
#!/usr/bin/env python3
|
||||
# SPDX-License-Identifier: GPL-2.0+
|
||||
"""
|
||||
Test for hwids-to-dtsi.py script
|
||||
|
||||
Validates that the HWIDS to devicetree conversion script correctly parses
|
||||
hardware ID files and generates proper devicetree-source output
|
||||
"""
|
||||
|
||||
import os
|
||||
import sys
|
||||
import tempfile
|
||||
import unittest
|
||||
import uuid
|
||||
from io import StringIO
|
||||
|
||||
# Add the scripts directory to the path
|
||||
script_dir = os.path.join(os.path.dirname(__file__), '..', '..', 'scripts')
|
||||
sys.path.insert(0, script_dir)
|
||||
|
||||
# pylint: disable=wrong-import-position,import-error
|
||||
from hwids_to_dtsi import (
|
||||
load_compatible_map,
|
||||
parse_hwids_file,
|
||||
generate_dtsi,
|
||||
parse_variant_description,
|
||||
VERSION_FIELDS,
|
||||
HEX_ENCLOSURE_FIELD,
|
||||
_finalise_combined_dtsi
|
||||
)
|
||||
|
||||
|
||||
class TestHwidsToDeviceTree(unittest.TestCase):
|
||||
"""Test cases for HWIDS to devicetree conversion"""
|
||||
|
||||
def setUp(self):
|
||||
"""Set up test fixtures"""
|
||||
self.test_hwids_content = """Computer Information
|
||||
--------------------
|
||||
BiosVendor: Insyde Corp.
|
||||
BiosVersion: V1.24
|
||||
BiosMajorRelease: 0
|
||||
BiosMinorRelease: 0
|
||||
FirmwareMajorRelease: 01
|
||||
FirmwareMinorRelease: 15
|
||||
Manufacturer: Acer
|
||||
Family: Swift 14 AI
|
||||
ProductName: Swift SF14-11
|
||||
ProductSku:
|
||||
EnclosureKind: a
|
||||
BaseboardManufacturer: SX1
|
||||
BaseboardProduct: Bluetang_SX1
|
||||
Hardware IDs
|
||||
------------
|
||||
{27d2dba8-e6f1-5c19-ba1c-c25a4744c161} <- Manufacturer + Family + ProductName + ProductSku + BiosVendor + BiosVersion + BiosMajorRelease + BiosMinorRelease
|
||||
{676172cd-d185-53ed-aac6-245d0caa02c4} <- Manufacturer + Family + ProductName + BiosVendor + BiosVersion + BiosMajorRelease + BiosMinorRelease
|
||||
{20c2cf2f-231c-5d02-ae9b-c837ab5653ed} <- Manufacturer + ProductName + BiosVendor + BiosVersion + BiosMajorRelease + BiosMinorRelease
|
||||
{f2ea7095-999d-5e5b-8f2a-4b636a1e399f} <- Manufacturer + Family + ProductName + ProductSku + BaseboardManufacturer + BaseboardProduct
|
||||
{331d7526-8b88-5923-bf98-450cf3ea82a4} <- Manufacturer + Family + ProductName + ProductSku
|
||||
{98ad068a-f812-5f13-920c-3ff3d34d263f} <- Manufacturer + Family + ProductName
|
||||
{3f49141c-d8fb-5a6f-8b4a-074a2397874d} <- Manufacturer + ProductSku + BaseboardManufacturer + BaseboardProduct
|
||||
{7c107a7f-2d77-51aa-aef8-8d777e26ffbc} <- Manufacturer + ProductSku
|
||||
{6a12c9bc-bcfa-5448-9f66-4159dbe8c326} <- Manufacturer + ProductName + BaseboardManufacturer + BaseboardProduct
|
||||
{f55122fb-303f-58bc-b342-6ef653956d1d} <- Manufacturer + ProductName
|
||||
{ee8fa049-e5f4-51e4-89d8-89a0140b8f38} <- Manufacturer + Family + BaseboardManufacturer + BaseboardProduct
|
||||
{4cdff732-fd0c-5bac-b33e-9002788ea557} <- Manufacturer + Family
|
||||
{92dcc94d-48f7-5ee8-b9ec-a6393fb7a484} <- Manufacturer + EnclosureKind
|
||||
{32f83b0f-1fad-5be2-88be-5ab020e7a70e} <- Manufacturer + BaseboardManufacturer + BaseboardProduct
|
||||
{1e301734-5d49-5df4-9ed2-aa1c0a9dddda} <- Manufacturer
|
||||
Extra Hardware IDs
|
||||
------------------
|
||||
{058c0739-1843-5a10-bab7-fae8aaf30add} <- Manufacturer + Family + ProductName + ProductSku + BiosVendor
|
||||
{100917f4-9c0a-5ac3-a297-794222da9bc9} <- Manufacturer + Family + ProductName + BiosVendor
|
||||
{86654360-65f0-5935-bc87-81102c6a022b} <- Manufacturer + BiosVendor
|
||||
"""
|
||||
|
||||
self.test_compatible_map = """# SPDX-License-Identifier: GPL-2.0+
|
||||
# compatible map
|
||||
test-device: test,example-device
|
||||
"""
|
||||
|
||||
def test_parse_hwids_file(self):
|
||||
"""Test parsing of HWIDS file content"""
|
||||
with tempfile.NamedTemporaryFile(mode='w', suffix='.txt',
|
||||
delete=False) as outf:
|
||||
outf.write(self.test_hwids_content)
|
||||
outf.flush()
|
||||
|
||||
try:
|
||||
info, hardware_ids = parse_hwids_file(outf.name)
|
||||
expected_info = {
|
||||
'BiosVendor': 'Insyde Corp.',
|
||||
'BiosVersion': 'V1.24',
|
||||
'BiosMajorRelease': '0',
|
||||
'BiosMinorRelease': '0',
|
||||
'FirmwareMajorRelease': '01',
|
||||
'FirmwareMinorRelease': '15',
|
||||
'Manufacturer': 'Acer',
|
||||
'Family': 'Swift 14 AI',
|
||||
'ProductName': 'Swift SF14-11',
|
||||
'ProductSku': '',
|
||||
'EnclosureKind': 'a',
|
||||
'BaseboardManufacturer': 'SX1',
|
||||
'BaseboardProduct': 'Bluetang_SX1'
|
||||
}
|
||||
self.assertEqual(info, expected_info)
|
||||
|
||||
# Check hardware IDs (now tuples with variant info and bitmask)
|
||||
expected_ids = [
|
||||
# Variant 0: All main fields
|
||||
('27d2dba8-e6f1-5c19-ba1c-c25a4744c161', 0, 0x3cf),
|
||||
# Variant 1: Without SKU
|
||||
('676172cd-d185-53ed-aac6-245d0caa02c4', 1, 0x3c7),
|
||||
# Variant 2: Without family
|
||||
('20c2cf2f-231c-5d02-ae9b-c837ab5653ed', 2, 0x3c5),
|
||||
# Variant 3: With baseboard, no BIOS version
|
||||
('f2ea7095-999d-5e5b-8f2a-4b636a1e399f', 3, 0x3f),
|
||||
# Variant 4: Basic product ID
|
||||
('331d7526-8b88-5923-bf98-450cf3ea82a4', 4, 0xf),
|
||||
# Variant 5: Without SKU
|
||||
('98ad068a-f812-5f13-920c-3ff3d34d263f', 5, 0x7),
|
||||
# Variant 6: SKU with baseboard
|
||||
('3f49141c-d8fb-5a6f-8b4a-074a2397874d', 6, 0x39),
|
||||
# Variant 7: Manufacturer and SKU
|
||||
('7c107a7f-2d77-51aa-aef8-8d777e26ffbc', 7, 0x9),
|
||||
# Variant 8: Product name with baseboard
|
||||
('6a12c9bc-bcfa-5448-9f66-4159dbe8c326', 8, 0x35),
|
||||
# Variant 9: Manufacturer and product name
|
||||
('f55122fb-303f-58bc-b342-6ef653956d1d', 9, 0x5),
|
||||
# Variant 10: Family with baseboard
|
||||
('ee8fa049-e5f4-51e4-89d8-89a0140b8f38', 10, 0x33),
|
||||
# Variant 11: Manufacturer and family
|
||||
('4cdff732-fd0c-5bac-b33e-9002788ea557', 11, 0x3),
|
||||
# Variant 12: Manufacturer and enclosure
|
||||
('92dcc94d-48f7-5ee8-b9ec-a6393fb7a484', 12, 0x401),
|
||||
# Variant 13: Manufacturer with baseboard
|
||||
('32f83b0f-1fad-5be2-88be-5ab020e7a70e', 13, 0x31),
|
||||
# Variant 14: Manufacturer only
|
||||
('1e301734-5d49-5df4-9ed2-aa1c0a9dddda', 14, 0x1),
|
||||
# Extra Hardware IDs (non-standard variants)
|
||||
# Extra: Manufacturer + Family + ProductName + ProductSku + BiosVendor
|
||||
('058c0739-1843-5a10-bab7-fae8aaf30add', None, 0x4f),
|
||||
# Extra: Manufacturer + Family + ProductName + BiosVendor
|
||||
('100917f4-9c0a-5ac3-a297-794222da9bc9', None, 0x47),
|
||||
# Extra: Manufacturer + BiosVendor
|
||||
('86654360-65f0-5935-bc87-81102c6a022b', None, 0x41),
|
||||
]
|
||||
self.assertEqual(hardware_ids, expected_ids)
|
||||
|
||||
finally:
|
||||
os.unlink(outf.name)
|
||||
|
||||
def test_load_compatible_map(self):
|
||||
"""Test loading compatible string mapping"""
|
||||
with tempfile.TemporaryDirectory() as tmpdir:
|
||||
map_file = os.path.join(tmpdir, 'compatible-map')
|
||||
with open(map_file, 'w', encoding='utf-8') as f:
|
||||
f.write(self.test_compatible_map)
|
||||
|
||||
compatible_map = load_compatible_map(tmpdir)
|
||||
self.assertEqual(compatible_map['test-device'],
|
||||
'test,example-device')
|
||||
|
||||
def test_guid_to_binary(self):
|
||||
"""Test GUID to binary conversion"""
|
||||
test_guid = '810e34c6-cc69-5e36-8675-2f6e354272d3'
|
||||
guid_obj = uuid.UUID(test_guid)
|
||||
binary_data = guid_obj.bytes
|
||||
|
||||
# Should be 16 bytes
|
||||
self.assertEqual(len(binary_data), 16)
|
||||
|
||||
# Test known conversion (raw bytes in string order)
|
||||
expected = bytearray([
|
||||
0x81, 0x0e, 0x34, 0xc6, # time_low (raw bytes)
|
||||
0xcc, 0x69, # time_mid (raw bytes)
|
||||
0x5e, 0x36, # time_hi (raw bytes)
|
||||
0x86, 0x75, # clock_seq (raw bytes)
|
||||
0x2f, 0x6e, 0x35, 0x42, 0x72, 0xd3 # node (raw bytes)
|
||||
])
|
||||
self.assertEqual(binary_data, bytes(expected))
|
||||
|
||||
|
||||
def test_generate_dtsi(self):
|
||||
"""Test devicetree source generation"""
|
||||
info = {
|
||||
'Manufacturer': 'LENOVO',
|
||||
'ProductName': '21BXCTO1WW',
|
||||
'BiosMajorRelease': '1',
|
||||
'EnclosureKind': 'a'
|
||||
}
|
||||
hardware_ids = [('810e34c6-cc69-5e36-8675-2f6e354272d3', 0, 0x3cf)]
|
||||
|
||||
content = generate_dtsi('test-device', 'test,example-device',
|
||||
info, hardware_ids)
|
||||
|
||||
self.assertIn('// SPDX-License-Identifier: GPL-2.0+', content)
|
||||
self.assertIn('test-device {', content)
|
||||
self.assertIn('compatible = "test,example-device";', content)
|
||||
self.assertIn('manufacturer = "LENOVO";', content)
|
||||
self.assertIn('product-name = "21BXCTO1WW";', content)
|
||||
self.assertIn('bios-major-release = <1>;', content)
|
||||
self.assertIn('enclosure-kind = <0xa>;', content)
|
||||
self.assertIn('// Hardware IDs (CHIDs)', content)
|
||||
self.assertIn('hardware-id-00 {', content)
|
||||
self.assertIn('variant = <0>;', content)
|
||||
self.assertIn('fields = <0x3cf>;', content)
|
||||
self.assertIn(
|
||||
'chid = [81 0e 34 c6 cc 69 5e 36 86 75 2f 6e 35 42 72 d3];',
|
||||
content)
|
||||
|
||||
def test_invalid_guid_format(self):
|
||||
"""Test error handling for invalid GUID format"""
|
||||
with self.assertRaises(ValueError):
|
||||
uuid.UUID('invalid-guid-format')
|
||||
|
||||
def test_missing_compatible_map(self):
|
||||
"""Test behavior when compatible-map file is missing"""
|
||||
with tempfile.TemporaryDirectory() as tmpdir:
|
||||
compatible_map = load_compatible_map(tmpdir)
|
||||
self.assertEqual(compatible_map, {})
|
||||
|
||||
def test_enclosure_kind_conversion(self):
|
||||
"""Test enclosure kind hex conversion"""
|
||||
info = {'EnclosureKind': 'a'}
|
||||
hardware_ids = []
|
||||
|
||||
content = generate_dtsi('test', 'test,device', info, hardware_ids)
|
||||
self.assertIn('enclosure-kind = <0xa>;', content)
|
||||
|
||||
# Test numeric enclosure kind ('10' is interpreted as hex 0x10)
|
||||
info = {'EnclosureKind': '10'}
|
||||
content = generate_dtsi('test', 'test,device', info, hardware_ids)
|
||||
self.assertIn('enclosure-kind = <0x10>;', content)
|
||||
|
||||
def test_empty_hardware_ids(self):
|
||||
"""Test handling of empty hardware IDs list"""
|
||||
info = {'Manufacturer': 'TEST'}
|
||||
hardware_ids = []
|
||||
|
||||
content = generate_dtsi('test', 'test,device', info, hardware_ids)
|
||||
self.assertIn('// Hardware IDs (CHIDs)', content)
|
||||
|
||||
# Should have no hardware-id-XX or extra-X nodes
|
||||
self.assertNotIn('hardware-id-', content)
|
||||
self.assertNotIn('extra-', content)
|
||||
|
||||
def test_parse_variant_from_field_description(self):
|
||||
"""Test parsing variant ID from field descriptions"""
|
||||
# Test variant 0 - most specific
|
||||
desc = ('Manufacturer + Family + ProductName + ProductSku + '
|
||||
'BiosVendor + BiosVersion + BiosMajorRelease + '
|
||||
'BiosMinorRelease')
|
||||
variant_id, fields_mask = parse_variant_description(desc)
|
||||
self.assertEqual(variant_id, 0)
|
||||
self.assertEqual(fields_mask, 0x3cf)
|
||||
|
||||
# Test variant 14 - least specific (manufacturer only)
|
||||
desc = 'Manufacturer'
|
||||
variant_id, fields_mask = parse_variant_description(desc)
|
||||
self.assertEqual(variant_id, 14)
|
||||
self.assertEqual(fields_mask, 0x1)
|
||||
|
||||
# Test variant 5 - manufacturer, family, product name
|
||||
desc = 'Manufacturer + Family + ProductName'
|
||||
variant_id, fields_mask = parse_variant_description(desc)
|
||||
self.assertEqual(variant_id, 5)
|
||||
self.assertEqual(fields_mask, 0x7)
|
||||
|
||||
def test_constants_usage(self):
|
||||
"""Test that magic number constants are used correctly"""
|
||||
# Test GUID_LENGTH constant in regex pattern
|
||||
test_guid = '12345678-1234-5678-9abc-123456789abc'
|
||||
content = f'''Computer Information
|
||||
--------------------
|
||||
Manufacturer: Test
|
||||
|
||||
Hardware IDs
|
||||
------------
|
||||
{{{test_guid}}} <- Manufacturer
|
||||
'''
|
||||
with tempfile.NamedTemporaryFile(mode='w', suffix='.txt', delete=False) as f:
|
||||
f.write(content)
|
||||
f.flush()
|
||||
try:
|
||||
_info, hardware_ids = parse_hwids_file(f.name)
|
||||
# Should successfully parse the GUID
|
||||
self.assertEqual(len(hardware_ids), 1)
|
||||
self.assertEqual(hardware_ids[0][0], test_guid)
|
||||
finally:
|
||||
os.unlink(f.name)
|
||||
|
||||
def test_version_fields_constants(self):
|
||||
"""Test that VERSION_FIELDS constant is used correctly"""
|
||||
|
||||
# Test that all expected version fields are in the constant
|
||||
expected_fields = {'BiosMajorRelease', 'BiosMinorRelease',
|
||||
'FirmwareMajorRelease', 'FirmwareMinorRelease'}
|
||||
self.assertTrue(expected_fields.issubset(VERSION_FIELDS))
|
||||
|
||||
# Test HEX_ENCLOSURE_FIELD constant
|
||||
self.assertEqual(HEX_ENCLOSURE_FIELD, 'EnclosureKind')
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
||||
Reference in New Issue
Block a user