#!/usr/bin/python3 # # Copyright 2024 Richard Hughes # # SPDX-License-Identifier: LGPL-2.1-or-later # # pylint: disable=invalid-name,missing-docstring,too-few-public-methods,too-many-locals import sys import os import struct import subprocess import io from typing import List, Optional import argparse class IfdPartition: REGION_NAMES = [ "desc", "bios", "me", "gbe", "platform", "devexp", "bios2", "ec", "ie", "10gbe", ] def __init__(self, region: int = 0, offset: int = 0, size: int = 0) -> None: self.region: int = region self.offset: int = offset self.size: int = size def __str__(self) -> str: try: region_name: str = IfdPartition.REGION_NAMES[self.region] except IndexError: region_name = "unknown" return ( "IfdPartition(" f"region=0x{self.region} ({region_name}), " f"offset=0x{self.offset:x}, " f"size=0x{self.size:x})" ) def _read_partitions(f: io.BufferedReader) -> bytearray: f.seek(0) blob_ifd: bytes = f.read(0x1000) ( signature, descriptor_map0, descriptor_map1, descriptor_map2, ) = struct.unpack_from("<16xIIII", blob_ifd, offset=0) if signature != 0x0FF0A55A: sys.exit(f"Not IFD signature 0x0FF0A55A, got 0x{signature:X}") # read out the descriptor maps print(f"descriptor_map0=0x{descriptor_map0:X}") print(f"descriptor_map1=0x{descriptor_map1:X}") print(f"descriptor_map2=0x{descriptor_map2:X}") # default to all partitions num_regions = (descriptor_map0 >> 24) & 0b111 if num_regions == 0: num_regions = 10 print(f"num_regions=0x{num_regions:X}") # read out FREGs flash_region_base_addr = (descriptor_map0 >> 12) & 0x00000FF0 print(f"flash_region_base_addr=0x{flash_region_base_addr:X}") flash_descriptor_regs: List[int] = [] for region in range(num_regions): flash_descriptor_regs.append( struct.unpack_from( "> 4) & 0x07FFF000) | 0x00000FFF # invalid if freg_base > freg_limit: continue fregs.append( IfdPartition(region=i, offset=freg_base, size=freg_limit - freg_base) ) # create a binary blob big enough image_size: int = 0 for freg in fregs: if freg.offset + freg.size > image_size: image_size = freg.offset + freg.size print(f"image_size=0x{image_size:x}") blob: bytearray = bytearray(image_size) # copy each partition for freg in fregs: print("reading...", freg) try: f.seek(freg.offset) blob_part: bytes = f.read(freg.size) blob_size: int = len(blob_part) if blob_size != freg.size: print(f"tried to read 0x{freg.size:x} and instead got 0x{blob_size:x}") if blob_size: blob[freg.offset : freg.offset + blob_size] = blob_part except OSError as e: print(f"failed to read: {e}") return blob def _read_device_to_file(devname: str, filename: Optional[str]) -> None: # grab system info from sysfs if not filename: filename = "" for sysfs_fn in [ "/sys/class/dmi/id/sys_vendor", "/sys/class/dmi/id/product_family", "/sys/class/dmi/id/product_name", "/sys/class/dmi/id/product_sku", ]: try: with open(sysfs_fn, "rb") as f: if filename: filename += "-" filename += ( f.read().decode().replace("\n", "").replace(" ", "_").lower() ) except FileNotFoundError: pass if filename: filename += ".bin" else: filename = "bios.bin" # check this device name is what we expect print(f"checking {devname}...") try: with open(f"/sys/class/mtd/{os.path.basename(devname)}/name", "rb") as f_name: name = f_name.read().decode().replace("\n", "") except FileNotFoundError as e: sys.exit(str(e)) if name != "BIOS": sys.exit(f"Not Intel Corporation PCH SPI Controller, got {name}") # read the IFD header, then each partition try: with open(devname, "rb") as f_in: print(f"reading from {devname}...") blob = _read_partitions(f_in) except PermissionError as e: sys.exit(f"cannot read mtd device: {e}") print(f"writing {filename}...") with open(filename, "wb") as f_out: f_out.write(blob) # this is really helpful for debugging print(f"getting additional data from {devname}...") try: p = subprocess.run(["mtdinfo", devname], check=True, capture_output=True) except subprocess.CalledProcessError as e: print(f"{' '.join(args)}: {e}") else: for line in p.stdout.decode().split("\n"): if not line: continue print(line) print("done!") if __name__ == "__main__": # both have defaults parser = argparse.ArgumentParser( prog="dump-mtd-ifd", description="Dump local SPI contents using MTD" ) parser.add_argument( "--filename", action="store", type=str, help="Output filename", default=None, ) parser.add_argument( "--devname", action="store", type=str, help="Device name, e.g. /dev/mtd0", default="/dev/mtd0", ) args = parser.parse_args() _read_device_to_file(args.devname, args.filename)