diff --git a/.gitignore b/.gitignore index 2f6c058..a7fab66 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1 @@ -SOURCES/linux-firmware-20230404.tar.xz +SOURCES/linux-firmware-20230824.tar.xz diff --git a/.linux-firmware.metadata b/.linux-firmware.metadata index 88dd85b..71ab2b1 100644 --- a/.linux-firmware.metadata +++ b/.linux-firmware.metadata @@ -1 +1 @@ -f584e174d59343652ceab6a8a4a2d929a8355efa SOURCES/linux-firmware-20230404.tar.xz +074c0923c1a85e1ea5843bf77d5e88d2da87522b SOURCES/linux-firmware-20230824.tar.xz diff --git a/SOURCES/amd_ucode_info.py b/SOURCES/amd_ucode_info.py new file mode 100755 index 0000000..8186cbc --- /dev/null +++ b/SOURCES/amd_ucode_info.py @@ -0,0 +1,374 @@ +#!/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() diff --git a/SPECS/linux-firmware.spec b/SPECS/linux-firmware.spec index 38d4b71..077fcb6 100644 --- a/SPECS/linux-firmware.spec +++ b/SPECS/linux-firmware.spec @@ -1,12 +1,12 @@ -%global checkout 2e92a49f +%global checkout 0e048b06 -%global firmware_release 114 +%global firmware_release 119 %global _firmwarepath /usr/lib/firmware %define _binaries_in_noarch_packages_terminate_build 0 Name: linux-firmware -Version: 20230404 +Version: 20230824 Release: %{firmware_release}.git%{checkout}%{?dist} Summary: Firmware files used by the Linux kernel License: GPL+ and GPLv2+ and MIT and Redistributable, no modification permitted @@ -18,6 +18,7 @@ BuildArch: noarch # This is still causing problems in RHEL9 (see bug 1959913) and because of that we should keep out of RHEL8 too # 2) git archive --worktree-attributes --format=tar --prefix=linux-firmware-%%{checkout}/ %%{checkout} | xz > linux-firmware-%%{version}.tar.xz Source0: %{name}-%{version}.tar.xz +Source1: amd_ucode_info.py Provides: kernel-firmware = %{version} xorg-x11-drv-ati-firmware = 7.0 Obsoletes: kernel-firmware < %{version} xorg-x11-drv-ati-firmware < 6.13.0-0.22 @@ -41,6 +42,7 @@ Conflicts: microcode_ctl < 2.1-0 Obsoletes: ivtv-firmware < 2:20080701-28 BuildRequires: git make +BuildRequires: python3 %description This package includes firmware files required for some devices to @@ -231,7 +233,7 @@ contained inside the provided LICENSE file. Please read it carefully. %package -n libertas-usb8388-firmware Summary: Firmware for Marvell Libertas USB 8388 Network Adapter License: Redistributable, no modification permitted -Epoch: 2 +Epoch: 2 Obsoletes: libertas-usb8388-firmware < 2:5.110.22.p23-8 %description -n libertas-usb8388-firmware Firmware for Marvell Libertas USB 8388 Network Adapter @@ -258,6 +260,14 @@ Firmware for Marvell Libertas SD 8787 Network Adapter %prep %setup -q -n linux-firmware-%{checkout} + +# Repack AMD Family 19h microcode +/usr/bin/python3 %{SOURCE1} -vv -s amd-ucode/fam19 amd-ucode/microcode_amd_fam19h.bin +rm -rvf amd-ucode/fam19/*cpuid_0x00aa0f0*.bin +/usr/bin/python3 %{SOURCE1} -vv amd-ucode/fam19/* -m amd-ucode/microcode_amd_fam19h.bin +/usr/bin/python3 %{SOURCE1} -vv amd-ucode/microcode_amd_fam19h.bin +rm -rvf amd-ucode/fam19 + %if 0 git init . if [ -z "$GIT_COMMITTER_NAME" ]; then @@ -428,6 +438,32 @@ sed -e 's/^/%%dir /' linux-firmware.dirs >> linux-firmware.files %config(noreplace) %{_firmwarepath}/netronome/nic_AMDA* %changelog +* Tue Sep 26 2023 Patrick Talbert - 20230824-119.git0e048b06 +- Exclude AMD cpu ucode for fam19/*cpuid_0x00aa0f0* +Resolves: RHEL-3903 + +* Thu Aug 24 2023 Denys Vlasenko - 20230824-118.git0e048b06 +- Update to latest upstream linux-firmware image for assorted updates +- AMD Zen3 and Zen4 firmware update for return address predictor velunerability +Resolves: rhbz#2230415 + +* Tue Aug 08 2023 Denys Vlasenko - 20230808-117.git0ab353f8 +- Update to latest upstream linux-firmware image for assorted updates +- Navi32 dGPU firmware +- Update to fix multi monitor behind TBT3 dock & random flickers +- AMD Zen2 firmware update for cross-process information leak +Resolves: rhbz#2047482, rhbz#2227846, rhbz#2227153 + +* Tue Jul 11 2023 Denys Vlasenko - 20230711-116.gitd3f66064 +- Update to latest upstream linux-firmware image for assorted updates +- AMD GPU firmware update: fix PSR-SU issues with kernel 6.2 or later +Resolves: rhbz#2218670 + +* Mon May 15 2023 Denys Vlasenko - 20230515-115.gitd1962891 +- Update to latest upstream linux-firmware image for assorted updates +- [RHEL8] Add latest NVIDIA signed firmware for Turing GPUs and later +Resolves: rhbz#2183606 + * Wed Apr 05 2023 Patrick Talbert - 20230404-114.git2e92a49f - Update to latest upstream linux-firmware image for assorted updates - Intel QAT Update - firmware for QAT (rhbz 2030316)