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>
307 lines
13 KiB
Python
307 lines
13 KiB
Python
#!/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()
|