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