1# This file includes the operations with eFuses for ESP32-S3(beta2) 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 io
9import os  # noqa: F401. It is used in IDF scripts
10import traceback
11
12import espsecure
13
14import esptool
15
16from . import fields
17from .. import util
18from ..base_operations import (
19    add_common_commands,
20    add_force_write_always,
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] "
46        "will remain readable anyway. "
47        "For the rest keypurposes the read-protection will be defined the option "
48        "(Read-protect by default).",
49        action="store_true",
50    )
51
52
53def add_commands(subparsers, efuses):
54    add_common_commands(subparsers, efuses)
55    burn_key = subparsers.add_parser(
56        "burn_key", help="Burn the key block with the specified name"
57    )
58    protect_options(burn_key)
59    add_force_write_always(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    burn_key_digest.add_argument(
111        "block",
112        help="Key block to burn",
113        action="append",
114        choices=efuses.BLOCKS_FOR_KEYS,
115    )
116    burn_key_digest.add_argument(
117        "keyfile",
118        help="Key file to digest (PEM format)",
119        action="append",
120        type=argparse.FileType("rb"),
121    )
122    burn_key_digest.add_argument(
123        "keypurpose",
124        help="Purpose to set.",
125        action="append",
126        choices=fields.EfuseKeyPurposeField.DIGEST_KEY_PURPOSES,
127    )
128    for _ in efuses.BLOCKS_FOR_KEYS:
129        burn_key_digest.add_argument(
130            "block",
131            help="Key block to burn",
132            nargs="?",
133            action="append",
134            metavar="BLOCK",
135            choices=efuses.BLOCKS_FOR_KEYS,
136        )
137        burn_key_digest.add_argument(
138            "keyfile",
139            help="Key file to digest (PEM format)",
140            nargs="?",
141            action="append",
142            metavar="KEYFILE",
143            type=argparse.FileType("rb"),
144        )
145        burn_key_digest.add_argument(
146            "keypurpose",
147            help="Purpose to set.",
148            nargs="?",
149            action="append",
150            metavar="KEYPURPOSE",
151            choices=fields.EfuseKeyPurposeField.DIGEST_KEY_PURPOSES,
152        )
153
154    p = subparsers.add_parser(
155        "set_flash_voltage",
156        help="Permanently set the internal flash voltage regulator "
157        "to either 1.8V, 3.3V or OFF. This means GPIO45 can be high or low at reset "
158        "without changing the flash voltage.",
159    )
160    p.add_argument("voltage", help="Voltage selection", choices=["1.8V", "3.3V", "OFF"])
161
162    p = subparsers.add_parser(
163        "burn_custom_mac", help="Burn a 48-bit Custom MAC Address to EFUSE BLOCK3."
164    )
165    p.add_argument(
166        "mac",
167        help="Custom MAC Address to burn given in hexadecimal format with bytes "
168        "separated by colons (e.g. AA:CD:EF:01:02:03).",
169        type=fields.base_fields.CheckArgValue(efuses, "CUSTOM_MAC"),
170    )
171    add_force_write_always(p)
172
173    p = subparsers.add_parser("get_custom_mac", help="Prints the Custom MAC Address.")
174
175
176def burn_custom_mac(esp, efuses, args):
177    efuses["CUSTOM_MAC"].save(args.mac)
178    if not efuses.burn_all(check_batch_mode=True):
179        return
180    get_custom_mac(esp, efuses, args)
181    print("Successful")
182
183
184def get_custom_mac(esp, efuses, args):
185    print("Custom MAC Address: {}".format(efuses["CUSTOM_MAC"].get()))
186
187
188def set_flash_voltage(esp, efuses, args):
189    sdio_force = efuses["VDD_SPI_FORCE"]
190    sdio_tieh = efuses["VDD_SPI_TIEH"]
191    sdio_reg = efuses["VDD_SPI_XPD"]
192
193    # check efuses aren't burned in a way which makes this impossible
194    if args.voltage == "OFF" and sdio_reg.get() != 0:
195        raise esptool.FatalError(
196            "Can't set flash regulator to OFF as VDD_SPI_XPD efuse is already burned"
197        )
198
199    if args.voltage == "1.8V" and sdio_tieh.get() != 0:
200        raise esptool.FatalError(
201            "Can't set regulator to 1.8V is VDD_SPI_TIEH efuse is already burned"
202        )
203
204    if args.voltage == "OFF":
205        msg = "Disable internal flash voltage regulator (VDD_SPI). "
206        "SPI flash will need to be powered from an external source.\n"
207        "The following efuse is burned: VDD_SPI_FORCE.\n"
208        "It is possible to later re-enable the internal regulator (%s) " % (
209            "to 3.3V" if sdio_tieh.get() != 0 else "to 1.8V or 3.3V"
210        )
211        "by burning an additional efuse"
212    elif args.voltage == "1.8V":
213        msg = "Set internal flash voltage regulator (VDD_SPI) to 1.8V.\n"
214        "The following efuses are burned: VDD_SPI_FORCE, VDD_SPI_XPD.\n"
215        "It is possible to later increase the voltage to 3.3V (permanently) "
216        "by burning additional efuse VDD_SPI_TIEH"
217    elif args.voltage == "3.3V":
218        msg = "Enable internal flash voltage regulator (VDD_SPI) to 3.3V.\n"
219        "The following efuses are burned: VDD_SPI_FORCE, VDD_SPI_XPD, VDD_SPI_TIEH."
220    print(msg)
221
222    sdio_force.save(1)  # Disable GPIO45
223    if args.voltage != "OFF":
224        sdio_reg.save(1)  # Enable internal regulator
225    if args.voltage == "3.3V":
226        sdio_tieh.save(1)
227    print("VDD_SPI setting complete.")
228    if not efuses.burn_all(check_batch_mode=True):
229        return
230    print("Successful")
231
232
233def adc_info(esp, efuses, args):
234    print("")
235    # fmt: off
236    if efuses["BLK_VERSION_MAJOR"].get() == 1:
237        print("Temperature Sensor Calibration = {}C".format(efuses["TEMP_SENSOR_CAL"].get()))
238
239        print("")
240        print("ADC1 readings stored in efuse BLOCK2:")
241        print("    MODE0 D1 reading  (250mV):  {}".format(efuses["ADC1_MODE0_D1"].get()))
242        print("    MODE0 D2 reading  (600mV):  {}".format(efuses["ADC1_MODE0_D2"].get()))
243
244        print("    MODE1 D1 reading  (250mV):  {}".format(efuses["ADC1_MODE1_D1"].get()))
245        print("    MODE1 D2 reading  (800mV):  {}".format(efuses["ADC1_MODE1_D2"].get()))
246
247        print("    MODE2 D1 reading  (250mV):  {}".format(efuses["ADC1_MODE2_D1"].get()))
248        print("    MODE2 D2 reading  (1000mV): {}".format(efuses["ADC1_MODE2_D2"].get()))
249
250        print("    MODE3 D1 reading  (250mV):  {}".format(efuses["ADC1_MODE3_D1"].get()))
251        print("    MODE3 D2 reading  (2000mV): {}".format(efuses["ADC1_MODE3_D2"].get()))
252
253        print("")
254        print("ADC2 readings stored in efuse BLOCK2:")
255        print("    MODE0 D1 reading  (250mV):  {}".format(efuses["ADC2_MODE0_D1"].get()))
256        print("    MODE0 D2 reading  (600mV):  {}".format(efuses["ADC2_MODE0_D2"].get()))
257
258        print("    MODE1 D1 reading  (250mV):  {}".format(efuses["ADC2_MODE1_D1"].get()))
259        print("    MODE1 D2 reading  (800mV):  {}".format(efuses["ADC2_MODE1_D2"].get()))
260
261        print("    MODE2 D1 reading  (250mV):  {}".format(efuses["ADC2_MODE2_D1"].get()))
262        print("    MODE2 D2 reading  (1000mV): {}".format(efuses["ADC2_MODE2_D2"].get()))
263
264        print("    MODE3 D1 reading  (250mV):  {}".format(efuses["ADC2_MODE3_D1"].get()))
265        print("    MODE3 D2 reading  (2000mV): {}".format(efuses["ADC2_MODE3_D2"].get()))
266    else:
267        print("BLK_VERSION_MAJOR = {}".format(efuses["BLK_VERSION_MAJOR"].get_meaning()))
268    # fmt: on
269
270
271def key_block_is_unused(block, key_purpose_block):
272    if not block.is_readable() or not block.is_writeable():
273        return False
274
275    if key_purpose_block.get() != "USER" or not key_purpose_block.is_writeable():
276        return False
277
278    if not block.get_bitstring().all(False):
279        return False
280
281    return True
282
283
284def get_next_key_block(efuses, current_key_block, block_name_list):
285    key_blocks = [b for b in efuses.blocks if b.key_purpose_name]
286    start = key_blocks.index(current_key_block)
287
288    # Sort key blocks so that we pick the next free block (and loop around if necessary)
289    key_blocks = key_blocks[start:] + key_blocks[0:start]
290
291    # Exclude any other blocks that will be be burned
292    key_blocks = [b for b in key_blocks if b.name not in block_name_list]
293
294    for block in key_blocks:
295        key_purpose_block = efuses[block.key_purpose_name]
296        if key_block_is_unused(block, key_purpose_block):
297            return block
298
299    return None
300
301
302def split_512_bit_key(efuses, block_name_list, datafile_list, keypurpose_list):
303    i = keypurpose_list.index("XTS_AES_256_KEY")
304    block_name = block_name_list[i]
305
306    block_num = efuses.get_index_block_by_name(block_name)
307    block = efuses.blocks[block_num]
308
309    data = datafile_list[i].read()
310    if len(data) != 64:
311        raise esptool.FatalError(
312            "Incorrect key file size %d, XTS_AES_256_KEY should be 64 bytes" % len(data)
313        )
314
315    key_block_2 = get_next_key_block(efuses, block, block_name_list)
316    if not key_block_2:
317        raise esptool.FatalError("XTS_AES_256_KEY requires two free keyblocks")
318
319    keypurpose_list.append("XTS_AES_256_KEY_1")
320    datafile_list.append(io.BytesIO(data[:32]))
321    block_name_list.append(block_name)
322
323    keypurpose_list.append("XTS_AES_256_KEY_2")
324    datafile_list.append(io.BytesIO(data[32:]))
325    block_name_list.append(key_block_2.name)
326
327    keypurpose_list.pop(i)
328    datafile_list.pop(i)
329    block_name_list.pop(i)
330
331
332def burn_key(esp, efuses, args, digest=None):
333    if digest is None:
334        datafile_list = args.keyfile[
335            0 : len([name for name in args.keyfile if name is not None]) :
336        ]
337    else:
338        datafile_list = digest[0 : len([name for name in digest if name is not None]) :]
339    efuses.force_write_always = args.force_write_always
340    block_name_list = args.block[
341        0 : len([name for name in args.block if name is not None]) :
342    ]
343    keypurpose_list = args.keypurpose[
344        0 : len([name for name in args.keypurpose if name is not None]) :
345    ]
346
347    if "XTS_AES_256_KEY" in keypurpose_list:
348        # XTS_AES_256_KEY is not an actual HW key purpose, needs to be split into
349        # XTS_AES_256_KEY_1 and XTS_AES_256_KEY_2
350        split_512_bit_key(efuses, block_name_list, datafile_list, keypurpose_list)
351
352    util.check_duplicate_name_in_list(block_name_list)
353    if len(block_name_list) != len(datafile_list) or len(block_name_list) != len(
354        keypurpose_list
355    ):
356        raise esptool.FatalError(
357            "The number of blocks (%d), datafile (%d) and keypurpose (%d) "
358            "should be the same."
359            % (len(block_name_list), len(datafile_list), len(keypurpose_list))
360        )
361
362    print("Burn keys to blocks:")
363    for block_name, datafile, keypurpose in zip(
364        block_name_list, datafile_list, keypurpose_list
365    ):
366        efuse = None
367        for block in efuses.blocks:
368            if block_name == block.name or block_name in block.alias:
369                efuse = efuses[block.name]
370        if efuse is None:
371            raise esptool.FatalError("Unknown block name - %s" % (block_name))
372        num_bytes = efuse.bit_len // 8
373
374        block_num = efuses.get_index_block_by_name(block_name)
375        block = efuses.blocks[block_num]
376
377        if digest is None:
378            data = datafile.read()
379        else:
380            data = datafile
381
382        print(" - %s" % (efuse.name), end=" ")
383        revers_msg = None
384        if efuses[block.key_purpose_name].need_reverse(keypurpose):
385            revers_msg = "\tReversing byte order for AES-XTS hardware peripheral"
386            data = data[::-1]
387        print("-> [%s]" % (util.hexify(data, " ")))
388        if revers_msg:
389            print(revers_msg)
390        if len(data) != num_bytes:
391            raise esptool.FatalError(
392                "Incorrect key file size %d. Key file must be %d bytes (%d bits) "
393                "of raw binary key data." % (len(data), num_bytes, num_bytes * 8)
394            )
395
396        if efuses[block.key_purpose_name].need_rd_protect(keypurpose):
397            read_protect = False if args.no_read_protect else True
398        else:
399            read_protect = False
400        write_protect = not args.no_write_protect
401
402        # using efuse instead of a block gives the advantage of
403        # checking it as the whole field.
404        efuse.save(data)
405
406        disable_wr_protect_key_purpose = False
407        if efuses[block.key_purpose_name].get() != keypurpose:
408            if efuses[block.key_purpose_name].is_writeable():
409                print(
410                    "\t'%s': '%s' -> '%s'."
411                    % (
412                        block.key_purpose_name,
413                        efuses[block.key_purpose_name].get(),
414                        keypurpose,
415                    )
416                )
417                efuses[block.key_purpose_name].save(keypurpose)
418                disable_wr_protect_key_purpose = True
419            else:
420                raise esptool.FatalError(
421                    "It is not possible to change '%s' to '%s' because "
422                    "write protection bit is set."
423                    % (block.key_purpose_name, keypurpose)
424                )
425        else:
426            print("\t'%s' is already '%s'." % (block.key_purpose_name, keypurpose))
427            if efuses[block.key_purpose_name].is_writeable():
428                disable_wr_protect_key_purpose = True
429
430        if disable_wr_protect_key_purpose:
431            print("\tDisabling write to '%s'." % block.key_purpose_name)
432            efuses[block.key_purpose_name].disable_write()
433
434        if read_protect:
435            print("\tDisabling read to key block")
436            efuse.disable_read()
437
438        if write_protect:
439            print("\tDisabling write to key block")
440            efuse.disable_write()
441        print("")
442
443    if not write_protect:
444        print("Keys will remain writeable (due to --no-write-protect)")
445    if args.no_read_protect:
446        print("Keys will remain readable (due to --no-read-protect)")
447
448    if not efuses.burn_all(check_batch_mode=True):
449        return
450    print("Successful")
451
452
453def burn_key_digest(esp, efuses, args):
454    digest_list = []
455    datafile_list = args.keyfile[
456        0 : len([name for name in args.keyfile if name is not None]) :
457    ]
458    block_list = args.block[
459        0 : len([block for block in args.block if block is not None]) :
460    ]
461    for block_name, datafile in zip(block_list, datafile_list):
462        efuse = None
463        for block in efuses.blocks:
464            if block_name == block.name or block_name in block.alias:
465                efuse = efuses[block.name]
466        if efuse is None:
467            raise esptool.FatalError("Unknown block name - %s" % (block_name))
468        num_bytes = efuse.bit_len // 8
469        digest = espsecure._digest_sbv2_public_key(datafile)
470        if len(digest) != num_bytes:
471            raise esptool.FatalError(
472                "Incorrect digest size %d. Digest must be %d bytes (%d bits) "
473                "of raw binary key data." % (len(digest), num_bytes, num_bytes * 8)
474            )
475        digest_list.append(digest)
476    burn_key(esp, efuses, args, digest=digest_list)
477
478
479def espefuse(esp, efuses, args, command):
480    parser = argparse.ArgumentParser()
481    subparsers = parser.add_subparsers(dest="operation")
482    add_commands(subparsers, efuses)
483    try:
484        cmd_line_args = parser.parse_args(command.split())
485    except SystemExit:
486        traceback.print_stack()
487        raise esptool.FatalError('"{}" - incorrect command'.format(command))
488    if cmd_line_args.operation == "execute_scripts":
489        configfiles = cmd_line_args.configfiles
490        index = cmd_line_args.index
491    # copy arguments from args to cmd_line_args
492    vars(cmd_line_args).update(vars(args))
493    if cmd_line_args.operation == "execute_scripts":
494        cmd_line_args.configfiles = configfiles
495        cmd_line_args.index = index
496    if cmd_line_args.operation is None:
497        parser.print_help()
498        parser.exit(1)
499    operation_func = globals()[cmd_line_args.operation]
500    # each 'operation' is a module-level function of the same name
501    operation_func(esp, efuses, cmd_line_args)
502
503
504def execute_scripts(esp, efuses, args):
505    efuses.batch_mode_cnt += 1
506    del args.operation
507    scripts = args.scripts
508    del args.scripts
509
510    for file in scripts:
511        with open(file.name, "r") as file:
512            exec(compile(file.read(), file.name, "exec"))
513
514    if args.debug:
515        for block in efuses.blocks:
516            data = block.get_bitstring(from_read=False)
517            block.print_block(data, "regs_for_burn", args.debug)
518
519    efuses.batch_mode_cnt -= 1
520    if not efuses.burn_all(check_batch_mode=True):
521        return
522    print("Successful")
523