1#!/usr/bin/env python3 2 3# SPDX-FileCopyrightText: Copyright (c) 2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. 4# SPDX-License-Identifier: MIT 5# 6# Permission is hereby granted, free of charge, to any person obtaining a 7# copy of this software and associated documentation files (the "Software"), 8# to deal in the Software without restriction, including without limitation 9# the rights to use, copy, modify, merge, publish, distribute, sublicense, 10# and/or sell copies of the Software, and to permit persons to whom the 11# Software is furnished to do so, subject to the following conditions: 12# 13# The above copyright notice and this permission notice shall be included in 14# all copies or substantial portions of the Software. 15# 16# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL 19# THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 21# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 22# DEALINGS IN THE SOFTWARE. 23 24# Converts OpenRM binhex-encoded images to Nouveau-compatible binary blobs 25# See nouveau_firmware_layout.ods for documentation on the file format 26 27import sys 28import os 29import argparse 30import shutil 31import re 32import gzip 33import struct 34 35class MyException(Exception): 36 pass 37 38def round_up_to_base(x, base = 10): 39 return x + (base - x) % base 40 41def getbytes(filename, array): 42 """Extract the bytes for the given array in the given file. 43 44 :param filename: the file to parse 45 :param array: the name of the array to parse 46 :returns: byte array 47 48 This function scans the file for the array and returns a bytearray of 49 its contents, uncompressing the data if it is tagged as compressed. 50 51 This function assumes that each array is immediately preceded with a comment 52 section that specifies whether the array is compressed and how many bytes of 53 data there should be. Example: 54 55 #if defined(BINDATA_INCLUDE_DATA) 56 // 57 // FUNCTION: ksec2GetBinArchiveSecurescrubUcode_AD10X("header_prod") 58 // FILE NAME: kernel/inc/securescrub/bin/ad10x/g_securescrubuc_sec2_ad10x_boot_from_hs_prod.h 59 // FILE TYPE: TEXT 60 // VAR NAME: securescrub_ucode_header_ad10x_boot_from_hs 61 // COMPRESSION: YES 62 // COMPLEX_STRUCT: NO 63 // DATA SIZE (bytes): 36 64 // COMPRESSED SIZE (bytes): 27 65 // 66 static BINDATA_CONST NvU8 ksec2BinArchiveSecurescrubUcode_AD10X_header_prod_data[] = 67 { 68 0x63, 0x60, 0x00, 0x02, 0x46, 0x20, 0x96, 0x02, 0x62, 0x66, 0x08, 0x13, 0x4c, 0x48, 0x42, 0x69, 69 0x20, 0x00, 0x00, 0x30, 0x39, 0x0a, 0xfc, 0x24, 0x00, 0x00, 0x00, 70 }; 71 #endif // defined(BINDATA_INCLUDE_DATA) 72 """ 73 74 with open(filename) as f: 75 for line in f: 76 if "COMPRESSION: NO" in line: 77 compressed = False 78 if "COMPRESSION: YES" in line: 79 compressed = True 80 m = re.search("DATA SIZE \(bytes\): (\d+)", line) 81 if m: 82 data_size = int(m.group(1)) 83 m = re.search("COMPRESSED SIZE \(bytes\): (\d+)", line) 84 if m: 85 compressed_size = int(m.group(1)) 86 if "static BINDATA_CONST NvU8 " + array in line: 87 break 88 else: 89 raise MyException(f"array {array} not found in {filename}") 90 91 output = b'' 92 for line in f: 93 if "};" in line: 94 break 95 bytes = [int(b, 16) for b in re.findall('0x[0-9a-f][0-9a-f]', line)] 96 if len(bytes) > 0: 97 output += struct.pack(f"{len(bytes)}B", *bytes) 98 99 if len(output) == 0: 100 raise MyException(f"no data found for {array}") 101 102 if compressed: 103 if len(output) != compressed_size: 104 raise MyException(f"compressed array {array} in {filename} should be {compressed_size} bytes but is actually {len(output)}.") 105 gzipheader = struct.pack("<4BL2B", 0x1f, 0x8b, 8, 0, 0, 0, 3) 106 output = gzip.decompress(gzipheader + output) 107 if len(output) != data_size: 108 raise MyException(f"array {array} in {filename} decompressed to {len(output)} bytes but should have been {data_size} bytes.") 109 return output 110 else: 111 if len(output) != data_size: 112 raise MyException(f"array {array} in {filename} should be {compressed_size} bytes but is actually {len(output)}.") 113 return output 114 115# GSP bootloader 116def bootloader(gpu, type): 117 global outputpath 118 global version 119 120 GPU=gpu.upper() 121 filename = f"src/nvidia/generated/g_bindata_kgspGetBinArchiveGspRmBoot_{GPU}.c" 122 123 print(f"Creating nvidia/{gpu}/gsp/bootloader-{version}.bin") 124 os.makedirs(f"{outputpath}/nvidia/{gpu}/gsp/", exist_ok = True) 125 126 with open(f"{outputpath}/nvidia/{gpu}/gsp/bootloader-{version}.bin", "wb") as f: 127 # Extract the actual bootloader firmware 128 array = f"kgspBinArchiveGspRmBoot_{GPU}_ucode_image{type}data" 129 firmware = getbytes(filename, array) 130 firmware_size = len(firmware) 131 132 # Extract the descriptor (RM_RISCV_UCODE_DESC) 133 array = f"kgspBinArchiveGspRmBoot_{GPU}_ucode_desc{type}data" 134 descriptor = getbytes(filename, array) 135 descriptor_size = len(descriptor) 136 137 # First, add the nvfw_bin_hdr header 138 total_size = round_up_to_base(24 + firmware_size + descriptor_size, 256) 139 firmware_offset = 24 + descriptor_size 140 f.write(struct.pack("<6L", 0x10de, 1, total_size, 24, firmware_offset, firmware_size)) 141 142 # Second, add the descriptor 143 f.write(descriptor) 144 145 # Finally, the actual bootloader image 146 f.write(firmware) 147 148# GSP Booter load and unload 149def booter(gpu, load, sigsize): 150 global outputpath 151 global version 152 153 GPU = gpu.upper() 154 LOAD = load.capitalize() 155 156 filename = f"src/nvidia/generated/g_bindata_kgspGetBinArchiveBooter{LOAD}Ucode_{GPU}.c" 157 158 print(f"Creating nvidia/{gpu}/gsp/booter_{load}-{version}.bin") 159 os.makedirs(f"{outputpath}/nvidia/{gpu}/gsp/", exist_ok = True) 160 161 with open(f"{outputpath}/nvidia/{gpu}/gsp/booter_{load}-{version}.bin", "wb") as f: 162 # Extract the actual scrubber firmware 163 array = f"kgspBinArchiveBooter{LOAD}Ucode_{GPU}_image_prod_data" 164 firmware = getbytes(filename, array) 165 firmware_size = len(firmware) 166 167 # Extract the signatures 168 array = f"kgspBinArchiveBooter{LOAD}Ucode_{GPU}_sig_prod_data" 169 signatures = getbytes(filename, array) 170 signatures_size = len(signatures) 171 if signatures_size % sigsize: 172 raise MyException(f"signature file size for {array} is uneven value of {sigsize}") 173 num_sigs = int(signatures_size / sigsize); 174 if num_sigs < 1: 175 raise MyException(f"invalid number of signatures {num_sigs}") 176 177 # First, add the nvfw_bin_hdr header 178 total_size = round_up_to_base(120 + signatures_size + firmware_size, 256) 179 firmware_offset = 120 + signatures_size 180 f.write(struct.pack("<6L", 0x10de, 1, total_size, 24, firmware_offset, firmware_size)) 181 182 # Second, add the nvfw_hs_header_v2 header 183 patch_loc_offset = 60 + signatures_size 184 patch_sig_offset = patch_loc_offset + 4 185 meta_data_offset = patch_sig_offset + 4 186 num_sig_offset = meta_data_offset + 12 187 header_offset = num_sig_offset + 4 188 f.write(struct.pack("<9L", 60, signatures_size, patch_loc_offset, 189 patch_sig_offset, meta_data_offset, 12, 190 num_sig_offset, header_offset, 36)) 191 192 # Third, the actual signatures 193 f.write(signatures) 194 195 # Extract the patch location 196 array = f"kgspBinArchiveBooter{LOAD}Ucode_{GPU}_patch_loc_data" 197 bytes = getbytes(filename, array) 198 patchloc = struct.unpack("<L", bytes)[0] 199 200 # Extract the patch meta variables 201 array = f"kgspBinArchiveBooter{LOAD}Ucode_{GPU}_patch_meta_data" 202 bytes = getbytes(filename, array) 203 fuse_ver, engine_id, ucode_id = struct.unpack("<LLL", bytes) 204 205 # Fourth, patch_loc[], patch_sig[], fuse_ver, engine_id, ucode_id, and num_sigs 206 f.write(struct.pack("<6L", patchloc, 0, fuse_ver, engine_id, ucode_id, num_sigs)) 207 208 # Extract the descriptor (nvkm_gsp_booter_fw_hdr) 209 array = f"kgspBinArchiveBooter{LOAD}Ucode_{GPU}_header_prod_data" 210 descriptor = getbytes(filename, array) 211 212 # Fifth, the descriptor 213 f.write(descriptor) 214 215 # And finally, the actual scrubber image 216 f.write(firmware) 217 218# GPU memory scrubber, needed for some GPUs and configurations 219def scrubber(gpu, sigsize): 220 global outputpath 221 global version 222 223 # Unfortunately, RM breaks convention with the scrubber image and labels 224 # the files and arrays with AD10X instead of AD102. 225 GPUX = f"{gpu[:-1].upper()}X" 226 227 filename = f"src/nvidia/generated/g_bindata_ksec2GetBinArchiveSecurescrubUcode_{GPUX}.c" 228 229 print(f"Creating nvidia/{gpu}/gsp/scrubber-{version}.bin") 230 os.makedirs(f"{outputpath}/nvidia/{gpu}/gsp/", exist_ok = True) 231 232 with open(f"{outputpath}/nvidia/{gpu}/gsp/scrubber-{version}.bin", "wb") as f: 233 # Extract the actual scrubber firmware 234 array = f"ksec2BinArchiveSecurescrubUcode_{GPUX}_image_prod_data[]" 235 firmware = getbytes(filename, array) 236 firmware_size = len(firmware) 237 238 # Extract the signatures 239 array = f"ksec2BinArchiveSecurescrubUcode_{GPUX}_sig_prod_data" 240 signatures = getbytes(filename, array) 241 signatures_size = len(signatures) 242 if signatures_size % sigsize: 243 raise MyException(f"signature file size for {array} is uneven value of {sigsize}") 244 num_sigs = int(signatures_size / sigsize); 245 if num_sigs < 1: 246 raise MyException(f"invalid number of signatures {num_sigs}") 247 248 # First, add the nvfw_bin_hdr header 249 total_size = round_up_to_base(120 + signatures_size + firmware_size, 256) 250 firmware_offset = 120 + signatures_size 251 f.write(struct.pack("<6L", 0x10de, 1, total_size, 24, firmware_offset, firmware_size)) 252 253 # Second, add the nvfw_hs_header_v2 header 254 patch_loc_offset = 60 + signatures_size 255 patch_sig_offset = patch_loc_offset + 4 256 meta_data_offset = patch_sig_offset + 4 257 num_sig_offset = meta_data_offset + 12 258 header_offset = num_sig_offset + 4 259 f.write(struct.pack("<9L", 60, signatures_size, patch_loc_offset, 260 patch_sig_offset, meta_data_offset, 12, 261 num_sig_offset, header_offset, 36)) 262 263 # Third, the actual signatures 264 f.write(signatures) 265 266 # Extract the patch location 267 array = f"ksec2BinArchiveSecurescrubUcode_{GPUX}_patch_loc_data" 268 bytes = getbytes(filename, array) 269 patchloc = struct.unpack("<L", bytes)[0] 270 271 # Extract the patch meta variables 272 array = f"ksec2BinArchiveSecurescrubUcode_{GPUX}_patch_meta_data" 273 bytes = getbytes(filename, array) 274 fuse_ver, engine_id, ucode_id = struct.unpack("<LLL", bytes) 275 276 # Fourth, patch_loc[], patch_sig[], fuse_ver, engine_id, ucode_id, and num_sigs 277 f.write(struct.pack("<6L", patchloc, 0, fuse_ver, engine_id, ucode_id, num_sigs)) 278 279 # Extract the descriptor (nvkm_gsp_booter_fw_hdr) 280 array = f"ksec2BinArchiveSecurescrubUcode_{GPUX}_header_prod_data" 281 descriptor = getbytes(filename, array) 282 283 # Fifth, the descriptor 284 f.write(descriptor) 285 286 # And finally, the actual scrubber image 287 f.write(firmware) 288 289def main(): 290 global outputpath 291 global version 292 293 parser = argparse.ArgumentParser( 294 description = 'Extract firmware binaries from the OpenRM git repository' 295 ' in a format expected by the Nouveau device driver.') 296 parser.add_argument('-i', '--input', default = os.getcwd(), 297 help = 'Path to source directory (where version.mk exists)') 298 parser.add_argument('-o', '--output', default = os.path.abspath(os.getcwd() + '/_out'), 299 help = 'Path to target directory (where files will be written)') 300 args = parser.parse_args() 301 302 os.chdir(args.input) 303 304 with open("version.mk") as f: 305 version = re.search(r'^NVIDIA_VERSION = ([^\s]+)', f.read(), re.MULTILINE).group(1) 306 print(f"Generating files for version {version}") 307 308 # Normal version strings are of the format xxx.yy.zz, which are all 309 # numbers. If it's a normal version string, convert it to a single number, 310 # as Nouveau currently expects. Otherwise, leave it alone. 311 if set(version) <= set('0123456789.'): 312 version = version.replace(".", "") 313 314 outputpath = args.output; 315 print(f"Writing files to {outputpath}") 316 317 os.makedirs(f"{outputpath}/nvidia", exist_ok = True) 318 319 booter("tu102", "load", 16) 320 booter("tu102", "unload", 16) 321 bootloader("tu102", "_") 322 323 booter("tu116", "load", 16) 324 booter("tu116", "unload", 16) 325 # TU11x uses the same bootloader as TU10x 326 327 booter("ga100", "load", 384) 328 booter("ga100", "unload", 384) 329 bootloader("ga100", "_") 330 331 booter("ga102", "load", 384) 332 booter("ga102", "unload", 384) 333 bootloader("ga102", "_prod_") 334 335 booter("ad102", "load", 384) 336 booter("ad102", "unload", 384) 337 bootloader("ad102", "_prod_") 338 # scrubber("ad102", 384) # Not currently used by Nouveau 339 340if __name__ == "__main__": 341 main() 342 343