You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
linux-firmware/SOURCES/amd_ucode_info.py

375 lines
13 KiB

#!/usr/bin/python3
# SPDX-License-Identifier: MIT License
# Copyright (C) 2020 Advanced Micro Devices, Inc.
"""
Parse an amd-ucode container file and print the family, model, stepping number,
and patch level for each patch in the file. The --extract option will dump the
raw microcode patches to a provided directory.
"""
import argparse
import sys
import os
from collections import namedtuple
from collections import OrderedDict
EQ_TABLE_ENTRY_SIZE = 16
EQ_TABLE_LEN_OFFSET = 8
EQ_TABLE_OFFSET = 12
EQ_TABLE_TYPE = 0
PATCH_TYPE = 1
VERBOSE_DEBUG = 2
FMS = namedtuple("FMS", ("family", "model", "stepping"))
EquivTableEntry = namedtuple("EquivTableEntry", ("cpuid", "equiv_id", "data", "offset"))
PatchEntry = namedtuple("PatchEntry", ("file", "offset", "size", "equiv_id", "level"))
def read_int32(ucode_file):
""" Read four bytes of binary data and return as a 32 bit int """
return int.from_bytes(ucode_file.read(4), 'little')
def read_int16(ucode_file):
""" Read two bytes of binary data and return as a 16 bit int """
return int.from_bytes(ucode_file.read(2), 'little')
def read_int8(ucode_file):
""" Read one byte of binary data and return as a 8 bit int """
return int.from_bytes(ucode_file.read(1), 'little')
def cpuid2fms(cpu_id):
family = (cpu_id >> 8) & 0xf
family += (cpu_id >> 20) & 0xff
model = (cpu_id >> 4) & 0xf
model |= (cpu_id >> 12) & 0xf0
stepping = cpu_id & 0xf
return FMS(family, model, stepping)
def fms2str(fms):
return "Family=%#04x Model=%#04x Stepping=%#04x" % \
(fms.family, fms.model, fms.stepping)
def parse_equiv_table(opts, ucode_file, start_offset, eq_table_len):
"""
Read equivalence table and return a list of the equivalence ids contained
"""
table = {}
raw_table = []
# For sanity check only
cpuid_map = {}
table_item = start_offset + EQ_TABLE_OFFSET
table_stop = start_offset + EQ_TABLE_OFFSET + eq_table_len
while table_item < table_stop:
ucode_file.seek(table_item, 0)
data = ucode_file.read(EQ_TABLE_ENTRY_SIZE)
ucode_file.seek(table_item, 0)
cpu_id = read_int32(ucode_file)
if opts.verbose >= VERBOSE_DEBUG:
errata_mask = read_int32(ucode_file)
errata_compare = read_int32(ucode_file)
else:
# Skip errata mask and compare fields
ucode_file.seek(8, 1)
equiv_id = read_int16(ucode_file)
if opts.verbose >= VERBOSE_DEBUG:
res = read_int16(ucode_file)
if equiv_id != 0:
if equiv_id not in table:
table[equiv_id] = OrderedDict()
if cpu_id in table[equiv_id]:
print("WARNING: Duplicate CPUID %#010x (%s) in the equivalence table for equiv_id %#06x " %
(fms2str(cpuid2fms(cpu_id)), equiv_id))
if cpu_id in cpuid_map:
if equiv_id != cpuid_map[cpu_id]:
print("WARNING: Different equiv_id's (%#06x and %#06x) are present in the equivalence table for CPUID %#010x (%s)" %
(equiv_id, cpuid_map[cpu_id], cpu_id,
fms2str(cpuid2fms(cpu_id))))
else:
cpuid_map[cpu_id] = equiv_id
entry = EquivTableEntry(cpu_id, equiv_id, data, table_item)
table[equiv_id][cpu_id] = entry
raw_table.append(entry)
if opts.verbose >= VERBOSE_DEBUG:
print(" [equiv entry@%#010x: cpuid %#010x, equiv id %#06x, errata mask %#010x, errata compare %#010x, res %#06x]" %
(table_item, cpu_id, equiv_id, errata_mask, errata_compare, res))
table_item += EQ_TABLE_ENTRY_SIZE
return (table, raw_table)
def extract_patch(opts, out_dir, ucode_file, patch, equiv_table=None):
"""
Extract raw microcode patch starting at patch_start to the directory
provided by the -o option or the current directory if not specified.
Directory will be created if it doesn't already exist.
"""
cwd = os.getcwd()
if not os.path.exists(out_dir):
os.makedirs(out_dir)
os.chdir(out_dir)
if equiv_table is None:
# Raw patch
out_file_name = "mc_patch_0%x.bin" % patch.level
else:
out_file_name = "mc_equivid_%#06x" % patch.equiv_id
for cpuid in equiv_table[patch.equiv_id]:
out_file_name += '_cpuid_%#010x' % cpuid
out_file_name += "_patch_%#010x.bin" % patch.level
out_path = "%s/%s" % (os.getcwd(), out_file_name)
out_file = open(out_file_name, "wb")
os.chdir(cwd)
if equiv_table is not None:
cpuids = equiv_table[patch.equiv_id].values() if patch.equiv_id in equiv_table else []
else:
cpuids = None
write_mc(opts, out_file, [patch], ucode_file, cpuids)
out_file.close()
print(" Patch extracted to %s" % out_path)
def merge_mc(opts, out_path, table, patches):
# Do some sanity checks, ut only warn about the issues
equivid_map = {}
cpuid_map = {}
for entry in table:
if entry.equiv_id not in equivid_map:
equivid_map[entry.equiv_id] = dict()
if entry.cpuid in equivid_map[entry.equiv_id]:
print("WARNING: Duplicate CPUID %#010x (%s) in the equivalence table for equiv_id %#06x " %
(fms2str(cpuid2fms(entry.cpuid)), entry.equiv_id))
else:
equivid_map[entry.equiv_id][entry.cpuid] = entry
if entry.cpuid in cpuid_map:
if entry.equiv_id != cpuid_map[entry.cpuid]:
print("WARNING: Different equiv_id's (%#06x and %#06x) are present in the equivalence table for CPUID %#010x (%s)" %
(entry.equiv_id, cpuid_map[entry.cpuid], entry.cpuid,
fms2str(cpuid2fms(entry.cpuid))))
else:
cpuid_map[entry.cpuid] = entry.equiv_id
with open(out_path, "wb") as out_file:
write_mc(opts, out_file, patches, equiv_table=table)
print("Microcode written to %s" % out_path)
def write_mc(opts, out_file, patches, ucode_file=None, equiv_table=None):
"""
Writes microcode data to the specified file.
"""
if equiv_table is not None:
# Container header
out_file.write(b'DMA\x00')
# Equivalence table header
out_file.write(EQ_TABLE_TYPE.to_bytes(4, 'little'))
table_size = EQ_TABLE_ENTRY_SIZE * (len(equiv_table) + 1)
out_file.write(table_size.to_bytes(4, 'little'))
# Equivalence table
for cpuid in equiv_table:
out_file.write(cpuid.data)
out_file.write(b'\0' * EQ_TABLE_ENTRY_SIZE)
for patch in patches:
# Patch header
if equiv_table is not None:
out_file.write(PATCH_TYPE.to_bytes(4, 'little'))
out_file.write(patch.size.to_bytes(4, 'little'))
if ucode_file is None:
in_file = open(patch.file, "rb")
else:
in_file = ucode_file
in_file.seek(patch.offset, 0)
out_file.write(in_file.read(patch.size))
if ucode_file is None:
in_file.close()
def parse_ucode_file(opts, path, start_offset):
"""
Scan through microcode container file printing the microcode patch level
for each model contained in the file.
"""
table = None
patches = []
with open(path, "rb") as ucode_file:
print("Microcode patches in %s%s:" %
(path, "+%#x" % start_offset if start_offset else ""))
# Seek to end of file to determine file size
ucode_file.seek(0, 2)
end_of_file = ucode_file.tell()
# Check magic number
ucode_file.seek(start_offset, 0)
if ucode_file.read(4) != b'DMA\x00':
print("ERROR: Missing magic number at beginning of container")
return (None, None, None)
# Check the equivalence table type
eq_table_type = read_int32(ucode_file)
if eq_table_type != EQ_TABLE_TYPE:
print("ERROR: Invalid equivalence table identifier: %#010x" %
eq_table_type)
return (None, None, None)
# Read the equivalence table length
eq_table_len = read_int32(ucode_file)
ids, table = parse_equiv_table(opts, ucode_file, start_offset, eq_table_len)
cursor = start_offset + EQ_TABLE_OFFSET + eq_table_len
while cursor < end_of_file:
# Seek to the start of the patch information
ucode_file.seek(cursor, 0)
patch_start = cursor + 8
patch_type_bytes = ucode_file.read(4)
# Beginning of a new container
if patch_type_bytes == b'DMA\x00':
return (cursor, table, patches)
patch_type = int.from_bytes(patch_type_bytes, 'little')
if patch_type != PATCH_TYPE:
print("Invalid patch identifier: %#010x" % (patch_type))
return (None, table, patches)
patch_length = read_int32(ucode_file)
if opts.verbose:
data_code = read_int32(ucode_file)
else:
ucode_file.seek(4, 1)
ucode_level = read_int32(ucode_file)
if opts.verbose >= VERBOSE_DEBUG:
mc_patch_data_id = read_int16(ucode_file)
mc_patch_data_len = read_int8(ucode_file)
init_flag = read_int8(ucode_file)
mc_patch_data_checksum = read_int32(ucode_file)
nb_dev_id = read_int32(ucode_file)
sb_dev_id = read_int32(ucode_file)
else:
ucode_file.seek(16, 1)
equiv_id = read_int16(ucode_file)
if opts.verbose >= VERBOSE_DEBUG:
nb_rev_id = read_int8(ucode_file)
sb_rev_id = read_int8(ucode_file)
bios_api_rev = read_int8(ucode_file)
reserved1 = [read_int8(ucode_file) for _ in range(3)]
match_reg = [read_int32(ucode_file) for _ in range(8)]
if opts.verbose:
add_info = " Start=%u bytes Date=%04x-%02x-%02x Equiv_id=%#06x" % \
(patch_start, data_code & 0xffff, data_code >> 24,
(data_code >> 16) & 0xff, equiv_id)
else:
add_info = ""
if equiv_id not in ids:
print("Patch equivalence id not present in equivalence table (%#06x)"
% (equiv_id))
print(" Family=???? Model=???? Stepping=????: Patch=%#010x Length=%u bytes%s"
% (ucode_level, patch_length, add_info))
# The cpu_id is the equivalent to CPUID_Fn00000001_EAX
for cpuid in ids[equiv_id]:
print(" %s: Patch=%#010x Length=%u bytes%s"
% (fms2str(cpuid2fms(cpuid)), ucode_level, patch_length, add_info))
if opts.verbose >= VERBOSE_DEBUG:
print(" [data_code=%#010x, mc_patch_data_id=%#06x, mc_patch_data_len=%#04x, init_flag=%#04x, mc_patch_data_checksum=%#010x]" %
(data_code, mc_patch_data_id, mc_patch_data_len, init_flag, mc_patch_data_checksum))
print(" [nb_dev_id=%#010x, sb_dev_id=%#010x, nb_rev_id=%#04x, sb_rev_id=%#04x, bios_api_rev=%#04x, reserved=[%#04x, %#04x, %#04x]]" %
(nb_dev_id, sb_dev_id, nb_rev_id, sb_rev_id, bios_api_rev, reserved1[0], reserved1[1], reserved1[2]))
patch = PatchEntry(path, patch_start, patch_length, equiv_id, ucode_level)
patches.append(patch)
if opts.extract:
extract_patch(opts, opts.extract, ucode_file, patch)
if opts.split:
extract_patch(opts, opts.split, ucode_file, patch, ids)
cursor = cursor + patch_length + 8
return (None, table, patches)
def parse_ucode_files(opts):
all_tables = []
all_patches = []
for f in opts.container_file:
offset = 0
while offset is not None:
offset, table, patches = parse_ucode_file(opts, f, offset)
if opts.merge:
if table is not None:
all_tables += table
if patches is not None:
all_patches += patches
if opts.merge:
merge_mc(opts, opts.merge, all_tables, all_patches)
def parse_options():
""" Parse options """
parser = argparse.ArgumentParser(description="Print information about an amd-ucode container")
parser.add_argument("container_file", nargs='+')
parser.add_argument("-e", "--extract",
help="Dump each patch in container to the specified directory")
parser.add_argument("-s", "--split",
help="Split out each patch in a separate container to the specified directory")
parser.add_argument("-m", "--merge",
help="Write a merged container to the specified file")
parser.add_argument("-v", "--verbose", action="count", default=0,
help="Be verbose about the information in the container file")
opts = parser.parse_args()
for f in opts.container_file:
if not os.path.isfile(f):
parser.print_help()
print()
print("ERROR: Container file \"%s\" does not exist" % f)
sys.exit()
return opts
def main():
""" main """
opts = parse_options()
parse_ucode_files(opts)
if __name__ == "__main__":
main()