1# This file includes the operations with eFuses for ESP32-H2 chip
2#
3# SPDX-FileCopyrightText: 2021-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 post-write "
36        "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] will remain "
45        "readable anyway. For the rest keypurposes the read-protection will be "
46        "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. This means GPIO45 can be high or low "
156        "at reset without changing the flash voltage.",
157    )
158    p.add_argument("voltage", help="Voltage selection", choices=["1.8V", "3.3V", "OFF"])
159
160    p = subparsers.add_parser(
161        "burn_custom_mac", help="Burn a 48-bit Custom MAC Address to EFUSE BLOCK3."
162    )
163    p.add_argument(
164        "mac",
165        help="Custom MAC Address to burn given in hexadecimal format with bytes "
166        "separated by colons (e.g. AA:CD:EF:01:02:03). "
167        "Final CUSTOM_MAC = CUSTOM_MAC[48] + MAC_EXT[16]",
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["BLOCK2_VERSION"].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("BLOCK2_VERSION = {}".format(efuses["BLOCK2_VERSION"].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' because write "
313                    "protection bit is set." % (block.key_purpose_name, keypurpose)
314                )
315        else:
316            print("\t'%s' is already '%s'." % (block.key_purpose_name, keypurpose))
317            if efuses[block.key_purpose_name].is_writeable():
318                disable_wr_protect_key_purpose = True
319
320        if disable_wr_protect_key_purpose:
321            print("\tDisabling write to '%s'." % block.key_purpose_name)
322            efuses[block.key_purpose_name].disable_write()
323
324        if read_protect:
325            print("\tDisabling read to key block")
326            efuse.disable_read()
327
328        if write_protect:
329            print("\tDisabling write to key block")
330            efuse.disable_write()
331        print("")
332
333    if not write_protect:
334        print("Keys will remain writeable (due to --no-write-protect)")
335    if args.no_read_protect:
336        print("Keys will remain readable (due to --no-read-protect)")
337
338    if not efuses.burn_all(check_batch_mode=True):
339        return
340    print("Successful")
341
342
343def burn_key_digest(esp, efuses, args):
344    digest_list = []
345    datafile_list = args.keyfile[
346        0 : len([name for name in args.keyfile if name is not None]) :
347    ]
348    block_list = args.block[
349        0 : len([block for block in args.block if block is not None]) :
350    ]
351    for block_name, datafile in zip(block_list, datafile_list):
352        efuse = None
353        for block in efuses.blocks:
354            if block_name == block.name or block_name in block.alias:
355                efuse = efuses[block.name]
356        if efuse is None:
357            raise esptool.FatalError("Unknown block name - %s" % (block_name))
358        num_bytes = efuse.bit_len // 8
359        digest = espsecure._digest_sbv2_public_key(datafile)
360        if len(digest) != num_bytes:
361            raise esptool.FatalError(
362                "Incorrect digest size %d. Digest must be %d bytes (%d bits) of raw "
363                "binary key data." % (len(digest), num_bytes, num_bytes * 8)
364            )
365        digest_list.append(digest)
366    burn_key(esp, efuses, args, digest=digest_list)
367
368
369def espefuse(esp, efuses, args, command):
370    parser = argparse.ArgumentParser()
371    subparsers = parser.add_subparsers(dest="operation")
372    add_commands(subparsers, efuses)
373    try:
374        cmd_line_args = parser.parse_args(command.split())
375    except SystemExit:
376        traceback.print_stack()
377        raise esptool.FatalError('"{}" - incorrect command'.format(command))
378    if cmd_line_args.operation == "execute_scripts":
379        configfiles = cmd_line_args.configfiles
380        index = cmd_line_args.index
381    # copy arguments from args to cmd_line_args
382    vars(cmd_line_args).update(vars(args))
383    if cmd_line_args.operation == "execute_scripts":
384        cmd_line_args.configfiles = configfiles
385        cmd_line_args.index = index
386    if cmd_line_args.operation is None:
387        parser.print_help()
388        parser.exit(1)
389    operation_func = globals()[cmd_line_args.operation]
390    # each 'operation' is a module-level function of the same name
391    operation_func(esp, efuses, cmd_line_args)
392
393
394def execute_scripts(esp, efuses, args):
395    efuses.batch_mode_cnt += 1
396    del args.operation
397    scripts = args.scripts
398    del args.scripts
399
400    for file in scripts:
401        with open(file.name, "r") as file:
402            exec(compile(file.read(), file.name, "exec"))
403
404    if args.debug:
405        for block in efuses.blocks:
406            data = block.get_bitstring(from_read=False)
407            block.print_block(data, "regs_for_burn", args.debug)
408
409    efuses.batch_mode_cnt -= 1
410    if not efuses.burn_all(check_batch_mode=True):
411        return
412    print("Successful")
413