blob: 834a25056614a418c849da432b918cd740ee2d38 [file] [log] [blame]
#!/usr/bin/env python3
#
# Copyright 2024 Richard Hughes <richard@hughsie.com>
#
# 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(
"<I", blob_ifd, offset=flash_region_base_addr + (region * 4)
)[0]
)
for region in range(num_regions):
print(f"flash_descriptor_reg{region}=0x{flash_descriptor_regs[region]:X}")
# parse each partition
fregs: List[IfdPartition] = []
for i in range(num_regions):
freg_base: int = (flash_descriptor_regs[i] << 12) & 0x07FFF000
freg_limit: int = ((flash_descriptor_regs[i] >> 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)