1# This file includes the operations with eFuses for ESP32-C6 chip
2#
3# SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD
4#
5# SPDX-License-Identifier: GPL-2.0-or-later
6
7import argparse
8import os  # noqa: F401. It is used in IDF scripts
9import traceback
10
11import espsecure
12
13import esptool
14
15from . import fields
16from .. import util
17from ..base_operations import (
18    add_common_commands,
19    add_force_write_always,
20    add_show_sensitive_info_option,
21    burn_bit,
22    burn_block_data,
23    burn_efuse,
24    check_error,
25    dump,
26    read_protect_efuse,
27    summary,
28    write_protect_efuse,
29)
30
31
32def protect_options(p):
33    p.add_argument(
34        "--no-write-protect",
35        help="Disable write-protecting of the key. The key remains writable. "
36        "(The keys use the RS coding scheme that does not support "
37        "post-write data changes. Forced write can damage RS encoding bits.) "
38        "The write-protecting of keypurposes does not depend on the option, "
39        "it will be set anyway.",
40        action="store_true",
41    )
42    p.add_argument(
43        "--no-read-protect",
44        help="Disable read-protecting of the key. The key remains readable software."
45        "The key with keypurpose[USER, RESERVED and *_DIGEST] "
46        "will remain readable anyway. For the rest keypurposes the read-protection "
47        "will be defined the option (Read-protect by default).",
48        action="store_true",
49    )
50
51
52def add_commands(subparsers, efuses):
53    add_common_commands(subparsers, efuses)
54    burn_key = subparsers.add_parser(
55        "burn_key", help="Burn the key block with the specified name"
56    )
57    protect_options(burn_key)
58    add_force_write_always(burn_key)
59    add_show_sensitive_info_option(burn_key)
60    burn_key.add_argument(
61        "block",
62        help="Key block to burn",
63        action="append",
64        choices=efuses.BLOCKS_FOR_KEYS,
65    )
66    burn_key.add_argument(
67        "keyfile",
68        help="File containing 256 bits of binary key data",
69        action="append",
70        type=argparse.FileType("rb"),
71    )
72    burn_key.add_argument(
73        "keypurpose",
74        help="Purpose to set.",
75        action="append",
76        choices=fields.EfuseKeyPurposeField.KEY_PURPOSES_NAME,
77    )
78    for _ in efuses.BLOCKS_FOR_KEYS:
79        burn_key.add_argument(
80            "block",
81            help="Key block to burn",
82            nargs="?",
83            action="append",
84            metavar="BLOCK",
85            choices=efuses.BLOCKS_FOR_KEYS,
86        )
87        burn_key.add_argument(
88            "keyfile",
89            help="File containing 256 bits of binary key data",
90            nargs="?",
91            action="append",
92            metavar="KEYFILE",
93            type=argparse.FileType("rb"),
94        )
95        burn_key.add_argument(
96            "keypurpose",
97            help="Purpose to set.",
98            nargs="?",
99            action="append",
100            metavar="KEYPURPOSE",
101            choices=fields.EfuseKeyPurposeField.KEY_PURPOSES_NAME,
102        )
103
104    burn_key_digest = subparsers.add_parser(
105        "burn_key_digest",
106        help="Parse a RSA public key and burn the digest to key efuse block",
107    )
108    protect_options(burn_key_digest)
109    add_force_write_always(burn_key_digest)
110    add_show_sensitive_info_option(burn_key_digest)
111    burn_key_digest.add_argument(
112        "block",
113        help="Key block to burn",
114        action="append",
115        choices=efuses.BLOCKS_FOR_KEYS,
116    )
117    burn_key_digest.add_argument(
118        "keyfile",
119        help="Key file to digest (PEM format)",
120        action="append",
121        type=argparse.FileType("rb"),
122    )
123    burn_key_digest.add_argument(
124        "keypurpose",
125        help="Purpose to set.",
126        action="append",
127        choices=fields.EfuseKeyPurposeField.DIGEST_KEY_PURPOSES,
128    )
129    for _ in efuses.BLOCKS_FOR_KEYS:
130        burn_key_digest.add_argument(
131            "block",
132            help="Key block to burn",
133            nargs="?",
134            action="append",
135            metavar="BLOCK",
136            choices=efuses.BLOCKS_FOR_KEYS,
137        )
138        burn_key_digest.add_argument(
139            "keyfile",
140            help="Key file to digest (PEM format)",
141            nargs="?",
142            action="append",
143            metavar="KEYFILE",
144            type=argparse.FileType("rb"),
145        )
146        burn_key_digest.add_argument(
147            "keypurpose",
148            help="Purpose to set.",
149            nargs="?",
150            action="append",
151            metavar="KEYPURPOSE",
152            choices=fields.EfuseKeyPurposeField.DIGEST_KEY_PURPOSES,
153        )
154
155    p = subparsers.add_parser(
156        "set_flash_voltage",
157        help="Permanently set the internal flash voltage regulator "
158        "to either 1.8V, 3.3V or OFF. "
159        "This means GPIO45 can be high or low at reset without "
160        "changing the flash voltage.",
161    )
162    p.add_argument("voltage", help="Voltage selection", choices=["1.8V", "3.3V", "OFF"])
163
164    p = subparsers.add_parser(
165        "burn_custom_mac", help="Burn a 48-bit Custom MAC Address to EFUSE BLOCK3."
166    )
167    p.add_argument(
168        "mac",
169        help="Custom MAC Address to burn given in hexadecimal format with bytes "
170        "separated by colons (e.g. AA:CD:EF:01:02:03).",
171        type=fields.base_fields.CheckArgValue(efuses, "CUSTOM_MAC"),
172    )
173    add_force_write_always(p)
174
175    p = subparsers.add_parser("get_custom_mac", help="Prints the Custom MAC Address.")
176
177
178def burn_custom_mac(esp, efuses, args):
179    efuses["CUSTOM_MAC"].save(args.mac)
180    if not efuses.burn_all(check_batch_mode=True):
181        return
182    get_custom_mac(esp, efuses, args)
183    print("Successful")
184
185
186def get_custom_mac(esp, efuses, args):
187    print("Custom MAC Address: {}".format(efuses["CUSTOM_MAC"].get()))
188
189
190def set_flash_voltage(esp, efuses, args):
191    raise esptool.FatalError("set_flash_voltage is not supported!")
192
193
194def adc_info(esp, efuses, args):
195    print("")
196    # fmt: off
197    if efuses["BLK_VERSION_MINOR"].get() == 1:
198        print("Temperature Sensor Calibration = {}C".format(efuses["TEMP_CALIB"].get()))
199        print("ADC OCode             = ", efuses["OCODE"].get())
200        print("ADC1:")
201        print("INIT_CODE_ATTEN0     = ", efuses['ADC1_INIT_CODE_ATTEN0'].get())
202        print("INIT_CODE_ATTEN1     = ", efuses['ADC1_INIT_CODE_ATTEN1'].get())
203        print("INIT_CODE_ATTEN2     = ", efuses['ADC1_INIT_CODE_ATTEN2'].get())
204        print("INIT_CODE_ATTEN3     = ", efuses['ADC1_INIT_CODE_ATTEN3'].get())
205        print("CAL_VOL_ATTEN0       = ", efuses['ADC1_CAL_VOL_ATTEN0'].get())
206        print("CAL_VOL_ATTEN1       = ", efuses['ADC1_CAL_VOL_ATTEN1'].get())
207        print("CAL_VOL_ATTEN2       = ", efuses['ADC1_CAL_VOL_ATTEN2'].get())
208        print("CAL_VOL_ATTEN3       = ", efuses['ADC1_CAL_VOL_ATTEN3'].get())
209        print("INIT_CODE_ATTEN0_CH0 = ", efuses['ADC1_INIT_CODE_ATTEN0_CH0'].get())
210        print("INIT_CODE_ATTEN0_CH1 = ", efuses['ADC1_INIT_CODE_ATTEN0_CH1'].get())
211        print("INIT_CODE_ATTEN0_CH2 = ", efuses['ADC1_INIT_CODE_ATTEN0_CH2'].get())
212        print("INIT_CODE_ATTEN0_CH3 = ", efuses['ADC1_INIT_CODE_ATTEN0_CH3'].get())
213        print("INIT_CODE_ATTEN0_CH4 = ", efuses['ADC1_INIT_CODE_ATTEN0_CH4'].get())
214        print("INIT_CODE_ATTEN0_CH5 = ", efuses['ADC1_INIT_CODE_ATTEN0_CH5'].get())
215        print("INIT_CODE_ATTEN0_CH6 = ", efuses['ADC1_INIT_CODE_ATTEN0_CH6'].get())
216    else:
217        print("BLK_VERSION_MINOR = {}".format(efuses["BLK_VERSION_MINOR"].get_meaning()))
218    # fmt: on
219
220
221def burn_key(esp, efuses, args, digest=None):
222    if digest is None:
223        datafile_list = args.keyfile[
224            0 : len([name for name in args.keyfile if name is not None]) :
225        ]
226    else:
227        datafile_list = digest[0 : len([name for name in digest if name is not None]) :]
228    efuses.force_write_always = args.force_write_always
229    block_name_list = args.block[
230        0 : len([name for name in args.block if name is not None]) :
231    ]
232    keypurpose_list = args.keypurpose[
233        0 : len([name for name in args.keypurpose if name is not None]) :
234    ]
235
236    util.check_duplicate_name_in_list(block_name_list)
237    if len(block_name_list) != len(datafile_list) or len(block_name_list) != len(
238        keypurpose_list
239    ):
240        raise esptool.FatalError(
241            "The number of blocks (%d), datafile (%d) and keypurpose (%d) "
242            "should be the same."
243            % (len(block_name_list), len(datafile_list), len(keypurpose_list))
244        )
245
246    print("Burn keys to blocks:")
247    for block_name, datafile, keypurpose in zip(
248        block_name_list, datafile_list, keypurpose_list
249    ):
250        efuse = None
251        for block in efuses.blocks:
252            if block_name == block.name or block_name in block.alias:
253                efuse = efuses[block.name]
254        if efuse is None:
255            raise esptool.FatalError("Unknown block name - %s" % (block_name))
256        num_bytes = efuse.bit_len // 8
257
258        block_num = efuses.get_index_block_by_name(block_name)
259        block = efuses.blocks[block_num]
260
261        if digest is None:
262            data = datafile.read()
263        else:
264            data = datafile
265
266        print(" - %s" % (efuse.name), end=" ")
267        revers_msg = None
268        if efuses[block.key_purpose_name].need_reverse(keypurpose):
269            revers_msg = "\tReversing byte order for AES-XTS hardware peripheral"
270            data = data[::-1]
271        print(
272            "-> [{}]".format(
273                util.hexify(data, " ")
274                if args.show_sensitive_info
275                else " ".join(["??"] * len(data))
276            )
277        )
278        if revers_msg:
279            print(revers_msg)
280        if len(data) != num_bytes:
281            raise esptool.FatalError(
282                "Incorrect key file size %d. Key file must be %d bytes (%d bits) "
283                "of raw binary key data." % (len(data), num_bytes, num_bytes * 8)
284            )
285
286        if efuses[block.key_purpose_name].need_rd_protect(keypurpose):
287            read_protect = False if args.no_read_protect else True
288        else:
289            read_protect = False
290        write_protect = not args.no_write_protect
291
292        # using efuse instead of a block gives the advantage of checking it as the whole field.
293        efuse.save(data)
294
295        disable_wr_protect_key_purpose = False
296        if efuses[block.key_purpose_name].get() != keypurpose:
297            if efuses[block.key_purpose_name].is_writeable():
298                print(
299                    "\t'%s': '%s' -> '%s'."
300                    % (
301                        block.key_purpose_name,
302                        efuses[block.key_purpose_name].get(),
303                        keypurpose,
304                    )
305                )
306                efuses[block.key_purpose_name].save(keypurpose)
307                disable_wr_protect_key_purpose = True
308            else:
309                raise esptool.FatalError(
310                    "It is not possible to change '%s' to '%s' "
311                    "because write protection bit is set."
312                    % (block.key_purpose_name, keypurpose)
313                )
314        else:
315            print("\t'%s' is already '%s'." % (block.key_purpose_name, keypurpose))
316            if efuses[block.key_purpose_name].is_writeable():
317                disable_wr_protect_key_purpose = True
318
319        if disable_wr_protect_key_purpose:
320            print("\tDisabling write to '%s'." % block.key_purpose_name)
321            efuses[block.key_purpose_name].disable_write()
322
323        if read_protect:
324            print("\tDisabling read to key block")
325            efuse.disable_read()
326
327        if write_protect:
328            print("\tDisabling write to key block")
329            efuse.disable_write()
330        print("")
331
332    if not write_protect:
333        print("Keys will remain writeable (due to --no-write-protect)")
334    if args.no_read_protect:
335        print("Keys will remain readable (due to --no-read-protect)")
336
337    if not efuses.burn_all(check_batch_mode=True):
338        return
339    print("Successful")
340
341
342def burn_key_digest(esp, efuses, args):
343    digest_list = []
344    datafile_list = args.keyfile[
345        0 : len([name for name in args.keyfile if name is not None]) :
346    ]
347    block_list = args.block[
348        0 : len([block for block in args.block if block is not None]) :
349    ]
350    for block_name, datafile in zip(block_list, datafile_list):
351        efuse = None
352        for block in efuses.blocks:
353            if block_name == block.name or block_name in block.alias:
354                efuse = efuses[block.name]
355        if efuse is None:
356            raise esptool.FatalError("Unknown block name - %s" % (block_name))
357        num_bytes = efuse.bit_len // 8
358        digest = espsecure._digest_sbv2_public_key(datafile)
359        if len(digest) != num_bytes:
360            raise esptool.FatalError(
361                "Incorrect digest size %d. Digest must be %d bytes (%d bits) "
362                "of raw binary key data." % (len(digest), num_bytes, num_bytes * 8)
363            )
364        digest_list.append(digest)
365    burn_key(esp, efuses, args, digest=digest_list)
366
367
368def espefuse(esp, efuses, args, command):
369    parser = argparse.ArgumentParser()
370    subparsers = parser.add_subparsers(dest="operation")
371    add_commands(subparsers, efuses)
372    try:
373        cmd_line_args = parser.parse_args(command.split())
374    except SystemExit:
375        traceback.print_stack()
376        raise esptool.FatalError('"{}" - incorrect command'.format(command))
377    if cmd_line_args.operation == "execute_scripts":
378        configfiles = cmd_line_args.configfiles
379        index = cmd_line_args.index
380    # copy arguments from args to cmd_line_args
381    vars(cmd_line_args).update(vars(args))
382    if cmd_line_args.operation == "execute_scripts":
383        cmd_line_args.configfiles = configfiles
384        cmd_line_args.index = index
385    if cmd_line_args.operation is None:
386        parser.print_help()
387        parser.exit(1)
388    operation_func = globals()[cmd_line_args.operation]
389    # each 'operation' is a module-level function of the same name
390    operation_func(esp, efuses, cmd_line_args)
391
392
393def execute_scripts(esp, efuses, args):
394    efuses.batch_mode_cnt += 1
395    del args.operation
396    scripts = args.scripts
397    del args.scripts
398
399    for file in scripts:
400        with open(file.name, "r") as file:
401            exec(compile(file.read(), file.name, "exec"))
402
403    if args.debug:
404        for block in efuses.blocks:
405            data = block.get_bitstring(from_read=False)
406            block.print_block(data, "regs_for_burn", args.debug)
407
408    efuses.batch_mode_cnt -= 1
409    if not efuses.burn_all(check_batch_mode=True):
410        return
411    print("Successful")
412