Last active
November 9, 2025 16:09
-
-
Save N1kroks/0b3942a951a2d4504efe82ab82bc7a50 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| import argparse | |
| import re | |
| import typing | |
| from pyfdt.pyfdt import FdtBlobParse, Fdt, FdtNode, FdtProperty | |
| CONFIG_PARAMETERS_BLACKLIST = [ | |
| "ConfigParameterCount", | |
| "MaxCount", | |
| "StrMaxCount" | |
| ] | |
| MEMORY_MAP_DTB_PATH = "/soc/memorymap/" | |
| REG_MAP_DTB_PATH = "/soc/registermap/" | |
| CONFIG_MAP_DTB_PATH = "/sw/uefi/" | |
| HOB_DICT = { | |
| 0x0: "AddMem", | |
| 0x4: "NoHob", | |
| 0x5: "AddDev", | |
| 0x9: "NoMap", | |
| 0xA: "AddMem" # AddDynamicMem | |
| } | |
| RESOURCE_TYPE_DICT = { | |
| 0x0: "SYS_MEM", | |
| 0x1: "MMAP_IO", | |
| 0x5: "MEM_RES" | |
| } | |
| RESOURCE_ATTRIBUTE_DICT = { | |
| 0x02: "INITIALIZED", | |
| 0x400: "UNCACHEABLE", | |
| 0x703C07: "SYS_MEM_CAP" | |
| } | |
| MEMORY_TYPE_DICT = { | |
| 0x0: "Reserv", | |
| 0x4: "BsData", | |
| 0x7: "Conv", | |
| 0xB: "MmIO" | |
| } | |
| CACHE_ATTRIBUTE_DICT = { | |
| 0x02: "WRITE_BACK", | |
| 0x09: "NS_DEVICE", | |
| 0x20: "WRITE_THROUGH_XN", | |
| 0x22: "WRITE_BACK_XN", | |
| 0x25: "UNCACHED_UNBUFFERED_XN" | |
| } | |
| def ParseArgs(): | |
| parser = argparse.ArgumentParser() | |
| parser.add_argument('files', nargs="+") | |
| parser.add_argument('-i', '--ignore-gaps', action='store_true') | |
| return parser.parse_args() | |
| class MemoryRegion: | |
| def __init__(self, name: str, space: int, base: int, size: int, hob_option: str, resource_attribute: str, arm_attribute: str, resource_type: str, cache_type: str): | |
| self.name = name | |
| self.space = space | |
| self.base = base | |
| self.size = size | |
| self.hob_option = hob_option | |
| self.resource_attribute = resource_attribute | |
| self.arm_attribute = arm_attribute | |
| self.resource_type = resource_type | |
| self.cache_type = cache_type | |
| def __str__(self) -> str: | |
| return "{{{},{}0x{:08X}, 0x{:08X}, {}, {}, {}, {}, {}}},".format(self.name, " " * self.space, self.base, self.size, self.hob_option, self.resource_attribute, self.arm_attribute, self.resource_type, self.cache_type) | |
| class ConfigurationParameter: | |
| def __init__(self, name: str, value: int): | |
| self.name = name | |
| self.value = value | |
| def __str__(self) -> str: | |
| return "{{\"{}\", 0x{:0x}}},".format(self.name, self.value) | |
| def ParseLineToMemoryRegion(line: str) -> MemoryRegion: | |
| if line[-1] == "\n": | |
| line = line[0:-1] | |
| base, size, name, hob, resource_attribute, arm_attribute, resource_type, cache_type = line.split(',') | |
| while name[0] == " ": | |
| name = name[1:] | |
| while resource_attribute[0] == " ": | |
| resource_attribute = resource_attribute[1:] | |
| while arm_attribute[0] == " ": | |
| arm_attribute = arm_attribute[1:] | |
| while resource_type[0] == " ": | |
| resource_type = resource_type[1:] | |
| while cache_type[0] == " ": | |
| cache_type = cache_type[1:] | |
| space_count = hob.count(' ') | |
| hob = hob[(space_count):] | |
| return MemoryRegion(name, space_count, int(base, 16), int(size, 16), hob, resource_attribute, arm_attribute, resource_type, cache_type) | |
| # Needed because SoCs newer than sm8650 use snake_case named props instead of CamelCase | |
| def camel_to_snake(name: str) -> str: | |
| s1 = re.sub('(.)([A-Z][a-z]+)', r'\1-\2', name) | |
| return re.sub('([a-z0-9])([A-Z])', r'\1-\2', s1).lower() | |
| def GetMemoryNodeProp(dtb: Fdt, path: str, name: str) -> FdtProperty: | |
| prop = dtb.resolve_path(path + name) | |
| if prop is None: | |
| prop = dtb.resolve_path(path + camel_to_snake(name)) | |
| return prop | |
| def MemoryNodeToMemoryRegion(dtb: Fdt, path: str, addr_cells: int, size_cells: int) -> MemoryRegion: | |
| reg = GetMemoryNodeProp(dtb, path, "reg") | |
| base = reg[0] if addr_cells == 1 else (reg[0] << 32) + reg[1] | |
| size = reg[1] if size_cells == 1 else (reg[2] << 32) + reg[3] | |
| name = GetMemoryNodeProp(dtb, path, "MemLabel")[0] | |
| hob = HOB_DICT.get(GetMemoryNodeProp(dtb, path, "BuildHob")[0], "UNK") | |
| resource_type = RESOURCE_TYPE_DICT.get(GetMemoryNodeProp(dtb, path, "ResourceType")[0], "UNK") | |
| resource_attribute = RESOURCE_ATTRIBUTE_DICT.get(GetMemoryNodeProp(dtb, path, "ResourceAttribute")[0], "UNK") | |
| memory_type = MEMORY_TYPE_DICT.get(GetMemoryNodeProp(dtb, path, "MemoryType")[0], "UNK") | |
| cache_attribute = CACHE_ATTRIBUTE_DICT.get(GetMemoryNodeProp(dtb, path, "CacheAttributes")[0], "UNK") | |
| if name == "NOMAP": | |
| return None | |
| return MemoryRegion("\"{}\"".format(name), 18 - len(name), base, size, hob, resource_type, resource_attribute, memory_type, cache_attribute) | |
| def GapChecker(regions: list[MemoryRegion]) -> list[MemoryRegion]: | |
| if len(regions) == 0: | |
| return regions | |
| result = regions.copy() | |
| descriptors = regions.copy() | |
| last_desc = descriptors[0] | |
| descriptors.pop(0) | |
| for desc in descriptors: | |
| if last_desc.base + last_desc.size < desc.base: | |
| ram_region = MemoryRegion("\"RAM Partition\"", 5, "0x0", "0x0", "AddMem", "SYS_MEM", "SYS_MEM_CAP", "Conv", "WRITE_BACK_XN") | |
| ram_region.base = last_desc.base + last_desc.size | |
| ram_region.size = desc.base - (last_desc.base + last_desc.size) | |
| if ram_region.size: | |
| result.append(ram_region) | |
| last_desc = desc | |
| result.sort(key=lambda r: r.base) | |
| return result | |
| def ParseLineToConfig(line: str) -> str: | |
| if line[-1] == "\n": | |
| line = line[0:-1] | |
| name, value = line.split(' = ') | |
| while name[-1] == " ": | |
| name = name[:-1] | |
| while value[-1] == " ": | |
| value = value[:-1] | |
| if value[0] == '\"': | |
| return None | |
| if name in CONFIG_PARAMETERS_BLACKLIST: | |
| return None | |
| return ConfigurationParameter(name, int(value, 0)) | |
| def ParseMapToRegions(file: typing.TextIO) -> typing.Tuple[str, list[MemoryRegion]]: | |
| regions = [] | |
| while True: | |
| this_line = file.readline() | |
| if this_line == '' or this_line[0] == '[': | |
| break | |
| if this_line[0] == '\n' or this_line[0] == '#' and this_line[0:2] != '#-': | |
| continue | |
| if this_line[0] == "#": | |
| continue | |
| while this_line[-1] == " " or this_line[-1] == "\n": | |
| this_line = this_line[:-1] | |
| regions.append(ParseLineToMemoryRegion(this_line)) | |
| return this_line, regions | |
| def Main(files: list[str], ignore_gaps: bool): | |
| this_line = None | |
| uefi_plat = None | |
| dtb = None | |
| for filename in files: | |
| if filename.endswith(".dtb"): | |
| dtb = FdtBlobParse(open(filename, "rb")).to_fdt() | |
| elif filename.endswith(".cfg"): | |
| uefi_plat = open(filename, "r") | |
| else: | |
| print("Unknown file") | |
| return | |
| ddr_regions = [] | |
| reg_regions = [] | |
| config_params = [] | |
| if uefi_plat is not None: | |
| while True: | |
| this_line = uefi_plat.readline() | |
| if this_line == '': | |
| break | |
| if this_line[0] == "#": | |
| continue | |
| if this_line == "[MemoryMap]\n": | |
| this_line, ddr_regions = ParseMapToRegions(uefi_plat) | |
| if this_line == "[RegisterMap]\n": | |
| this_line, reg_regions = ParseMapToRegions(uefi_plat) | |
| if this_line == "[ConfigParameters]\n": | |
| while True: | |
| this_line = uefi_plat.readline() | |
| if this_line == '' or this_line[0] == '[': | |
| break | |
| if this_line[0] == '\n' or this_line[0] == '#' and this_line[0:2] != '#-': | |
| continue | |
| while this_line[-1] == " " or this_line[-1] == "\n": | |
| this_line = this_line[:-1] | |
| config = ParseLineToConfig(this_line) | |
| if config is None: | |
| continue | |
| config_params.append(config) | |
| if len(ddr_regions) == 0 and len(reg_regions) == 0: | |
| if dtb == None: | |
| print("Provided UEFI Plat does not have 'MemoryMap' and 'RegisterMap', if SoC is newer than sm8550 they are in XBLConfig, so you need to provide dtb extracted from XBLConfig") | |
| else: | |
| addr_cells = dtb.resolve_path(MEMORY_MAP_DTB_PATH + "#address-cells")[0] | |
| size_cells = dtb.resolve_path(MEMORY_MAP_DTB_PATH + "#size-cells")[0] | |
| nodes = dtb.resolve_path(MEMORY_MAP_DTB_PATH) | |
| for node in nodes: | |
| if isinstance(node, FdtNode): | |
| region = MemoryNodeToMemoryRegion(dtb, MEMORY_MAP_DTB_PATH + node.name + "/", addr_cells, size_cells, ) | |
| if region is None: | |
| continue | |
| ddr_regions.append(region) | |
| addr_cells = dtb.resolve_path(REG_MAP_DTB_PATH + "#address-cells")[0] | |
| size_cells = dtb.resolve_path(REG_MAP_DTB_PATH + "#size-cells")[0] | |
| nodes = dtb.resolve_path(REG_MAP_DTB_PATH) | |
| for node in nodes: | |
| if isinstance(node, FdtNode): | |
| region = MemoryNodeToMemoryRegion(dtb, REG_MAP_DTB_PATH + node.name + "/", addr_cells, size_cells) | |
| if region is None: | |
| continue | |
| reg_regions.append(region) | |
| if len(config_params) == 0: | |
| if dtb == None: | |
| print("Provided UEFI Plat does not have 'ConfigParameters', if SoC is newer than sm8650 they are in XBLConfig, so you need to provide dtb extracted from XBLConfig") | |
| else: | |
| configs = dtb.resolve_path(CONFIG_MAP_DTB_PATH + "int_param") | |
| if isinstance(configs, FdtNode): | |
| for c in configs: | |
| if isinstance(c, FdtProperty): | |
| if c.name in CONFIG_PARAMETERS_BLACKLIST: | |
| continue | |
| config_params.append(ConfigurationParameter(c.name, c[0] << 32 | c[1])) | |
| else: | |
| print("Provided DTB Does not have 'int_param' node, if SoC is older than sm8750 they are in UEFI Plat config, so you need to provide UEFI Plat config extracted from XBL") | |
| # Sort regions by base | |
| ddr_regions.sort(key=lambda r: r.base) | |
| if ignore_gaps != True: | |
| ddr_regions = GapChecker(ddr_regions) | |
| if len(ddr_regions) > 0: | |
| print("// DDR Regions") | |
| for region in ddr_regions: | |
| print(region) | |
| if len(reg_regions) > 0: | |
| print("\n// Register Regions") | |
| for region in reg_regions: | |
| print(region) | |
| if len(config_params) > 0: | |
| print("\n// Configuration Map") | |
| for config in config_params: | |
| print(config) | |
| if len(ddr_regions) > 0: | |
| print("\nDynamic RAM Start Address: 0x{:08X}".format(ddr_regions[-1].base + ddr_regions[-1].size)) | |
| if uefi_plat is not None: | |
| uefi_plat.close() | |
| if __name__ == '__main__': | |
| Main(**vars(ParseArgs())) |
Author
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
It will print memory map and configuration map
Examples: