[elbe-devel] [PATCH 2/4] elbepack: introduce qemu_firmware package
Thomas Weißschuh
thomas.weissschuh at linutronix.de
Wed Mar 6 10:53:58 CET 2024
Firmware binaries can be installed in various system-dependent
locations.
Qemu provides a standardized JSON-based database to query for installed
firmware images providing various features.
This new module provide an interface to this database.
Signed-off-by: Thomas Weißschuh <thomas.weissschuh at linutronix.de>
---
elbepack/qemu_firmware.py | 239 ++++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 239 insertions(+)
diff --git a/elbepack/qemu_firmware.py b/elbepack/qemu_firmware.py
new file mode 100755
index 000000000000..f747ee19aa48
--- /dev/null
+++ b/elbepack/qemu_firmware.py
@@ -0,0 +1,239 @@
+#!/usr/bin/env python3
+
+import dataclasses
+import enum
+import fnmatch
+import json
+import os
+import pathlib
+import typing
+
+
+ at dataclasses.dataclass
+class FirmwareTarget:
+ architecture: str
+ machines: list[str]
+
+ @classmethod
+ def from_json(cls, json):
+ return cls(
+ architecture=json['architecture'],
+ machines=json['machines'],
+ )
+
+
+class FirmwareFlashMode(enum.StrEnum):
+ SPLIT = 'split'
+ COMBINED = 'combined'
+ STATELESS = 'stateless'
+
+
+ at dataclasses.dataclass
+class FirmwareFlashFile:
+ filename: str
+ format: str
+
+ @classmethod
+ def from_json(cls, json):
+ return cls(
+ filename=json['filename'],
+ format=json['format'],
+ )
+
+
+ at dataclasses.dataclass
+class FirmwareMappingFlash:
+ mode: FirmwareFlashMode
+ executable: FirmwareFlashFile
+ nvram_template: typing.Optional[FirmwareFlashFile]
+
+ @classmethod
+ def from_json(cls, json):
+ return cls(
+ mode=FirmwareFlashMode(json.get('mode', FirmwareFlashMode.COMBINED)),
+ executable=FirmwareFlashFile.from_json(json['executable']),
+ nvram_template=FirmwareFlashFile.from_json(json['nvram-template'])
+ if 'nvram-template' in json
+ else None,
+ )
+
+
+ at dataclasses.dataclass
+class FirmwareMappingMemory:
+ filename: str
+
+ @classmethod
+ def from_json(cls, json):
+ return cls(filename=json['filename'])
+
+
+ at dataclasses.dataclass
+class FirmwareMapping:
+ device: str
+
+ @classmethod
+ def from_json(cls, json):
+ if json['device'] == 'flash':
+ return FirmwareMappingFlash.from_json(json)
+ if json['device'] == 'memory':
+ return FirmwareMappingMemory.from_json(json)
+ raise ValueError(json)
+
+
+ at dataclasses.dataclass
+class Firmware:
+ description: str
+ interface_types: list[str]
+ features: list[str]
+ tags: list[str]
+ targets: list[FirmwareTarget]
+ mapping: FirmwareMapping
+ json_path: typing.Optional[pathlib.Path] = None
+
+ @classmethod
+ def from_json(cls, json):
+ return cls(
+ description=json['description'],
+ interface_types=json['interface-types'],
+ features=json['features'],
+ tags=json['tags'],
+ targets=[FirmwareTarget.from_json(j) for j in json['targets']],
+ mapping=FirmwareMapping.from_json(json['mapping']),
+ )
+
+
+ at dataclasses.dataclass
+class FeatureMatcher:
+ required_values: set[str]
+ forbidden_values: set[str]
+
+ @classmethod
+ def from_string(cls, s):
+ required_values = []
+ forbidden_values = []
+
+ for value in s.split(' '):
+ value = value.strip()
+ if value.startswith('!'):
+ forbidden_values.append(value[1:])
+ else:
+ required_values.append(value)
+
+ return cls(required_values=set(required_values),
+ forbidden_values=set(forbidden_values))
+
+ def is_satisfied_by(self, available_values):
+ if not set(self.required_values).issubset(available_values):
+ return False
+
+ if not set(self.forbidden_values).isdisjoint(available_values):
+ return False
+
+ return True
+
+
+ at dataclasses.dataclass
+class SearchRequest:
+ architecture: str
+ machine: str
+ interface_types: FeatureMatcher
+ features: FeatureMatcher
+
+ def _matches_target(self, target):
+ if self.architecture != target.architecture:
+ return False
+
+ for machine in target.machines:
+ if fnmatch.fnmatch(self.machine, machine):
+ return True
+
+ return False
+
+ def matches(self, firmware):
+ if not self.interface_types.is_satisfied_by(firmware.interface_types):
+ return False
+
+ if not self.features.is_satisfied_by(firmware.features):
+ return False
+
+ if not any([self._matches_target(target) for target in firmware.targets]):
+ return False
+
+ return True
+
+
+class FirmwareSearcher:
+ def __init__(self):
+ self.search_dirs = self._get_search_dirs()
+ self.filenames = self._get_filenames(self.search_dirs)
+
+ @staticmethod
+ def _get_search_dirs() -> list[pathlib.Path]:
+ search_dirs = []
+
+ xdg_config_home = os.environ.get('XDG_CONFIG_HOME')
+ if xdg_config_home is not None:
+ search_dirs.append(
+ pathlib.Path(xdg_config_home.joinpath('qemu', 'firmware'))
+ )
+ else:
+ search_dirs.append(
+ pathlib.Path.home().joinpath('.config', 'qemu', 'firmware')
+ )
+
+ search_dirs.append(pathlib.Path('/etc/qemu/firmware'))
+
+ for d in os.environ.get('XDG_DATA_DIRS', '/usr/local/share/:/usr/share/').split(
+ ':'
+ ):
+ search_dirs.append(pathlib.Path(d).joinpath('qemu', 'firmware'))
+
+ return search_dirs
+
+ @staticmethod
+ def _get_filenames(search_dirs: list[pathlib.Path]) -> list[str]:
+ filenames = set()
+
+ for search_dir in search_dirs:
+ if not search_dir.exists() or not search_dir.is_dir():
+ continue
+
+ for entry in search_dir.iterdir():
+ filenames.add(entry.name)
+
+ return sorted(filenames)
+
+ def _search_for_filename(self, filename, request):
+ for search_dir in self.search_dirs:
+ path = search_dir.joinpath(filename)
+
+ if not path.exists():
+ continue
+
+ if path.stat().st_size == 0:
+ return
+
+ with path.open('r') as f:
+ j = json.load(f)
+
+ fw = Firmware.from_json(j)
+ if request.matches(fw):
+ fw.json_path = path
+ return fw
+
+ def search(self, request):
+ for filename in self.filenames:
+ fw = self._search_for_filename(filename, request)
+ if fw is not None:
+ return fw
+
+
+if __name__ == '__main__':
+ searcher = FirmwareSearcher()
+ request = SearchRequest(
+ architecture='x86_64',
+ machine='pc-q35-foo',
+ interface_types=FeatureMatcher.from_string('uefi !bios'),
+ features=FeatureMatcher.from_string('!requires-smm'),
+ )
+ print(searcher.search(request))
--
2.44.0
More information about the elbe-devel
mailing list