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