580.65.06

This commit is contained in:
Maneet Singh
2025-08-04 11:15:02 -07:00
parent d890313300
commit 307159f262
1315 changed files with 477791 additions and 279973 deletions

View File

@@ -1,6 +1,6 @@
#!/usr/bin/env python3
# SPDX-FileCopyrightText: Copyright (c) 2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
# SPDX-FileCopyrightText: Copyright (c) 2023-2025 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
# SPDX-License-Identifier: MIT
#
# Permission is hereby granted, free of charge, to any person obtaining a
@@ -27,10 +27,12 @@
import sys
import os
import argparse
import shutil
import re
import gzip
import struct
import zlib
import tempfile
import urllib.request
class MyException(Exception):
pass
@@ -109,15 +111,15 @@ def getbytes(filename, array):
return output
else:
if len(output) != data_size:
raise MyException(f"array {array} in {filename} should be {compressed_size} bytes but is actually {len(output)}.")
raise MyException(f"array {array} in {filename} should be {data_size} bytes but is actually {len(output)}.")
return output
# GSP bootloader
def bootloader(gpu, type):
def bootloader(gpu, fuse):
global outputpath
global version
GPU=gpu.upper()
GPU = gpu.upper()
filename = f"src/nvidia/generated/g_bindata_kgspGetBinArchiveGspRmBoot_{GPU}.c"
print(f"Creating nvidia/{gpu}/gsp/bootloader-{version}.bin")
@@ -125,12 +127,12 @@ def bootloader(gpu, type):
with open(f"{outputpath}/nvidia/{gpu}/gsp/bootloader-{version}.bin", "wb") as f:
# Extract the actual bootloader firmware
array = f"kgspBinArchiveGspRmBoot_{GPU}_ucode_image{type}data"
array = f"kgspBinArchiveGspRmBoot_{GPU}_ucode_image{fuse}data"
firmware = getbytes(filename, array)
firmware_size = len(firmware)
# Extract the descriptor (RM_RISCV_UCODE_DESC)
array = f"kgspBinArchiveGspRmBoot_{GPU}_ucode_desc{type}data"
array = f"kgspBinArchiveGspRmBoot_{GPU}_ucode_desc{fuse}data"
descriptor = getbytes(filename, array)
descriptor_size = len(descriptor)
@@ -146,7 +148,7 @@ def bootloader(gpu, type):
f.write(firmware)
# GSP Booter load and unload
def booter(gpu, load, sigsize):
def booter(gpu, load, sigsize, fuse = "prod"):
global outputpath
global version
@@ -159,13 +161,13 @@ def booter(gpu, load, sigsize):
os.makedirs(f"{outputpath}/nvidia/{gpu}/gsp/", exist_ok = True)
with open(f"{outputpath}/nvidia/{gpu}/gsp/booter_{load}-{version}.bin", "wb") as f:
# Extract the actual scrubber firmware
array = f"kgspBinArchiveBooter{LOAD}Ucode_{GPU}_image_prod_data"
# Extract the actual booter firmware
array = f"kgspBinArchiveBooter{LOAD}Ucode_{GPU}_image_{fuse}_data"
firmware = getbytes(filename, array)
firmware_size = len(firmware)
# Extract the signatures
array = f"kgspBinArchiveBooter{LOAD}Ucode_{GPU}_sig_prod_data"
array = f"kgspBinArchiveBooter{LOAD}Ucode_{GPU}_sig_{fuse}_data"
signatures = getbytes(filename, array)
signatures_size = len(signatures)
if signatures_size % sigsize:
@@ -206,17 +208,17 @@ def booter(gpu, load, sigsize):
f.write(struct.pack("<6L", patchloc, 0, fuse_ver, engine_id, ucode_id, num_sigs))
# Extract the descriptor (nvkm_gsp_booter_fw_hdr)
array = f"kgspBinArchiveBooter{LOAD}Ucode_{GPU}_header_prod_data"
array = f"kgspBinArchiveBooter{LOAD}Ucode_{GPU}_header_{fuse}_data"
descriptor = getbytes(filename, array)
# Fifth, the descriptor
f.write(descriptor)
# And finally, the actual scrubber image
# And finally, the actual booter image
f.write(firmware)
# GPU memory scrubber, needed for some GPUs and configurations
def scrubber(gpu, sigsize):
def scrubber(gpu, sigsize, fuse = "prod"):
global outputpath
global version
@@ -231,12 +233,12 @@ def scrubber(gpu, sigsize):
with open(f"{outputpath}/nvidia/{gpu}/gsp/scrubber-{version}.bin", "wb") as f:
# Extract the actual scrubber firmware
array = f"ksec2BinArchiveSecurescrubUcode_{GPUX}_image_prod_data[]"
array = f"ksec2BinArchiveSecurescrubUcode_{GPUX}_image_{fuse}_data[]"
firmware = getbytes(filename, array)
firmware_size = len(firmware)
# Extract the signatures
array = f"ksec2BinArchiveSecurescrubUcode_{GPUX}_sig_prod_data"
array = f"ksec2BinArchiveSecurescrubUcode_{GPUX}_sig_{fuse}_data"
signatures = getbytes(filename, array)
signatures_size = len(signatures)
if signatures_size % sigsize:
@@ -277,7 +279,7 @@ def scrubber(gpu, sigsize):
f.write(struct.pack("<6L", patchloc, 0, fuse_ver, engine_id, ucode_id, num_sigs))
# Extract the descriptor (nvkm_gsp_booter_fw_hdr)
array = f"ksec2BinArchiveSecurescrubUcode_{GPUX}_header_prod_data"
array = f"ksec2BinArchiveSecurescrubUcode_{GPUX}_header_{fuse}_data"
descriptor = getbytes(filename, array)
# Fifth, the descriptor
@@ -286,6 +288,191 @@ def scrubber(gpu, sigsize):
# And finally, the actual scrubber image
f.write(firmware)
ELF_HDR_SIZE = 52
ELF_SHDR_SIZE = 40
ELF_ALIGNMENT = 4
# Create a 32-bit generic ELF header with no program header, and 'shnum'
# section headers, not including the .shstrtab and NULL sections.
# The section headers appear after the ELF header, and the section data
# follows. Note that e_shstrndx cannot be zero, because that implies
# that the .shstrndx section does not exist.
def elf_header(shnum: int):
bytes = struct.pack("<B3s5B7xHH5I6H",
0x7f, b'ELF',
1, 1, 1, 0, 0, # EI_CLASS, EI_DATA, EI_VERSION, EI_OSABI, EI_ABIVERSION
0, 0, 1, # e_type, e_machine, e_version
0, 0, ELF_HDR_SIZE, 0, # e_entry, e_phoff, e_shoff, e_flags
ELF_HDR_SIZE, 0, 0, # e_ehsize, e_phentsize, e_phnum
ELF_SHDR_SIZE, shnum + 2, 1) # e_shentsize, e_shnum, e_shstrndx
return bytes
# Create a 32-bit ELF section header, where 'sh_name' is the offset of the
# section name, 'sh_offset' is the offset of the section data, and 'sh_size'
# is the size (in bytes) of the image in the section data.
# We set sh_flags to SHF_OS_NONCONFORMING and use the sh_info field to store
# a 32-bit CRC of the image data.
def elf_section_header(sh_name, sh_offset, sh_size, sh_info):
bytes = struct.pack("<10I",
sh_name,
1, 0xFFF00102, 0, # sh_type, sh_flags, sh_addr
sh_offset, sh_size,
0, # sh_link
sh_info,
4, 0) # sh_addralign, sh_entsize
return bytes
# A little-known fact about ELF files is that the first section header must
# be empty. Readelf doesn't care about that, but objdump does. This may be
# why the first byte of the .shstrtab should be zero.
def elf_section_header_null():
return b'\0' * ELF_SHDR_SIZE
# Create a 64-bit .shstrtab ELF section header.
# 'shnum' is the number of sections.
# 'sh_offset' is the offset of the .shstrtab section.
# 'sh_size' is the unpadded size of the section.
# The section itself should be padded to the nearest 8-byte boundary, so that
# all the sections are aligned.
def elf_section_header_shstrtab(sh_name, shnum, sh_size):
sh_offset = ELF_HDR_SIZE + ELF_SHDR_SIZE * (shnum + 2);
bytes = struct.pack("<10I",
sh_name,
3, 0x20, 0, # sh_type (SHT_STRTAB), sh_flags (SHF_STRINGS), sh_addr
sh_offset, sh_size,
0, 0, 1, 1) # sh_link, sh_info, sh_addralign, sh_entsize
return bytes
# Build the .shstrtab section, where 'names' is a list of strings
def elf_build_shstrtab(names):
bytes = bytearray(b'\0')
for name in ['.shstrtab'] + names:
bytes.extend(name.encode('ascii') + b'\x00')
return bytes
# Returns a tuple of the size of a bytearray and the size rounded up to the next 8
def sizes(b):
return (len(b), round_up_to_base(len(b), ELF_ALIGNMENT))
# Returns the sh_name offset of a given section name in the .shstrtab section
# 'needle' is the name of the section
# 'haystack' is the .shstrtab section
def offset_of(needle, haystack):
null_terminated = bytearray(needle.encode('ascii') + b'\x00')
position = haystack.find(null_terminated)
if position == -1:
raise MyException(f"unknown section name {needle}")
return position
# Writes a bunch of bytes to f, padded with zeroes to the nearest 4 bytes
# Returns the total number of bytes written
def write_padded(f, b):
f.write(b)
(len, padded) = sizes(b)
if padded > len:
padding_length = padded - len;
f.write(b'\0' * padding_length)
return padded
# Unlike the other images, FMC firmware and its metadata are encapsulated in
# an ELF image. FMC metadata is simpler than the other firmware types, as it
# comprises just three binary blobs.
def fmc(gpu, fuse = "Prod"):
global outputpath
global version
GPU=gpu.upper()
filename = f"src/nvidia/generated/g_bindata_kgspGetBinArchiveGspRmFmcGfw{fuse}Signed_{GPU}.c"
print(f"Creating nvidia/{gpu}/gsp/fmc-{version}.bin")
os.makedirs(f"{outputpath}/nvidia/{gpu}/gsp/", exist_ok = True)
array = f"kgspBinArchiveGspRmFmcGfw{fuse}Signed_{GPU}_ucode_hash_data"
ucode_hash = getbytes(filename, array)
(ucode_hash_size, ucode_hash_padded_size) = sizes(ucode_hash)
array = f"kgspBinArchiveGspRmFmcGfw{fuse}Signed_{GPU}_ucode_sig_data"
ucode_sig = getbytes(filename, array)
(ucode_sig_size, ucode_sig_padded_size) = sizes(ucode_sig)
array = f"kgspBinArchiveGspRmFmcGfw{fuse}Signed_{GPU}_ucode_pkey_data"
ucode_pkey = getbytes(filename, array)
(ucode_pkey_size, ucode_pkey_padded_size) = sizes(ucode_pkey)
array = f"kgspBinArchiveGspRmFmcGfw{fuse}Signed_{GPU}_ucode_image_data"
ucode_image = getbytes(filename, array)
(ucode_image_size, ucode_image_padded_size) = sizes(ucode_image)
shnum = 4 # The number of image sections
# Build the .shstrtab section data
shstrtab = elf_build_shstrtab(['hash', 'signature', 'publickey', 'image'])
(shstrtab_size, shstrtab_padded_size) = sizes(shstrtab)
# Calculate the offsets of each section
shstrtab_offset = ELF_HDR_SIZE + ELF_SHDR_SIZE * (shnum + 2)
hash_offset = shstrtab_offset + shstrtab_padded_size
signature_offset = hash_offset + ucode_hash_padded_size
pkey_offset = signature_offset + ucode_sig_padded_size
image_offset = pkey_offset + ucode_pkey_padded_size
with open(f"{outputpath}/nvidia/{gpu}/gsp/fmc-{version}.bin", "wb") as f:
# Create the ELF header
header = elf_header(shnum)
f.write(header)
# Add the section headers
header = elf_section_header_null()
f.write(header)
header = elf_section_header_shstrtab(offset_of(".shstrtab", shstrtab), shnum, len(shstrtab))
f.write(header)
header = elf_section_header(offset_of("hash", shstrtab),
hash_offset, ucode_hash_size, zlib.crc32(ucode_hash))
f.write(header)
header = elf_section_header(offset_of("signature", shstrtab),
signature_offset, ucode_sig_size, zlib.crc32(ucode_sig))
f.write(header)
header = elf_section_header(offset_of("publickey", shstrtab),
pkey_offset, ucode_pkey_size, zlib.crc32(ucode_pkey))
f.write(header)
header = elf_section_header(offset_of("image", shstrtab),
image_offset, ucode_image_size, zlib.crc32(ucode_image))
f.write(header)
# Make sure we're where we are supposed to be
assert f.tell() == ELF_HDR_SIZE + ELF_SHDR_SIZE * (shnum + 2)
# Write the .shstrtab section data.
write_padded(f, shstrtab)
assert f.tell() % 4 == 0
# Finally, write the four images in sequence
write_padded(f, ucode_hash)
assert f.tell() % 4 == 0
write_padded(f, ucode_sig)
assert f.tell() % 4 == 0
write_padded(f, ucode_pkey)
assert f.tell() % 4 == 0
write_padded(f, ucode_image)
assert f.tell() % 4 == 0
# Extract the GSP-RM firmware from the .run file and copy the binaries
# to the target directory.
def gsp_firmware(filename):
@@ -293,36 +480,50 @@ def gsp_firmware(filename):
global version
import subprocess
import tempfile
import shutil
import time
basename = os.path.basename(filename)
with tempfile.TemporaryDirectory() as temp:
os.chdir(temp)
try:
print(f"Extracting {os.path.basename(filename)} to {temp}")
print(f"Validating {basename}")
result = subprocess.run(['/bin/sh', filename, '--check'], shell=False,
check=True, timeout=10,
stdout = subprocess.PIPE, stderr = subprocess.STDOUT)
output = result.stdout.strip().decode("ascii")
if not "check sums and md5 sums are ok" in output:
raise MyException(f"{basename} is not a valid Nvidia driver .run file")
except subprocess.CalledProcessError as error:
print(error.output.decode())
raise
try:
print(f"Extracting {basename} to {temp}")
# The -x parameter tells the installer to only extract the
# contents and then exit.
subprocess.run(['/bin/sh', filename, '-x'], shell=False,
check=True, timeout=60,
stdout = subprocess.PIPE, stderr = subprocess.STDOUT)
except subprocess.CalledProcessError as error:
except subprocess.SubprocessError as error:
print(error.output.decode())
sys.exit(error.returncode)
except subprocess.TimeoutExpired as error:
print(error.output.decode())
sys.exit(error.returncode)
raise
try:
# The .run file extracts its contents to a directory with the same
# name as the file itself, minus the .run. The GSP-RM firmware
# images are in the 'firmware' subdirectory.
directory = os.path.splitext(os.path.basename(filename))[0]
result = subprocess.run(['/bin/sh', filename, '--target-directory'], shell=False,
check=True, timeout=10,
stdout = subprocess.PIPE, stderr = subprocess.DEVNULL)
directory = result.stdout.strip().decode("ascii")
os.chdir(f"{directory}/firmware")
except:
print("Firmware failed to extract")
sys.exit(1)
except subprocess.SubprocessError as e:
print(error.output.decode())
raise
if not os.path.exists('gsp_tu10x.bin') or not os.path.exists('gsp_ga10x.bin'):
print("Firmware files are missing")
@@ -334,15 +535,15 @@ def gsp_firmware(filename):
print(f"Copied gsp_ga10x.bin to ga102/gsp/gsp-{version}.bin")
# Create a symlink, deleting the existing file/link if necessary
def symlink(src, dst, target_is_directory = False):
def symlink(dest, source, target_is_directory = False):
import errno
try:
os.symlink(src, dst, target_is_directory = target_is_directory)
os.symlink(dest, source, target_is_directory = target_is_directory)
except OSError as e:
if e.errno == errno.EEXIST:
os.remove(dst)
os.symlink(src, dst, target_is_directory = target_is_directory)
os.remove(source)
os.symlink(dest, source, target_is_directory = target_is_directory)
else:
raise e
@@ -355,54 +556,81 @@ def symlinks():
print(f"Creating symlinks in {outputpath}/nvidia")
os.chdir(f"{outputpath}/nvidia")
for d in ['tu116', 'ga100', 'ad102']:
os.makedirs(d, exist_ok = True)
for d in ['tu104', 'tu106', 'tu117']:
os.makedirs(d, exist_ok = True)
symlink('../tu102/gsp', f"{d}/gsp", target_is_directory = True)
for d in ['ad103', 'ad104', 'ad106', 'ad107']:
os.makedirs(d, exist_ok = True)
symlink('../ad102/gsp', f"{d}/gsp", target_is_directory = True)
for d in ['ga103', 'ga104', 'ga106', 'ga107']:
os.makedirs(d, exist_ok = True)
symlink('../ga102/gsp', f"{d}/gsp", target_is_directory = True)
for d in ['ad103', 'ad104', 'ad106', 'ad107']:
# Some older versions of /lib/firmware had symlinks from ad10x/gsp to ad102/gsp,
# even though there were no other directories in ad10x. Delete the existing
# ad10x directory so that we can replace it with a symlink.
if os.path.islink(f"{d}/gsp"):
os.remove(f"{d}/gsp")
os.rmdir(d)
symlink('ad102', d, target_is_directory = True)
# TU11x uses the same bootloader as TU10x
symlink(f"../../tu102/gsp/bootloader-{version}.bin", f"tu116/gsp/bootloader-{version}.bin")
# Blackwell is only supported with GSP, so we can symlink the top-level directories
# instead of just the gsp/ subdirectories.
for d in ['gb102']:
symlink('gb100', d, target_is_directory = True)
for d in ['gb203', 'gb205', 'gb206', 'gb207']:
symlink('gb202', d, target_is_directory = True)
# Symlink the GSP-RM image
symlink(f"../../tu102/gsp/gsp-{version}.bin", f"tu116/gsp/gsp-{version}.bin")
symlink(f"../../tu102/gsp/gsp-{version}.bin", f"ga100/gsp/gsp-{version}.bin")
symlink(f"../../ga102/gsp/gsp-{version}.bin", f"ad102/gsp/gsp-{version}.bin")
symlink(f"../../ga102/gsp/gsp-{version}.bin", f"gh100/gsp/gsp-{version}.bin")
symlink(f"../../ga102/gsp/gsp-{version}.bin", f"gb100/gsp/gsp-{version}.bin")
symlink(f"../../ga102/gsp/gsp-{version}.bin", f"gb202/gsp/gsp-{version}.bin")
# Create a text file that can be inserted as-is to the WHENCE file of the
# linux-firmware git repository.
# linux-firmware git repository. Note that existing firmware versions in
# the repository must be maintained, so those entries are hard-coded here.
# Also note that Nouveau supports Ada and later only with GSP, which is why
# ga103/gsp -> ga102/gsp, but ad103 -> ad102.
def whence():
global outputpath
global version
whence = f"""
whence = f"""File: nvidia/tu102/gsp/bootloader-535.113.01.bin
File: nvidia/tu102/gsp/booter_load-535.113.01.bin
File: nvidia/tu102/gsp/booter_unload-535.113.01.bin
File: nvidia/tu102/gsp/bootloader-{version}.bin
File: nvidia/tu102/gsp/booter_load-{version}.bin
File: nvidia/tu102/gsp/booter_unload-{version}.bin
Link: nvidia/tu104/gsp -> ../tu102/gsp
Link: nvidia/tu106/gsp -> ../tu102/gsp
File: nvidia/tu116/gsp/booter_load-535.113.01.bin
File: nvidia/tu116/gsp/booter_unload-535.113.01.bin
Link: nvidia/tu116/gsp/bootloader-535.113.01.bin -> ../../tu102/gsp/bootloader-535.113.01.bin
File: nvidia/tu116/gsp/booter_load-{version}.bin
File: nvidia/tu116/gsp/booter_unload-{version}.bin
Link: nvidia/tu116/gsp/bootloader-{version}.bin -> ../../tu102/gsp/bootloader-{version}.bin
Link: nvidia/tu117/gsp -> ../tu116/gsp
File: nvidia/ga100/gsp/bootloader-535.113.01.bin
File: nvidia/ga100/gsp/booter_load-535.113.01.bin
File: nvidia/ga100/gsp/booter_unload-535.113.01.bin
File: nvidia/ga100/gsp/bootloader-{version}.bin
File: nvidia/ga100/gsp/booter_load-{version}.bin
File: nvidia/ga100/gsp/booter_unload-{version}.bin
File: nvidia/ad102/gsp/bootloader-{version}.bin
File: nvidia/ad102/gsp/booter_load-{version}.bin
File: nvidia/ad102/gsp/booter_unload-{version}.bin
Link: nvidia/ad103/gsp -> ../ad102/gsp
Link: nvidia/ad104/gsp -> ../ad102/gsp
Link: nvidia/ad106/gsp -> ../ad102/gsp
Link: nvidia/ad107/gsp -> ../ad102/gsp
File: nvidia/ga102/gsp/bootloader-535.113.01.bin
File: nvidia/ga102/gsp/booter_load-535.113.01.bin
File: nvidia/ga102/gsp/booter_unload-535.113.01.bin
File: nvidia/ga102/gsp/bootloader-{version}.bin
File: nvidia/ga102/gsp/booter_load-{version}.bin
File: nvidia/ga102/gsp/booter_unload-{version}.bin
@@ -411,6 +639,41 @@ Link: nvidia/ga104/gsp -> ../ga102/gsp
Link: nvidia/ga106/gsp -> ../ga102/gsp
Link: nvidia/ga107/gsp -> ../ga102/gsp
File: nvidia/ad102/gsp/bootloader-535.113.01.bin
File: nvidia/ad102/gsp/booter_load-535.113.01.bin
File: nvidia/ad102/gsp/booter_unload-535.113.01.bin
File: nvidia/ad102/gsp/bootloader-{version}.bin
File: nvidia/ad102/gsp/booter_load-{version}.bin
File: nvidia/ad102/gsp/booter_unload-{version}.bin
File: nvidia/ad102/gsp/scrubber-{version}.bin
Link: nvidia/ad103 -> ad102
Link: nvidia/ad104 -> ad102
Link: nvidia/ad106 -> ad102
Link: nvidia/ad107 -> ad102
File: nvidia/gh100/gsp/bootloader-{version}.bin
File: nvidia/gh100/gsp/fmc-{version}.bin
File: nvidia/gb100/gsp/bootloader-{version}.bin
File: nvidia/gb100/gsp/fmc-{version}.bin
Link: nvidia/gb102 -> gb100
File: nvidia/gb202/gsp/bootloader-{version}.bin
File: nvidia/gb202/gsp/fmc-{version}.bin
Link: nvidia/gb203 -> gb202
Link: nvidia/gb205 -> gb202
Link: nvidia/gb206 -> gb202
Link: nvidia/gb207 -> gb202
File: nvidia/tu102/gsp/gsp-535.113.01.bin
Origin: gsp_tu10x.bin from NVIDIA-Linux-x86_64-535.113.01.run
Link: nvidia/tu116/gsp/gsp-535.113.01.bin -> ../../tu102/gsp/gsp-535.113.01.bin
Link: nvidia/ga100/gsp/gsp-535.113.01.bin -> ../../tu102/gsp/gsp-535.113.01.bin
File: nvidia/ga102/gsp/gsp-535.113.01.bin
Origin: gsp_ga10x.bin from NVIDIA-Linux-x86_64-535.113.01.run
Link: nvidia/ad102/gsp/gsp-535.113.01.bin -> ../../ga102/gsp/gsp-535.113.01.bin
File: nvidia/tu102/gsp/gsp-{version}.bin
Origin: gsp_tu10x.bin from NVIDIA-Linux-x86_64-{version}.run
Link: nvidia/tu116/gsp/gsp-{version}.bin -> ../../tu102/gsp/gsp-{version}.bin
@@ -419,6 +682,9 @@ Link: nvidia/ga100/gsp/gsp-{version}.bin -> ../../tu102/gsp/gsp-{version}.bin
File: nvidia/ga102/gsp/gsp-{version}.bin
Origin: gsp_ga10x.bin from NVIDIA-Linux-x86_64-{version}.run
Link: nvidia/ad102/gsp/gsp-{version}.bin -> ../../ga102/gsp/gsp-{version}.bin
Link: nvidia/gh100/gsp/gsp-{version}.bin -> ../../ga102/gsp/gsp-{version}.bin
Link: nvidia/gb100/gsp/gsp-{version}.bin -> ../../ga102/gsp/gsp-{version}.bin
Link: nvidia/gb202/gsp/gsp-{version}.bin -> ../../ga102/gsp/gsp-{version}.bin
"""
with open(f"{outputpath}/WHENCE.txt", 'w') as f:
@@ -437,16 +703,21 @@ def main():
' the firmware files directly where Nouveau expects them.'
' The --revision option is useful for testing new firmware'
' versions without changing Nouveau source code.'
' The --driver option lets you specify the path to the .run file,'
' and this script also will extract and copy the GSP-RM firmware images.')
' The --driver option lets you specify the local path to the .run file,'
' or the URL of a file to download, and this script also will extract'
' and copy the GSP-RM firmware images. If no path/url is provided, then'
' the script will guess the URL and download the file automatically.')
parser.add_argument('-i', '--input', default = os.getcwd(),
help = 'Path to source directory (where version.mk exists)')
parser.add_argument('-o', '--output', default = os.path.abspath(os.getcwd() + '/_out'),
help = 'Path to target directory (where files will be written)')
parser.add_argument('-r', '--revision',
help = 'Files will be named with this version number')
parser.add_argument('--debug-fused', action='store_true',
help = 'Extract debug instead of production images')
parser.add_argument('-d', '--driver',
help = 'Path to Nvidia driver .run package, for also extracting the GSP-RM firmware')
nargs = '?', const = '',
help = 'Path or URL to NVIDIA-Linux-x86_64-<version>.run driver package, for also extracting the GSP-RM firmware')
parser.add_argument('-s', '--symlink', action='store_true',
help = 'Also create symlinks for all supported GPUs')
parser.add_argument('-w', '--whence', action='store_true',
@@ -455,15 +726,11 @@ def main():
os.chdir(args.input)
if args.driver:
if not os.path.exists(args.driver):
print(f"File {args.driver} does not exist.")
sys.exit(1)
version = args.revision
if not version:
with open("version.mk") as f:
version = re.search(r'^NVIDIA_VERSION = ([^\s]+)', f.read(), re.MULTILINE).group(1)
del f
print(f"Generating files for version {version}")
@@ -472,29 +739,65 @@ def main():
os.makedirs(f"{outputpath}/nvidia", exist_ok = True)
booter("tu102", "load", 16)
booter("tu102", "unload", 16)
# TU10x and GA100 do not have debug-fused versions of the bootloader
if args.debug_fused:
print("Generation images for debug-fused GPUs")
bootloader_fuse = "_dbg_"
booter_fuse = "dbg" # Also used for scrubber
fmc_fuse = "Debug"
else:
bootloader_fuse = "_prod_"
booter_fuse = "prod"
fmc_fuse = "Prod"
booter("tu102", "load", 16, booter_fuse)
booter("tu102", "unload", 16, booter_fuse)
bootloader("tu102", "_")
booter("tu116", "load", 16)
booter("tu116", "unload", 16)
booter("tu116", "load", 16, booter_fuse)
booter("tu116", "unload", 16, booter_fuse)
# TU11x uses the same bootloader as TU10x
booter("ga100", "load", 384)
booter("ga100", "unload", 384)
booter("ga100", "load", 384, booter_fuse)
booter("ga100", "unload", 384, booter_fuse)
bootloader("ga100", "_")
booter("ga102", "load", 384)
booter("ga102", "unload", 384)
bootloader("ga102", "_prod_")
booter("ga102", "load", 384, booter_fuse)
booter("ga102", "unload", 384, booter_fuse)
bootloader("ga102", bootloader_fuse)
booter("ad102", "load", 384)
booter("ad102", "unload", 384)
bootloader("ad102", "_prod_")
# scrubber("ad102", 384) # Not currently used by Nouveau
booter("ad102", "load", 384, booter_fuse)
booter("ad102", "unload", 384, booter_fuse)
bootloader("ad102", bootloader_fuse)
scrubber("ad102", 384, booter_fuse) # Not currently used by Nouveau
if args.driver:
gsp_firmware(os.path.abspath(args.driver))
bootloader("gh100", bootloader_fuse)
fmc("gh100", fmc_fuse)
bootloader("gb100", bootloader_fuse)
fmc("gb100", fmc_fuse)
bootloader("gb202", bootloader_fuse)
fmc("gb202", fmc_fuse)
if args.driver is not None:
if args.driver == '':
# No path/url provided, so make a guess of the URL
# to automatically download the right version.
args.driver = f'https://download.nvidia.com/XFree86/Linux-x86_64/{version}/NVIDIA-Linux-x86_64-{version}.run'
if re.search('^http[s]://', args.driver):
with tempfile.NamedTemporaryFile(prefix = f'NVIDIA-Linux-x86_64-{version}-', suffix = '.run') as f:
print(f"Downloading driver from {args.driver} as {f.name}")
urllib.request.urlretrieve(args.driver, f.name)
gsp_firmware(f.name)
del f
else:
if not os.path.exists(args.driver):
print(f"File {args.driver} does not exist.")
sys.exit(1)
gsp_firmware(os.path.abspath(args.driver))
if args.symlink:
symlinks()

View File

@@ -0,0 +1,445 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 4
[[package]]
name = "adler2"
version = "2.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627"
[[package]]
name = "aho-corasick"
version = "1.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916"
dependencies = [
"memchr",
]
[[package]]
name = "anstream"
version = "0.6.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8acc5369981196006228e28809f761875c0327210a891e941f4c683b3a99529b"
dependencies = [
"anstyle",
"anstyle-parse",
"anstyle-query",
"anstyle-wincon",
"colorchoice",
"is_terminal_polyfill",
"utf8parse",
]
[[package]]
name = "anstyle"
version = "1.0.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "55cc3b69f167a1ef2e161439aa98aed94e6028e5f9a59be9a6ffb47aef1651f9"
[[package]]
name = "anstyle-parse"
version = "0.2.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3b2d16507662817a6a20a9ea92df6652ee4f94f914589377d69f3b21bc5798a9"
dependencies = [
"utf8parse",
]
[[package]]
name = "anstyle-query"
version = "1.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "79947af37f4177cfead1110013d678905c37501914fba0efea834c3fe9a8d60c"
dependencies = [
"windows-sys",
]
[[package]]
name = "anstyle-wincon"
version = "3.0.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ca3534e77181a9cc07539ad51f2141fe32f6c3ffd4df76db8ad92346b003ae4e"
dependencies = [
"anstyle",
"once_cell",
"windows-sys",
]
[[package]]
name = "bitflags"
version = "2.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8f68f53c83ab957f72c32642f3868eec03eb974d1fb82e453128456482613d36"
[[package]]
name = "byteorder"
version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b"
[[package]]
name = "cfg-if"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
[[package]]
name = "clap"
version = "4.5.29"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8acebd8ad879283633b343856142139f2da2317c96b05b4dd6181c61e2480184"
dependencies = [
"clap_builder",
"clap_derive",
]
[[package]]
name = "clap_builder"
version = "4.5.29"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f6ba32cbda51c7e1dfd49acc1457ba1a7dec5b64fe360e828acb13ca8dc9c2f9"
dependencies = [
"anstream",
"anstyle",
"clap_lex",
"strsim",
]
[[package]]
name = "clap_derive"
version = "4.5.28"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bf4ced95c6f4a675af3da73304b9ac4ed991640c36374e4b46795c49e17cf1ed"
dependencies = [
"heck",
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "clap_lex"
version = "0.7.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f46ad14479a25103f283c0f10005961cf086d8dc42205bb44c46ac563475dca6"
[[package]]
name = "colorchoice"
version = "1.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5b63caa9aa9397e2d9480a9b13673856c78d8ac123288526c37d7839f2a86990"
[[package]]
name = "crc32fast"
version = "1.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a97769d94ddab943e4510d138150169a2758b5ef3eb191a9ee688de3e23ef7b3"
dependencies = [
"cfg-if",
]
[[package]]
name = "either"
version = "1.13.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0"
[[package]]
name = "errno"
version = "0.3.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "33d852cb9b869c2a9b3df2f71a3074817f01e1844f839a144f5fcef059a4eb5d"
dependencies = [
"libc",
"windows-sys",
]
[[package]]
name = "extract-firmware-nouveau"
version = "0.1.0"
dependencies = [
"byteorder",
"clap",
"flate2",
"itertools",
"regex",
"tempfile",
]
[[package]]
name = "fastrand"
version = "2.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be"
[[package]]
name = "flate2"
version = "1.0.35"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c936bfdafb507ebbf50b8074c54fa31c5be9a1e7e5f467dd659697041407d07c"
dependencies = [
"crc32fast",
"miniz_oxide",
]
[[package]]
name = "getrandom"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "43a49c392881ce6d5c3b8cb70f98717b7c07aabbdff06687b9030dbfbe2725f8"
dependencies = [
"cfg-if",
"libc",
"wasi",
"windows-targets",
]
[[package]]
name = "heck"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
[[package]]
name = "is_terminal_polyfill"
version = "1.70.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf"
[[package]]
name = "itertools"
version = "0.14.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2b192c782037fadd9cfa75548310488aabdbf3d2da73885b31bd0abd03351285"
dependencies = [
"either",
]
[[package]]
name = "libc"
version = "0.2.169"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b5aba8db14291edd000dfcc4d620c7ebfb122c613afb886ca8803fa4e128a20a"
[[package]]
name = "linux-raw-sys"
version = "0.4.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d26c52dbd32dccf2d10cac7725f8eae5296885fb5703b261f7d0a0739ec807ab"
[[package]]
name = "memchr"
version = "2.7.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3"
[[package]]
name = "miniz_oxide"
version = "0.8.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b3b1c9bd4fe1f0f8b387f6eb9eb3b4a1aa26185e5750efb9140301703f62cd1b"
dependencies = [
"adler2",
]
[[package]]
name = "once_cell"
version = "1.20.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "945462a4b81e43c4e3ba96bd7b49d834c6f61198356aa858733bc4acf3cbe62e"
[[package]]
name = "proc-macro2"
version = "1.0.93"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "60946a68e5f9d28b0dc1c21bb8a97ee7d018a8b322fa57838ba31cc878e22d99"
dependencies = [
"unicode-ident",
]
[[package]]
name = "quote"
version = "1.0.38"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0e4dccaaaf89514f546c693ddc140f729f958c247918a13380cccc6078391acc"
dependencies = [
"proc-macro2",
]
[[package]]
name = "regex"
version = "1.11.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191"
dependencies = [
"aho-corasick",
"memchr",
"regex-automata",
"regex-syntax",
]
[[package]]
name = "regex-automata"
version = "0.4.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908"
dependencies = [
"aho-corasick",
"memchr",
"regex-syntax",
]
[[package]]
name = "regex-syntax"
version = "0.8.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c"
[[package]]
name = "rustix"
version = "0.38.44"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fdb5bc1ae2baa591800df16c9ca78619bf65c0488b41b96ccec5d11220d8c154"
dependencies = [
"bitflags",
"errno",
"libc",
"linux-raw-sys",
"windows-sys",
]
[[package]]
name = "strsim"
version = "0.11.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f"
[[package]]
name = "syn"
version = "2.0.98"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "36147f1a48ae0ec2b5b3bc5b537d267457555a10dc06f3dbc8cb11ba3006d3b1"
dependencies = [
"proc-macro2",
"quote",
"unicode-ident",
]
[[package]]
name = "tempfile"
version = "3.16.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "38c246215d7d24f48ae091a2902398798e05d978b24315d6efbc00ede9a8bb91"
dependencies = [
"cfg-if",
"fastrand",
"getrandom",
"once_cell",
"rustix",
"windows-sys",
]
[[package]]
name = "unicode-ident"
version = "1.0.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a210d160f08b701c8721ba1c726c11662f877ea6b7094007e1ca9a1041945034"
[[package]]
name = "utf8parse"
version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821"
[[package]]
name = "wasi"
version = "0.13.3+wasi-0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "26816d2e1a4a36a2940b96c5296ce403917633dff8f3440e9b236ed6f6bacad2"
dependencies = [
"wit-bindgen-rt",
]
[[package]]
name = "windows-sys"
version = "0.59.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b"
dependencies = [
"windows-targets",
]
[[package]]
name = "windows-targets"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973"
dependencies = [
"windows_aarch64_gnullvm",
"windows_aarch64_msvc",
"windows_i686_gnu",
"windows_i686_gnullvm",
"windows_i686_msvc",
"windows_x86_64_gnu",
"windows_x86_64_gnullvm",
"windows_x86_64_msvc",
]
[[package]]
name = "windows_aarch64_gnullvm"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3"
[[package]]
name = "windows_aarch64_msvc"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469"
[[package]]
name = "windows_i686_gnu"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b"
[[package]]
name = "windows_i686_gnullvm"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66"
[[package]]
name = "windows_i686_msvc"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66"
[[package]]
name = "windows_x86_64_gnu"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78"
[[package]]
name = "windows_x86_64_gnullvm"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d"
[[package]]
name = "windows_x86_64_msvc"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"
[[package]]
name = "wit-bindgen-rt"
version = "0.33.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3268f3d866458b787f390cf61f4bbb563b922d091359f9608842999eaee3943c"
dependencies = [
"bitflags",
]

View File

@@ -0,0 +1,12 @@
[package]
name = "extract-firmware-nouveau"
version = "0.1.0"
edition = "2021"
[dependencies]
byteorder = "1.5.0"
clap = { version = "4.5.23", features = ["derive", "string"] }
flate2 = { version = "1.0.35" }
itertools = "0.14.0"
regex = "1.11.1"
tempfile = "3.16.0"

View File

@@ -0,0 +1,817 @@
use byteorder::{ByteOrder, LittleEndian};
use clap::Parser;
use flate2::{Decompress, FlushDecompress};
use itertools::Itertools;
use regex::Regex;
use std::fs;
use std::io::BufRead;
use std::os::unix;
use std::path::{Path, PathBuf};
use std::process::Command;
use std::process::ExitCode;
use std::{env, io::Write};
use tempfile::tempdir;
/// Clap value parser function that verifies that a path (directory) exists
/// and converts it to its absolute path form.
fn parser_path_exists(s: &str) -> Result<PathBuf, String> {
let path = fs::canonicalize(PathBuf::from(s))
.map_err(|err| format!("Invalid path {s}: {err}"))?;
if fs::exists(&path).is_ok_and(|b| b == true) && path.is_dir() {
Ok(path)
} else {
Err(format!("Path {} does not exist or is not accessible", path.display()))
}
}
/// Clap value parser function that verifies that a file exists
fn parser_file_exists(s: &str) -> Result<PathBuf, String> {
let file = fs::canonicalize(PathBuf::from(s))
.map_err(|err| format!("Invalid file {s}: {err}"))?;
if fs::exists(&file).is_ok_and(|b| b == true) && file.is_file() {
Ok(file)
} else {
Err(format!("File {} does not exist or is not accessible", file.display()))
}
}
/// Clap value parser function that verifies that a given string is a valid version number
fn parser_valid_revision(s: &str) -> Result<String, String> {
// Match any version number, e.g. X.Y, X.Y.Z, X.Y.Z.W, etc
let re = Regex::new(r"^[0-9]+(\.[0-9]+){1,}$").unwrap();
if re.is_match(s) {
Ok(s.to_string())
} else {
Err(format!("Invalid revision \"{s}\""))
}
}
#[derive(Parser, Debug)]
/// Extract firmware binaries from the OpenRM git repository in a format expected by the Nouveau device driver.
struct Cli {
/// Path to source directory (where version.mk exists)
#[arg (short, long,
default_value=env::current_dir().unwrap().into_os_string(),
value_parser=parser_path_exists)]
input: PathBuf,
/// Path to target directory (where files will be written)
/// Unlike the Python version, the output directory must already exist.
#[arg (short, long,
default_value=env::current_dir().unwrap().into_os_string(),
value_parser=parser_path_exists)]
output: PathBuf,
/// Files will be named with this version number
#[arg (short, long,
value_parser=parser_valid_revision)]
revision: Option<String>,
/// Path to Nvidia driver .run package, for also extracting the GSP-RM firmware
#[arg (short, long,
value_parser=parser_file_exists)]
driver: Option<PathBuf>,
/// Also create symlinks for all supported GPUs
#[arg(short, long, action)]
symlink: bool,
/// Also generate a WHENCE file
#[arg(short, long, action)]
whence: bool,
}
/// Extract the driver/firmware version number from the version.mk file
fn find_version() -> Option<String> {
let re = Regex::new(r"^NVIDIA_VERSION = ([^\s]+)").unwrap();
let file = fs::File::open("version.mk").unwrap();
let reader = std::io::BufReader::new(file);
// Alternative approach: https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=fa91a4ad9024a836a7fd74de387755c1
// Or https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=d3dd6b77c08a9bf9d16510dea9ba1a86
for line in reader.lines() {
let line = line.unwrap();
if let Some(version) = re.captures(&line) {
return Some(version[1].to_string());
}
}
None
}
// Executes a shell command, and either returns stdout as success,
// or an error message as failure.
fn command(filename: &Path, parameter: &str) -> Result<String, String> {
let output = Command::new("/bin/sh")
.arg(filename)
.arg(parameter)
.output()
.map_err(|err| format!("Could not execute {} {parameter}: {err}", filename.display()))?;
if !output.status.success() {
return Err(format!(
"Command {} {parameter} failed with exit code {}.\n{}\n{}",
filename.display(),
output.status.code().unwrap_or(0),
String::from_utf8_lossy(&output.stdout),
String::from_utf8_lossy(&output.stderr)
));
}
Ok(String::from_utf8_lossy(&output.stdout).trim().to_string())
}
// Extract the GSP-RM firmware from the .run file and copy the binaries
// to the target directory.
fn gsp_firmware(output: &Path, version: &str, runfile: &Path) -> Result<(), String> {
// When tempdir goes out of scope, its destructor will delete the directory
let tempdir = tempdir()
.map_err(|err| format!("Could not create temp directory: {err}"))?;
// Chdir to the new temporary directory, because the .run file only extracts
// to the current directory.
env::set_current_dir(&tempdir)
.map_err(|err| format!("Could not chdir to {}: {err}", tempdir.path().display()))?;
let directory = command(runfile, "--target-directory")?;
// Extract the firmware images from the .run file
command(runfile, "-x")?;
// Chdir to the directory that has the firmware images
let imagedir = format!("{directory}/firmware");
env::set_current_dir(&imagedir)
.map_err(|err| format!("Could not chdir to {imagedir}: {err}"))?;
let target = output.join(format!("tu102/gsp/gsp-{version}.bin"));
let source = "gsp_tu10x.bin";
fs::copy(source, &target)
.map_err(|err| format!("Could not copy {source} to {}: {err}", target.display()))?;
let target = output.join(format!("ga102/gsp/gsp-{version}.bin"));
let source = "gsp_ga10x.bin";
fs::copy(source, &target)
.map_err(|err| format!("Could not copy {source} to {}: {err}", target.display()))?;
Ok(())
}
fn whence(output: &Path, version: &str) -> Result<(), String> {
let text = format!(
"File: nvidia/tu102/gsp/bootloader-{version}.bin
File: nvidia/tu102/gsp/booter_load-{version}.bin
File: nvidia/tu102/gsp/booter_unload-{version}.bin
Link: nvidia/tu104/gsp -> ../tu102/gsp
Link: nvidia/tu106/gsp -> ../tu102/gsp
File: nvidia/tu116/gsp/booter_load-{version}.bin
File: nvidia/tu116/gsp/booter_unload-{version}.bin
Link: nvidia/tu116/gsp/bootloader-{version}.bin -> ../../tu102/gsp/bootloader-{version}.bin
Link: nvidia/tu117/gsp -> ../tu116/gsp
File: nvidia/ga100/gsp/bootloader-{version}.bin
File: nvidia/ga100/gsp/booter_load-{version}.bin
File: nvidia/ga100/gsp/booter_unload-{version}.bin
File: nvidia/ad102/gsp/bootloader-{version}.bin
File: nvidia/ad102/gsp/booter_load-{version}.bin
File: nvidia/ad102/gsp/booter_unload-{version}.bin
Link: nvidia/ad103/gsp -> ../ad102/gsp
Link: nvidia/ad104/gsp -> ../ad102/gsp
Link: nvidia/ad106/gsp -> ../ad102/gsp
Link: nvidia/ad107/gsp -> ../ad102/gsp
File: nvidia/ga102/gsp/bootloader-{version}.bin
File: nvidia/ga102/gsp/booter_load-{version}.bin
File: nvidia/ga102/gsp/booter_unload-{version}.bin
Link: nvidia/ga103/gsp -> ../ga102/gsp
Link: nvidia/ga104/gsp -> ../ga102/gsp
Link: nvidia/ga106/gsp -> ../ga102/gsp
Link: nvidia/ga107/gsp -> ../ga102/gsp
File: nvidia/tu102/gsp/gsp-{version}.bin
Origin: gsp_tu10x.bin from NVIDIA-Linux-x86_64-{version}.run
Link: nvidia/tu116/gsp/gsp-{version}.bin -> ../../tu102/gsp/gsp-{version}.bin
Link: nvidia/ga100/gsp/gsp-{version}.bin -> ../../tu102/gsp/gsp-{version}.bin
File: nvidia/ga102/gsp/gsp-{version}.bin
Origin: gsp_ga10x.bin from NVIDIA-Linux-x86_64-{version}.run
Link: nvidia/ad102/gsp/gsp-{version}.bin -> ../../ga102/gsp/gsp-{version}.bin
");
let whence = "WHENCE.txt";
let filename = output.join(whence);
let mut file = fs::OpenOptions::new()
.write(true)
.create(true)
.open(&filename)
.map_err(|err| format!("Could not create {whence}: {err}"))?;
file.write_all(text.as_bytes())
.map_err(|err| format!("Could not write to {whence}: {err}"))?;
file.sync_all()
.map_err(|err| format!("Failed to sync {whence}: {err}"))?;
Ok(())
}
/// Create a symlink if it doesn't exist already, or return an error
fn symlink(original: &Path, link: &Path) -> Result<(), String> {
match fs::read_link(&link) {
Ok(o) if o == original => {
// Symlink already exists and is correct
}
Ok(o) => {
// Symlink exists but points to wrong file for some reason
return Err(format!("Symlink {} points to wrong item {}", link.display(), o.display()));
}
Err(err) if err.kind() == std::io::ErrorKind::NotFound => {
// Symlink doesn't exist, so we can create it.
unix::fs::symlink(&original, &link)
.map_err(|err| format!("Could not create symlink {} -> {}: {err}", link.display(), original.display()))?;
}
Err(err) => {
// Something else is wrong
return Err(format!("File/symlink {} is broken: {err}", link.display()));
}
}
Ok(())
}
/// Create symlinks in the target directory for the other GPUs. This mirrors
/// what the WHENCE file in linux-firmware does.
fn symlinks(output: &Path, version: &str) -> Result<(), String> {
env::set_current_dir(&output)
.map_err(|err| format!("Could not chdir to {}: {err}", output.display()))?;
// For these paths, we can just create a symlink of the entire 'gsp' directory.
// For example, tu104, tu106, and tu117 all use the same binaries as tu102.
let paths = [
("tu104", "tu102"),
("tu106", "tu102"),
("tu117", "tu102"),
("ad103", "ad102"),
("ad104", "ad102"),
("ad106", "ad102"),
("ad107", "ad102"),
("ga103", "ga102"),
("ga104", "ga102"),
("ga106", "ga102"),
("ga107", "ga102"),
];
// Create the source directory for each GPU, and then create symlink
// for the gsp directory to the "parent" GPU.
for (source, dest) in paths {
fs::create_dir_all(&source)
.map_err(|err| format!("Could not create {}/{source}: {err}", output.display()))?;
let original = PathBuf::from(format!("../{dest}/gsp"));
let link = PathBuf::from(format!("{source}/gsp"));
symlink(&original, &link)?;
}
// Create additional symlinks for specific firmware files. TU116 is a special case
// because it uses the same bootloader as TU102, but the booter images are different.
let paths = [
("tu116", "tu102", "bootloader"),
("tu116", "tu102", "gsp"),
("ga100", "tu102", "gsp"),
("ad102", "ga102", "gsp"),
];
for (source, dest, kind) in paths {
// These directories should already exist, but create them anyway for consistency.
let path = format!("{source}/gsp");
fs::create_dir_all(&path)
.map_err(|err| format!("Could not create {}/{path}: {err}", output.display()))?;
let link = PathBuf::from(format!("{path}/{kind}-{version}.bin"));
let original = PathBuf::from(format!("../../{dest}/gsp/{kind}-{version}.bin"));
symlink(&original, &link)?;
}
Ok(())
}
fn get_bytes(filename: &Path, array: &str, expected_size: Option<usize>) -> Result<Vec<u8>, String> {
let re_datasize = Regex::new(r"DATA SIZE \(bytes\): (\d+)").unwrap();
let re_compressedsize = Regex::new(r"COMPRESSED SIZE \(bytes\): (\d+)").unwrap();
let re_bytes = Regex::new(r"0x([0-9a-f][0-9a-f])[^0-9a-f]").unwrap();
let file = fs::File::open(filename)
.map_err(|err| format!("Could not open {}: {err}", filename.display()))?;
let reader = std::io::BufReader::new(file);
let mut compressed: bool = false;
let mut data_size: usize = 0;
let mut compressed_size: usize = 0;
let mut bytes: Vec<u8> = vec![];
let mut in_bytes: bool = false;
for line in reader.lines() {
let line = line.unwrap();
if in_bytes {
if line.contains("};") {
break;
}
// Extract all of the two-digit hex numbers from the line and convert
// them to a vector of u8. captures_iter() will return an iterator only
// over matches, ignoring everything else, even if there are 0 matches.
let row = re_bytes
.captures_iter(&line)
.map(|c| u8::from_str_radix(&c[1], 16).unwrap());
bytes.extend(row);
continue;
}
if line.contains("COMPRESSION: NO") {
compressed = false;
continue;
}
if line.contains("COMPRESSION: YES") {
compressed = true;
continue;
}
if let Some(size) = re_datasize.captures(&line) {
data_size = size[1].parse().unwrap();
continue;
}
if let Some(size) = re_compressedsize.captures(&line) {
compressed_size = size[1].parse().unwrap();
continue;
}
if line.contains(&format!("static BINDATA_CONST NvU8 {array}")) {
in_bytes = true;
bytes.reserve_exact(if compressed {
compressed_size
} else {
data_size
});
continue;
}
}
if bytes.is_empty() {
return Err(format!("Error: no data found for {array}"));
}
let output = if compressed {
if bytes.len() != compressed_size {
return Err(format!("compressed array {array} in {} should be {compressed_size} bytes but is actually {}",
filename.display(), bytes.len()));
}
let mut uncompressed = Vec::<u8>::with_capacity(data_size);
let mut decompressor = Decompress::new(false);
if let Err(err) =
decompressor.decompress_vec(&bytes, &mut uncompressed, FlushDecompress::Finish)
{
return Err(format!("array {array} in {} decompressed to {} bytes but should have been {data_size} bytes: {err}",
filename.display(), bytes.len()));
}
uncompressed
} else {
if bytes.len() != data_size {
return Err(format!("array {array} in {} should be {data_size} bytes but is actually {}",
filename.display(), bytes.len()));
}
bytes
};
if let Some(size) = expected_size {
if size != output.len() {
return Err(format!("array {array} in {} is {} bytes but should be {size} bytes",
filename.display(), output.len()));
}
}
Ok(output)
}
fn round_up_to_base(x: u32, base: u32) -> u32 {
((x + base - 1) / base) * base
}
fn bootloader(output: &Path, version: &str, gpu: &str, prod: &str) -> Result<(), String> {
let gpu_upper = gpu.to_uppercase();
let filename = PathBuf::from(format!(
"src/nvidia/generated/g_bindata_kgspGetBinArchiveGspRmBoot_{gpu_upper}.c"
));
println!("Creating nvidia/{gpu}/gsp/bootloader-{version}");
let gsp = output.join(format!("{gpu}/gsp"));
fs::create_dir_all(&gsp)
.map_err(|err| format!("Could not create nvidia/{gpu}/gsp/: {err}"))?;
// Extract the actual bootloader firmware
let array = format!("kgspBinArchiveGspRmBoot_{gpu_upper}_ucode_image_{prod}data");
let firmware = get_bytes(&filename, &array, None)?;
let firmware_size: u32 = firmware.len() as u32;
// Extract the descriptor (RM_RISCV_UCODE_DESC)
let array = format!("kgspBinArchiveGspRmBoot_{gpu_upper}_ucode_desc_{prod}data");
let descriptor = get_bytes(&filename, &array, None)?;
let descriptor_size: u32 = descriptor.len() as u32;
// Create the output
let binfile: PathBuf = gsp.join(format!("bootloader-{version}.bin"));
let mut file = fs::OpenOptions::new().write(true).create(true).open(&binfile)
.map_err(|err| format!("Could not create {}: {err}", binfile.display()))?;
// First, add the nvfw_bin_hdr header
let total_size: u32 = round_up_to_base(24 + firmware_size + descriptor_size, 256);
let firmware_offset: u32 = 24 + descriptor_size;
let nvfw_bin_hdr = [0x10de, 1, total_size, 24, firmware_offset, firmware_size]
.iter()
.flat_map(|x| x.to_le_bytes())
.collect::<Vec<_>>();
file.write_all(&nvfw_bin_hdr)
.map_err(|err| format!("Could not write to {}: {err}", filename.display()))?;
// Second, add the descriptor
file.write_all(&descriptor)
.map_err(|err| format!("Could not write to {}: {err}", filename.display()))?;
// Finally, the actual bootloader image
file.write_all(&firmware)
.map_err(|err| format!("Could not write to {}: {err}", filename.display()))?;
file.sync_all()
.map_err(|err| format!("Failed to sync {}: {err}", filename.display()))?;
Ok(())
}
trait StrExt {
fn capitalize_first_letter(&self) -> String;
fn replace_last_with_x(&self) -> String;
}
impl StrExt for str {
fn capitalize_first_letter(&self) -> String {
let (first, rest) = self.split_at(1);
first.to_ascii_uppercase() + rest
}
fn replace_last_with_x(&self) -> String {
format!("{}X", &self[..self.len() - 1])
}
}
/// GSP Booter load and unload
fn booter(output: &Path, version: &str, gpu: &str, load: &str, sigsize: u32) -> Result<(), String> {
let gpu_upper = gpu.to_uppercase();
let load_upper: String = load.capitalize_first_letter();
let filename: PathBuf = PathBuf::from(format!(
"src/nvidia/generated/g_bindata_kgspGetBinArchiveBooter{load_upper}Ucode_{gpu_upper}.c"
));
println!("Creating nvidia/{gpu}/gsp/booter_{load}-{version}.bin");
let gsp: PathBuf = output.join(format!("{gpu}/gsp"));
fs::create_dir_all(&gsp)
.map_err(|err| format!("Could not create nvidia/{gpu}/gsp/: {err}"))?;
// Extract the actual booter firmware
let array = format!("kgspBinArchiveBooter{load_upper}Ucode_{gpu_upper}_image_prod_data");
let firmware = get_bytes(&filename, &array, None)?;
let firmware_size = firmware.len() as u32;
// Extract the signatures
let array = format!("kgspBinArchiveBooter{load_upper}Ucode_{gpu_upper}_sig_prod_data");
let signatures = get_bytes(&filename, &array, None)?;
let signatures_size = signatures.len() as u32;
if (signatures_size % sigsize) != 0 {
return Err(format!("signature file size for {array} is uneven value of {sigsize}"));
}
let num_sigs = signatures_size / sigsize;
if num_sigs < 1 {
return Err(format!("invalid number of signatures: {num_sigs}"));
}
// Extract the patch location
let array = format!("kgspBinArchiveBooter{load_upper}Ucode_{gpu_upper}_patch_loc_data");
let patch_loc_data = get_bytes(&filename, &array, Some(4))?;
let patch_loc = LittleEndian::read_u32(&patch_loc_data);
// Extract the patch meta variables
let array = format!("kgspBinArchiveBooter{load_upper}Ucode_{gpu_upper}_patch_meta_data");
let patch_meta_data = get_bytes(&filename, &array, Some(12))?;
let (fuse_ver, engine_id, ucode_id) = patch_meta_data
.chunks(4)
.map(LittleEndian::read_u32)
.collect_tuple()
.unwrap();
let binfile: PathBuf = gsp.join(format!("booter_{load}-{version}.bin"));
let mut file = fs::OpenOptions::new().write(true).create(true).open(&binfile)
.map_err(|err| format!("Could not create {}: {err}", binfile.display()))?;
// First, add the nvfw_bin_hdr header
let total_size: u32 = round_up_to_base(120 + signatures_size + firmware_size, 256);
let firmware_offset: u32 = 120 + signatures_size;
let nvfw_bin_hdr = [0x10de, 1, total_size, 24, firmware_offset, firmware_size]
.iter()
.flat_map(|x| x.to_le_bytes())
.collect::<Vec<u8>>();
file.write_all(&nvfw_bin_hdr)
.map_err(|err| format!("Could not write to {}: {err}", filename.display()))?;
// Second, add the nvfw_hs_header_v2 header
let patch_loc_offset = 60 + signatures_size;
let patch_sig_offset = patch_loc_offset + 4;
let meta_data_offset = patch_sig_offset + 4;
let num_sig_offset = meta_data_offset + 12;
let header_offset = num_sig_offset + 4;
let nvfw_hs_header_v2 = [
60,
signatures_size,
patch_loc_offset,
patch_sig_offset,
meta_data_offset,
12,
num_sig_offset,
header_offset,
36,
]
.iter()
.flat_map(|x| x.to_le_bytes())
.collect::<Vec<u8>>();
file.write_all(&nvfw_hs_header_v2)
.map_err(|err| format!("Could not write to {}: {err}", filename.display()))?;
// Third, the actual signatures
file.write_all(&signatures)
.map_err(|err| format!("Could not write to {}: {err}", filename.display()))?;
// Fourth, patch_loc[], patch_sig[], fuse_ver, engine_id, ucode_id, and num_sigs
let patch_meta = [patch_loc, 0, fuse_ver, engine_id, ucode_id, num_sigs]
.iter()
.flat_map(|x| x.to_le_bytes())
.collect::<Vec<u8>>();
file.write_all(&patch_meta)
.map_err(|err| format!("Could not write to {}: {err}", filename.display()))?;
// Extract the descriptor (nvkm_gsp_booter_fw_hdr)
let array = format!("kgspBinArchiveBooter{load_upper}Ucode_{gpu_upper}_header_prod_data");
let descriptor = get_bytes(&filename, &array, Some(36))?;
// Fifth, the descriptor
file.write_all(&descriptor)
.map_err(|err| format!("Could not write to {}: {err}", filename.display()))?;
// And finally, the actual scrubber image
file.write_all(&firmware)
.map_err(|err| format!("Could not write to {}: {err}", filename.display()))?;
file.sync_all()
.map_err(|err| format!("Failed to sync {}: {err}", filename.display()))?;
Ok(())
}
/// GPU memory scrubber, needed for some GPUs and configurations
#[allow(dead_code)]
fn scrubber(output: &Path, version: &str, gpu: &str, sigsize: u32) -> Result<(), String> {
// Unfortunately, RM breaks convention with the scrubber image and labels
// the files and arrays with AD10X instead of AD102.
let gpux = gpu.replace_last_with_x().to_uppercase();
let filename = PathBuf::from(
format!("src/nvidia/generated/g_bindata_ksec2GetBinArchiveSecurescrubUcode_{gpux}.c"));
println!("Creating nvidia/{gpu}/gsp/scrubber-{version}.bin");
let gsp: PathBuf = output.join(format!("{gpu}/gsp"));
fs::create_dir_all(&gsp)
.map_err(|err| format!("Could not create nvidia/{gpu}/gsp/: {err}"))?;
// Extract the actual scrubber firmware
let array = format!("ksec2BinArchiveSecurescrubUcode_{gpux}_image_prod_data");
let firmware = get_bytes(&filename, &array, None)?;
let firmware_size = firmware.len() as u32;
// Extract the signatures
let array = format!("ksec2BinArchiveSecurescrubUcode_{gpux}_sig_prod_data");
let signatures = get_bytes(&filename, &array, None)?;
let signatures_size = signatures.len() as u32;
if (signatures_size % sigsize) != 0 {
return Err(format!("signature file size for {array} is uneven value of {sigsize}"));
}
let num_sigs = signatures_size / sigsize;
if num_sigs < 1 {
return Err(format!("invalid number of signatures: {num_sigs}"));
}
// Extract the patch location
let array = format!("ksec2BinArchiveSecurescrubUcode_{gpux}_patch_loc_data");
let patch_loc_data = get_bytes(&filename, &array, Some(4))?;
let patch_loc = LittleEndian::read_u32(&patch_loc_data);
// Extract the patch meta variables
let array = format!("ksec2BinArchiveSecurescrubUcode_{gpux}_patch_meta_data");
let patch_meta_data = get_bytes(&filename, &array, Some(12))?;
let (fuse_ver, engine_id, ucode_id) = patch_meta_data
.chunks(4)
.map(LittleEndian::read_u32)
.collect_tuple()
.unwrap();
let binfile = gsp.join(format!("scrubber-{version}.bin"));
let mut file = fs::OpenOptions::new().write(true).create(true).open(&binfile)
.map_err(|err| format!("Could not create {}: {err}", binfile.display()))?;
// First, add the nvfw_bin_hdr header
let total_size: u32 = round_up_to_base(120 + signatures_size + firmware_size, 256);
let firmware_offset: u32 = 120 + signatures_size;
let nvfw_bin_hdr = [0x10de, 1, total_size, 24, firmware_offset, firmware_size]
.iter()
.flat_map(|x| x.to_le_bytes())
.collect::<Vec<u8>>();
file.write_all(&nvfw_bin_hdr)
.map_err(|err| format!("Could not write to {}: {err}", filename.display()))?;
// Second, add the nvfw_hs_header_v2 header
let patch_loc_offset = 60 + signatures_size;
let patch_sig_offset = patch_loc_offset + 4;
let meta_data_offset = patch_sig_offset + 4;
let num_sig_offset = meta_data_offset + 12;
let header_offset = num_sig_offset + 4;
let nvfw_hs_header_v2 = [
60,
signatures_size,
patch_loc_offset,
patch_sig_offset,
meta_data_offset,
12,
num_sig_offset,
header_offset,
36,
]
.iter()
.flat_map(|x| x.to_le_bytes())
.collect::<Vec<u8>>();
file.write_all(&nvfw_hs_header_v2)
.map_err(|err| format!("Could not write to {}: {err}", filename.display()))?;
// Third, the actual signatures
file.write_all(&signatures)
.map_err(|err| format!("Could not write to {}: {err}", filename.display()))?;
// Fourth, patch_loc[], patch_sig[], fuse_ver, engine_id, ucode_id, and num_sigs
let patch_meta = [patch_loc, 0, fuse_ver, engine_id, ucode_id, num_sigs]
.iter()
.flat_map(|x| x.to_le_bytes())
.collect::<Vec<u8>>();
file.write_all(&patch_meta)
.map_err(|err| format!("Could not write to {}: {err}", filename.display()))?;
// Extract the descriptor (nvkm_gsp_booter_fw_hdr)
let array = format!("ksec2BinArchiveSecurescrubUcode_{gpux}_header_prod_data");
let descriptor = get_bytes(&filename, &array, Some(36))?;
// Fifth, the descriptor
file.write_all(&descriptor)
.map_err(|err| format!("Could not write to {}: {err}", filename.display()))?;
// And finally, the actual scrubber image
file.write_all(&firmware)
.map_err(|err| format!("Could not write to {}: {err}", filename.display()))?;
file.sync_all()
.map_err(|err| format!("Failed to sync {}: {err}", filename.display()))?;
Ok(())
}
fn main() -> ExitCode {
let args = Cli::parse();
// If -i was specified, then we start by chdir to that path
if let Err(err) = env::set_current_dir(&args.input) {
println!("Error: could not chdir to {}: {err}", args.input.display());
return ExitCode::from(2);
}
// version.mk must exist in the current directory
match fs::exists("version.mk") {
Ok(false) | Err(_) => {
println!("Error: version.mk not found");
return ExitCode::from(2);
}
_ => (),
}
// If -r is passed, then use that as the version number.
// Otherwise, extract it from version.mk
let version: String = if let Some(r) = &args.revision {
r.clone()
} else {
let v = find_version();
if v == None {
println!("Error: could not determine firmware version");
return ExitCode::from(1);
}
v.unwrap()
};
println!("Generating files for version {version}");
// Create the "nvidia" subdir where all the files will go
let output = args.output.join("nvidia");
match fs::create_dir(&output) {
Ok(_) => println!("Writing files to {}.", output.display()),
Err(e) if e.kind() == std::io::ErrorKind::AlreadyExists =>
println!("Overwriting files in {}", output.display()),
Err(e) => {
println!("Error: could not create \"nvidia\" target directory: {e}");
return ExitCode::from(1);
}
}
let booters = [
("tu102", "load", 16),
("tu102", "unload", 16),
("tu116", "load", 16),
("tu116", "unload", 16),
("ga100", "load", 384),
("ga100", "unload", 384),
("ga102", "load", 384),
("ga102", "unload", 384),
("ad102", "load", 384),
("ad102", "unload", 384),
];
for (gpu, load, sigsize) in booters {
if let Err(err) = booter(&output, &version, gpu, load, sigsize) {
println!("{err}");
return ExitCode::from(1);
}
}
let bootloaders = [
("tu102", ""),
("ga100", ""),
("ga102", "prod_"),
("ad102", "prod_"),
];
for (gpu, prod) in bootloaders {
if let Err(err) = bootloader(&output, &version, gpu, prod) {
println!("{err}");
return ExitCode::from(1);
}
}
// Scrubber is currently not used, but let's generate the files anyway
if let Err(err) = scrubber(&output, &version, "ad102", 384) {
println!("{err}");
return ExitCode::from(1);
}
if args.driver.is_some() {
let driver = args.driver.unwrap();
println!("Extracting GSP-RM firmware from {}", driver.display());
if let Err(err) = gsp_firmware(&output, &version, &driver) {
println!("{err}");
return ExitCode::from(1);
}
}
if args.whence {
// Unlike all other output, the whence file goes in the root of the target
// directory.
println!("Creating {}/WHENCE.txt", args.output.display());
if let Err(err) = whence(&args.output, &version) {
println!("{err}");
return ExitCode::from(1);
}
}
if args.symlink {
println!("Creating symlinks in {}", output.display());
if let Err(err) = symlinks(&output, &version) {
println!("{err}");
return ExitCode::from(1);
}
}
ExitCode::SUCCESS
}

Binary file not shown.