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    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 post-write "
37        "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] will remain "
46        "readable anyway. For the rest keypurposes the read-protection will be "
47        "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. This means GPIO45 can be high or low "
159        "at reset without changing the flash voltage.",
160    )
161    p.add_argument("voltage", help="Voltage selection", choices=["1.8V", "3.3V", "OFF"])
162
163    p = subparsers.add_parser(
164        "burn_custom_mac", help="Burn a 48-bit Custom MAC Address to EFUSE BLOCK3."
165    )
166    p.add_argument(
167        "mac",
168        help="Custom MAC Address to burn given in hexadecimal format with bytes "
169        "separated by colons (e.g. AA:CD:EF:01:02:03).",
170        type=fields.base_fields.CheckArgValue(efuses, "CUSTOM_MAC"),
171    )
172    add_force_write_always(p)
173
174    p = subparsers.add_parser("get_custom_mac", help="Prints the Custom MAC Address.")
175
176
177def burn_custom_mac(esp, efuses, args):
178    efuses["CUSTOM_MAC"].save(args.mac)
179    if not efuses.burn_all(check_batch_mode=True):
180        return
181    get_custom_mac(esp, efuses, args)
182    print("Successful")
183
184
185def get_custom_mac(esp, efuses, args):
186    print("Custom MAC Address: {}".format(efuses["CUSTOM_MAC"].get()))
187
188
189def set_flash_voltage(esp, efuses, args):
190    raise esptool.FatalError("set_flash_voltage is not supported!")
191
192
193def adc_info(esp, efuses, args):
194    print("")
195    # fmt: off
196    if efuses["BLK_VERSION_MINOR"].get() == 2:
197        print("Temperature Sensor Calibration = {}C".format(efuses["TEMP_CALIB"].get()))
198        print("")
199        print("ADC1:")
200        print("AVE_INITCODE_ATTEN0      = ", efuses["ADC1_AVE_INITCODE_ATTEN0"].get())
201        print("AVE_INITCODE_ATTEN1      = ", efuses["ADC1_AVE_INITCODE_ATTEN1"].get())
202        print("AVE_INITCODE_ATTEN2      = ", efuses["ADC1_AVE_INITCODE_ATTEN2"].get())
203        print("AVE_INITCODE_ATTEN3      = ", efuses["ADC1_AVE_INITCODE_ATTEN3"].get())
204        print("HI_DOUT_ATTEN0           = ", efuses["ADC1_HI_DOUT_ATTEN0"].get())
205        print("HI_DOUT_ATTEN1           = ", efuses["ADC1_HI_DOUT_ATTEN1"].get())
206        print("HI_DOUT_ATTEN2           = ", efuses["ADC1_HI_DOUT_ATTEN2"].get())
207        print("HI_DOUT_ATTEN3           = ", efuses["ADC1_HI_DOUT_ATTEN3"].get())
208        print("CH0_ATTEN0_INITCODE_DIFF = ", efuses["ADC1_CH0_ATTEN0_INITCODE_DIFF"].get())
209        print("CH1_ATTEN0_INITCODE_DIFF = ", efuses["ADC1_CH1_ATTEN0_INITCODE_DIFF"].get())
210        print("CH2_ATTEN0_INITCODE_DIFF = ", efuses["ADC1_CH2_ATTEN0_INITCODE_DIFF"].get())
211        print("CH3_ATTEN0_INITCODE_DIFF = ", efuses["ADC1_CH3_ATTEN0_INITCODE_DIFF"].get())
212        print("CH4_ATTEN0_INITCODE_DIFF = ", efuses["ADC1_CH4_ATTEN0_INITCODE_DIFF"].get())
213    else:
214        print("BLK_VERSION_MINOR = {}".format(efuses["BLK_VERSION_MINOR"].get()))
215    # fmt: on
216
217
218def burn_key(esp, efuses, args, digest=None):
219    if digest is None:
220        datafile_list = args.keyfile[
221            0 : len([name for name in args.keyfile if name is not None]) :
222        ]
223    else:
224        datafile_list = digest[0 : len([name for name in digest if name is not None]) :]
225    efuses.force_write_always = args.force_write_always
226    block_name_list = args.block[
227        0 : len([name for name in args.block if name is not None]) :
228    ]
229    keypurpose_list = args.keypurpose[
230        0 : len([name for name in args.keypurpose if name is not None]) :
231    ]
232
233    util.check_duplicate_name_in_list(block_name_list)
234    if len(block_name_list) != len(datafile_list) or len(block_name_list) != len(
235        keypurpose_list
236    ):
237        raise esptool.FatalError(
238            "The number of blocks (%d), datafile (%d) and keypurpose (%d) "
239            "should be the same."
240            % (len(block_name_list), len(datafile_list), len(keypurpose_list))
241        )
242
243    print("Burn keys to blocks:")
244    for block_name, datafile, keypurpose in zip(
245        block_name_list, datafile_list, keypurpose_list
246    ):
247        efuse = None
248        for block in efuses.blocks:
249            if block_name == block.name or block_name in block.alias:
250                efuse = efuses[block.name]
251        if efuse is None:
252            raise esptool.FatalError("Unknown block name - %s" % (block_name))
253        num_bytes = efuse.bit_len // 8
254
255        block_num = efuses.get_index_block_by_name(block_name)
256        block = efuses.blocks[block_num]
257
258        if digest is None:
259            data = datafile.read()
260        else:
261            data = datafile
262
263        print(" - %s" % (efuse.name), end=" ")
264        revers_msg = None
265        if efuses[block.key_purpose_name].need_reverse(keypurpose):
266            revers_msg = "\tReversing byte order for AES-XTS hardware peripheral"
267            data = data[::-1]
268        print(
269            "-> [{}]".format(
270                util.hexify(data, " ")
271                if args.show_sensitive_info
272                else " ".join(["??"] * len(data))
273            )
274        )
275        if revers_msg:
276            print(revers_msg)
277        if len(data) != num_bytes:
278            raise esptool.FatalError(
279                "Incorrect key file size %d. Key file must be %d bytes (%d bits) "
280                "of raw binary key data." % (len(data), num_bytes, num_bytes * 8)
281            )
282
283        if efuses[block.key_purpose_name].need_rd_protect(keypurpose):
284            read_protect = False if args.no_read_protect else True
285        else:
286            read_protect = False
287        write_protect = not args.no_write_protect
288
289        # using efuse instead of a block gives the advantage of checking it as the whole field.
290        efuse.save(data)
291
292        disable_wr_protect_key_purpose = False
293        if efuses[block.key_purpose_name].get() != keypurpose:
294            if efuses[block.key_purpose_name].is_writeable():
295                print(
296                    "\t'%s': '%s' -> '%s'."
297                    % (
298                        block.key_purpose_name,
299                        efuses[block.key_purpose_name].get(),
300                        keypurpose,
301                    )
302                )
303                efuses[block.key_purpose_name].save(keypurpose)
304                disable_wr_protect_key_purpose = True
305            else:
306                raise esptool.FatalError(
307                    "It is not possible to change '%s' to '%s' because write "
308                    "protection bit is set." % (block.key_purpose_name, keypurpose)
309                )
310        else:
311            print("\t'%s' is already '%s'." % (block.key_purpose_name, keypurpose))
312            if efuses[block.key_purpose_name].is_writeable():
313                disable_wr_protect_key_purpose = True
314
315        if disable_wr_protect_key_purpose:
316            print("\tDisabling write to '%s'." % block.key_purpose_name)
317            efuses[block.key_purpose_name].disable_write()
318
319        if read_protect:
320            print("\tDisabling read to key block")
321            efuse.disable_read()
322
323        if write_protect:
324            print("\tDisabling write to key block")
325            efuse.disable_write()
326        print("")
327
328    if not write_protect:
329        print("Keys will remain writeable (due to --no-write-protect)")
330    if args.no_read_protect:
331        print("Keys will remain readable (due to --no-read-protect)")
332
333    if not efuses.burn_all(check_batch_mode=True):
334        return
335    print("Successful")
336
337
338def burn_key_digest(esp, efuses, args):
339    digest_list = []
340    datafile_list = args.keyfile[
341        0 : len([name for name in args.keyfile if name is not None]) :
342    ]
343    block_list = args.block[
344        0 : len([block for block in args.block if block is not None]) :
345    ]
346    for block_name, datafile in zip(block_list, datafile_list):
347        efuse = None
348        for block in efuses.blocks:
349            if block_name == block.name or block_name in block.alias:
350                efuse = efuses[block.name]
351        if efuse is None:
352            raise esptool.FatalError("Unknown block name - %s" % (block_name))
353        num_bytes = efuse.bit_len // 8
354        digest = espsecure._digest_sbv2_public_key(datafile)
355        if len(digest) != num_bytes:
356            raise esptool.FatalError(
357                "Incorrect digest size %d. Digest must be %d bytes (%d bits) of raw "
358                "binary key data." % (len(digest), num_bytes, num_bytes * 8)
359            )
360        digest_list.append(digest)
361    burn_key(esp, efuses, args, digest=digest_list)
362
363
364def espefuse(esp, efuses, args, command):
365    parser = argparse.ArgumentParser()
366    subparsers = parser.add_subparsers(dest="operation")
367    add_commands(subparsers, efuses)
368    try:
369        cmd_line_args = parser.parse_args(command.split())
370    except SystemExit:
371        traceback.print_stack()
372        raise esptool.FatalError('"{}" - incorrect command'.format(command))
373    if cmd_line_args.operation == "execute_scripts":
374        configfiles = cmd_line_args.configfiles
375        index = cmd_line_args.index
376    # copy arguments from args to cmd_line_args
377    vars(cmd_line_args).update(vars(args))
378    if cmd_line_args.operation == "execute_scripts":
379        cmd_line_args.configfiles = configfiles
380        cmd_line_args.index = index
381    if cmd_line_args.operation is None:
382        parser.print_help()
383        parser.exit(1)
384    operation_func = globals()[cmd_line_args.operation]
385    # each 'operation' is a module-level function of the same name
386    operation_func(esp, efuses, cmd_line_args)
387
388
389def execute_scripts(esp, efuses, args):
390    efuses.batch_mode_cnt += 1
391    del args.operation
392    scripts = args.scripts
393    del args.scripts
394
395    for file in scripts:
396        with open(file.name, "r") as file:
397            exec(compile(file.read(), file.name, "exec"))
398
399    if args.debug:
400        for block in efuses.blocks:
401            data = block.get_bitstring(from_read=False)
402            block.print_block(data, "regs_for_burn", args.debug)
403
404    efuses.batch_mode_cnt -= 1
405    if not efuses.burn_all(check_batch_mode=True):
406        return
407    print("Successful")
408