1# This file includes the operations with eFuses for ESP32-C3 chip
2#
3# SPDX-FileCopyrightText: 2020-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_MAJOR"].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    else:
210        print("BLK_VERSION_MAJOR = {}".format(efuses["BLK_VERSION_MAJOR"].get_meaning()))
211    # fmt: on
212
213
214def burn_key(esp, efuses, args, digest=None):
215    if digest is None:
216        datafile_list = args.keyfile[
217            0 : len([name for name in args.keyfile if name is not None]) :
218        ]
219    else:
220        datafile_list = digest[0 : len([name for name in digest if name is not None]) :]
221    efuses.force_write_always = args.force_write_always
222    block_name_list = args.block[
223        0 : len([name for name in args.block if name is not None]) :
224    ]
225    keypurpose_list = args.keypurpose[
226        0 : len([name for name in args.keypurpose if name is not None]) :
227    ]
228
229    util.check_duplicate_name_in_list(block_name_list)
230    if len(block_name_list) != len(datafile_list) or len(block_name_list) != len(
231        keypurpose_list
232    ):
233        raise esptool.FatalError(
234            "The number of blocks (%d), datafile (%d) and keypurpose (%d) "
235            "should be the same."
236            % (len(block_name_list), len(datafile_list), len(keypurpose_list))
237        )
238
239    print("Burn keys to blocks:")
240    for block_name, datafile, keypurpose in zip(
241        block_name_list, datafile_list, keypurpose_list
242    ):
243        efuse = None
244        for block in efuses.blocks:
245            if block_name == block.name or block_name in block.alias:
246                efuse = efuses[block.name]
247        if efuse is None:
248            raise esptool.FatalError("Unknown block name - %s" % (block_name))
249        num_bytes = efuse.bit_len // 8
250
251        block_num = efuses.get_index_block_by_name(block_name)
252        block = efuses.blocks[block_num]
253
254        if digest is None:
255            data = datafile.read()
256        else:
257            data = datafile
258
259        print(" - %s" % (efuse.name), end=" ")
260        revers_msg = None
261        if efuses[block.key_purpose_name].need_reverse(keypurpose):
262            revers_msg = "\tReversing byte order for AES-XTS hardware peripheral"
263            data = data[::-1]
264        print(
265            "-> [{}]".format(
266                util.hexify(data, " ")
267                if args.show_sensitive_info
268                else " ".join(["??"] * len(data))
269            )
270        )
271        if revers_msg:
272            print(revers_msg)
273        if len(data) != num_bytes:
274            raise esptool.FatalError(
275                "Incorrect key file size %d. Key file must be %d bytes (%d bits) "
276                "of raw binary key data." % (len(data), num_bytes, num_bytes * 8)
277            )
278
279        if efuses[block.key_purpose_name].need_rd_protect(keypurpose):
280            read_protect = False if args.no_read_protect else True
281        else:
282            read_protect = False
283        write_protect = not args.no_write_protect
284
285        # using efuse instead of a block gives the advantage of checking it as the whole field.
286        efuse.save(data)
287
288        disable_wr_protect_key_purpose = False
289        if efuses[block.key_purpose_name].get() != keypurpose:
290            if efuses[block.key_purpose_name].is_writeable():
291                print(
292                    "\t'%s': '%s' -> '%s'."
293                    % (
294                        block.key_purpose_name,
295                        efuses[block.key_purpose_name].get(),
296                        keypurpose,
297                    )
298                )
299                efuses[block.key_purpose_name].save(keypurpose)
300                disable_wr_protect_key_purpose = True
301            else:
302                raise esptool.FatalError(
303                    "It is not possible to change '%s' to '%s' "
304                    "because write protection bit is set."
305                    % (block.key_purpose_name, keypurpose)
306                )
307        else:
308            print("\t'%s' is already '%s'." % (block.key_purpose_name, keypurpose))
309            if efuses[block.key_purpose_name].is_writeable():
310                disable_wr_protect_key_purpose = True
311
312        if disable_wr_protect_key_purpose:
313            print("\tDisabling write to '%s'." % block.key_purpose_name)
314            efuses[block.key_purpose_name].disable_write()
315
316        if read_protect:
317            print("\tDisabling read to key block")
318            efuse.disable_read()
319
320        if write_protect:
321            print("\tDisabling write to key block")
322            efuse.disable_write()
323        print("")
324
325    if not write_protect:
326        print("Keys will remain writeable (due to --no-write-protect)")
327    if args.no_read_protect:
328        print("Keys will remain readable (due to --no-read-protect)")
329
330    if not efuses.burn_all(check_batch_mode=True):
331        return
332    print("Successful")
333
334
335def burn_key_digest(esp, efuses, args):
336    digest_list = []
337    datafile_list = args.keyfile[
338        0 : len([name for name in args.keyfile if name is not None]) :
339    ]
340    block_list = args.block[
341        0 : len([block for block in args.block if block is not None]) :
342    ]
343    for block_name, datafile in zip(block_list, datafile_list):
344        efuse = None
345        for block in efuses.blocks:
346            if block_name == block.name or block_name in block.alias:
347                efuse = efuses[block.name]
348        if efuse is None:
349            raise esptool.FatalError("Unknown block name - %s" % (block_name))
350        num_bytes = efuse.bit_len // 8
351        digest = espsecure._digest_sbv2_public_key(datafile)
352        if len(digest) != num_bytes:
353            raise esptool.FatalError(
354                "Incorrect digest size %d. Digest must be %d bytes (%d bits) "
355                "of raw binary key data." % (len(digest), num_bytes, num_bytes * 8)
356            )
357        digest_list.append(digest)
358    burn_key(esp, efuses, args, digest=digest_list)
359
360
361def espefuse(esp, efuses, args, command):
362    parser = argparse.ArgumentParser()
363    subparsers = parser.add_subparsers(dest="operation")
364    add_commands(subparsers, efuses)
365    try:
366        cmd_line_args = parser.parse_args(command.split())
367    except SystemExit:
368        traceback.print_stack()
369        raise esptool.FatalError('"{}" - incorrect command'.format(command))
370    if cmd_line_args.operation == "execute_scripts":
371        configfiles = cmd_line_args.configfiles
372        index = cmd_line_args.index
373    # copy arguments from args to cmd_line_args
374    vars(cmd_line_args).update(vars(args))
375    if cmd_line_args.operation == "execute_scripts":
376        cmd_line_args.configfiles = configfiles
377        cmd_line_args.index = index
378    if cmd_line_args.operation is None:
379        parser.print_help()
380        parser.exit(1)
381    operation_func = globals()[cmd_line_args.operation]
382    # each 'operation' is a module-level function of the same name
383    operation_func(esp, efuses, cmd_line_args)
384
385
386def execute_scripts(esp, efuses, args):
387    efuses.batch_mode_cnt += 1
388    del args.operation
389    scripts = args.scripts
390    del args.scripts
391
392    for file in scripts:
393        with open(file.name, "r") as file:
394            exec(compile(file.read(), file.name, "exec"))
395
396    if args.debug:
397        for block in efuses.blocks:
398            data = block.get_bitstring(from_read=False)
399            block.print_block(data, "regs_for_burn", args.debug)
400
401    efuses.batch_mode_cnt -= 1
402    if not efuses.burn_all(check_batch_mode=True):
403        return
404    print("Successful")
405