]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
ukify: Support building UKIs with a .hwids section 34158/head
authoranonymix007 <48598263+anonymix007@users.noreply.github.com>
Sat, 31 Aug 2024 12:51:42 +0000 (15:51 +0300)
committeranonymix007 <48598263+anonymix007@users.noreply.github.com>
Thu, 14 Nov 2024 13:42:42 +0000 (16:42 +0300)
This section contains a predefined set of HWIDs and the corresponding compatibles to be used in dtb matching

src/ukify/ukify.py

index 69904df6d2fdb48ec226a6015b89a8c71ecd88f7..5ea52898ee7bd746a64957107a7d610672cb0e0d 100755 (executable)
@@ -240,6 +240,7 @@ class UkifyConfig:
     devicetree: Path
     devicetree_auto: list[Path]
     efi_arch: str
+    hwids: Path
     initrd: list[Path]
     join_profiles: list[Path]
     json: Union[Literal['pretty'], Literal['short'], Literal['off']]
@@ -367,6 +368,7 @@ DEFAULT_SECTIONS_TO_SHOW = {
     '.splash':  'binary',
     '.dtb':     'binary',
     '.dtbauto': 'binary',
+    '.hwids':   'binary',
     '.cmdline': 'text',
     '.osrel':   'text',
     '.uname':   'text',
@@ -622,8 +624,11 @@ def check_inputs(opts: UkifyConfig) -> None:
             continue
 
         if isinstance(value, Path):
-            # Open file to check that we can read it, or generate an exception
-            value.open().close()
+            # Check that we can open the directory or file, or generate and exception
+            if value.is_dir():
+                value.iterdir()
+            else:
+                value.open().close()
         elif isinstance(value, list):
             for item in value:
                 if isinstance(item, Path):
@@ -997,6 +1002,111 @@ def merge_sbat(input_pe: list[Path], input_text: list[str]) -> str:
     )
 
 
+# Keep in sync with EFI_GUID (src/boot/efi/efi.h)
+# uint32_t Data1, uint16_t Data2, uint16_t Data3, uint8_t Data4[8]
+EFI_GUID = tuple[int, int, int, tuple[int, int, int, int, int, int, int, int]]
+EFI_GUID_STRUCT_SIZE = 4 + 2 + 2 + 1 * 8
+
+# Keep in sync with Device (src/boot/efi/chid.h)
+# uint32_t struct_size, uint32_t name_offset, uint32_t compatible_offset, EFI_GUID chid
+DEVICE_STRUCT_SIZE = 4 + 4 + 4 + EFI_GUID_STRUCT_SIZE
+NULL_DEVICE = b'\0' * DEVICE_STRUCT_SIZE
+
+
+def pack_device(offsets: dict[str, int], name: str, compatible: str, chids: list[EFI_GUID]) -> bytes:
+    data = b''
+
+    for data1, data2, data3, data4 in chids:
+        data += struct.pack(
+            '<IIIIHH8B', DEVICE_STRUCT_SIZE, offsets[name], offsets[compatible], data1, data2, data3, *data4
+        )
+
+    assert len(data) == DEVICE_STRUCT_SIZE * len(chids)
+    return data
+
+
+def hex_pairs_list(string: str) -> list[int]:
+    return [int(string[i : i + 2], 16) for i in range(0, len(string), 2)]
+
+
+def pack_strings(strings: set[str], base: int) -> tuple[bytes, dict[str, int]]:
+    blob = b''
+    offsets = {}
+
+    for string in sorted(strings):
+        offsets[string] = base + len(blob)
+        blob += string.encode('utf-8') + b'\00'
+
+    return (blob, offsets)
+
+
+def parse_hwid_dir(path: Path) -> bytes:
+    hwid_files = path.rglob('*.txt')
+
+    strings: set[str] = set()
+    devices: collections.defaultdict[tuple[str, str], list[EFI_GUID]] = collections.defaultdict(list)
+
+    uuid_regexp = re.compile(
+        r'\{[0-9a-f]{8}-[0-9a-f]{4}-[0-5][0-9a-f]{3}-[089ab][0-9a-f]{3}-[0-9a-f]{12}\}', re.I
+    )
+
+    for hwid_file in hwid_files:
+        content = hwid_file.open().readlines()
+
+        data: dict[str, str] = {
+            'Manufacturer': '',
+            'Family': '',
+            'Compatible': '',
+        }
+        uuids: list[EFI_GUID] = []
+
+        for line in content:
+            for k in data:
+                if line.startswith(k):
+                    data[k] = line.split(':')[1].strip()
+                    break
+            else:
+                uuid = uuid_regexp.match(line)
+                if uuid is not None:
+                    d1, d2, d3, d4, d5 = uuid.group(0)[1:-1].split('-')
+
+                    data1 = int(d1, 16)
+                    data2 = int(d2, 16)
+                    data3 = int(d3, 16)
+                    data4 = cast(
+                        tuple[int, int, int, int, int, int, int, int],
+                        tuple(hex_pairs_list(d4) + hex_pairs_list(d5)),
+                    )
+
+                    uuids.append((data1, data2, data3, data4))
+
+        for k, v in data.items():
+            if not v:
+                raise ValueError(f'hwid description file "{hwid_file}" does not contain "{k}"')
+
+        name = data['Manufacturer'] + ' ' + data['Family']
+        compatible = data['Compatible']
+
+        strings |= set([name, compatible])
+
+        # (compatible, name) pair uniquely identifies the device
+        devices[(compatible, name)] += uuids
+
+    total_device_structs = 1
+    for dev, uuids in devices.items():
+        total_device_structs += len(uuids)
+
+    strings_blob, offsets = pack_strings(strings, total_device_structs * DEVICE_STRUCT_SIZE)
+
+    devices_blob = b''
+    for (compatible, name), uuids in devices.items():
+        devices_blob += pack_device(offsets, name, compatible, uuids)
+
+    devices_blob += NULL_DEVICE
+
+    return devices_blob + strings_blob
+
+
 STUB_SBAT = """\
 sbat,1,SBAT Version,sbat,1,https://github.com/rhboot/shim/blob/main/SBAT.md
 uki,1,UKI,uki,1,https://uapi-group.org/specifications/specs/unified_kernel_image/
@@ -1063,12 +1173,18 @@ def make_uki(opts: UkifyConfig) -> None:
             print('+', shell_join(cmd))
             pcrpkey = subprocess.check_output(cmd)
 
+    hwids = None
+
+    if opts.hwids is not None:
+        hwids = parse_hwid_dir(opts.hwids)
+
     sections = [
         # name,      content,         measure?
         ('.osrel',   opts.os_release, True),
         ('.cmdline', opts.cmdline,    True),
         ('.dtb',     opts.devicetree, True),
         *(('.dtbauto', dtb, True) for dtb in opts.devicetree_auto),
+        ('.hwids',   hwids,           True),
         ('.uname',   opts.uname,      True),
         ('.splash',  opts.splash,     True),
         ('.pcrpkey', pcrpkey,         True),
@@ -1638,6 +1754,13 @@ CONFIG_ITEMS = [
         config_key='UKI/DeviceTreeAuto',
         config_push=ConfigItem.config_list_prepend,
     ),
+    ConfigItem(
+        '--hwids',
+        metavar='DIR',
+        type=Path,
+        help='Directory with HWID text files [.hwids section]',
+        config_key='UKI/HWIDs',
+    ),
     ConfigItem(
         '--uname',
         metavar='VERSION',