1# This file includes the operations with eFuses for ESP32S2 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. 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    burn_key.add_argument(
60        "block",
61        help="Key block to burn",
62        action="append",
63        choices=efuses.BLOCKS_FOR_KEYS,
64    )
65    burn_key.add_argument(
66        "keyfile",
67        help="File containing 256 bits of binary key data",
68        action="append",
69        type=argparse.FileType("rb"),
70    )
71    burn_key.add_argument(
72        "keypurpose",
73        help="Purpose to set.",
74        action="append",
75        choices=fields.EfuseKeyPurposeField.KEY_PURPOSES_NAME,
76    )
77    for _ in efuses.BLOCKS_FOR_KEYS:
78        burn_key.add_argument(
79            "block",
80            help="Key block to burn",
81            nargs="?",
82            action="append",
83            metavar="BLOCK",
84            choices=efuses.BLOCKS_FOR_KEYS,
85        )
86        burn_key.add_argument(
87            "keyfile",
88            help="File containing 256 bits of binary key data",
89            nargs="?",
90            action="append",
91            metavar="KEYFILE",
92            type=argparse.FileType("rb"),
93        )
94        burn_key.add_argument(
95            "keypurpose",
96            help="Purpose to set.",
97            nargs="?",
98            action="append",
99            metavar="KEYPURPOSE",
100            choices=fields.EfuseKeyPurposeField.KEY_PURPOSES_NAME,
101        )
102
103    burn_key_digest = subparsers.add_parser(
104        "burn_key_digest",
105        help="Parse a RSA public key and burn the digest to key efuse block",
106    )
107    protect_options(burn_key_digest)
108    add_force_write_always(burn_key_digest)
109    burn_key_digest.add_argument(
110        "block",
111        help="Key block to burn",
112        action="append",
113        choices=efuses.BLOCKS_FOR_KEYS,
114    )
115    burn_key_digest.add_argument(
116        "keyfile",
117        help="Key file to digest (PEM format)",
118        action="append",
119        type=argparse.FileType("rb"),
120    )
121    burn_key_digest.add_argument(
122        "keypurpose",
123        help="Purpose to set.",
124        action="append",
125        choices=fields.EfuseKeyPurposeField.DIGEST_KEY_PURPOSES,
126    )
127    for _ in efuses.BLOCKS_FOR_KEYS:
128        burn_key_digest.add_argument(
129            "block",
130            help="Key block to burn",
131            nargs="?",
132            action="append",
133            metavar="BLOCK",
134            choices=efuses.BLOCKS_FOR_KEYS,
135        )
136        burn_key_digest.add_argument(
137            "keyfile",
138            help="Key file to digest (PEM format)",
139            nargs="?",
140            action="append",
141            metavar="KEYFILE",
142            type=argparse.FileType("rb"),
143        )
144        burn_key_digest.add_argument(
145            "keypurpose",
146            help="Purpose to set.",
147            nargs="?",
148            action="append",
149            metavar="KEYPURPOSE",
150            choices=fields.EfuseKeyPurposeField.DIGEST_KEY_PURPOSES,
151        )
152
153    p = subparsers.add_parser(
154        "set_flash_voltage",
155        help="Permanently set the internal flash voltage regulator "
156        "to either 1.8V, 3.3V or OFF. "
157        "This means GPIO45 can be high or low at reset without "
158        "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). SPI flash will "
206        "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
229    if not efuses.burn_all(check_batch_mode=True):
230        return
231    print("Successful")
232
233
234def adc_info(esp, efuses, args):
235    print("")
236    # fmt: off
237    if efuses["BLK_VERSION_MINOR"].get() == 1:
238        print("Temperature Sensor Calibration = {}C".format(efuses["TEMP_SENSOR_CAL"].get()))
239
240        print("")
241        print("ADC1 readings stored in efuse BLOCK2:")
242        print("    MODE0 D1 reading  (250mV):  {}".format(efuses["ADC1_MODE0_D1"].get()))
243        print("    MODE0 D2 reading  (600mV):  {}".format(efuses["ADC1_MODE0_D2"].get()))
244
245        print("    MODE1 D1 reading  (250mV):  {}".format(efuses["ADC1_MODE1_D1"].get()))
246        print("    MODE1 D2 reading  (800mV):  {}".format(efuses["ADC1_MODE1_D2"].get()))
247
248        print("    MODE2 D1 reading  (250mV):  {}".format(efuses["ADC1_MODE2_D1"].get()))
249        print("    MODE2 D2 reading  (1000mV): {}".format(efuses["ADC1_MODE2_D2"].get()))
250
251        print("    MODE3 D1 reading  (250mV):  {}".format(efuses["ADC1_MODE3_D1"].get()))
252        print("    MODE3 D2 reading  (2000mV): {}".format(efuses["ADC1_MODE3_D2"].get()))
253
254        print("")
255        print("ADC2 readings stored in efuse BLOCK2:")
256        print("    MODE0 D1 reading  (250mV):  {}".format(efuses["ADC2_MODE0_D1"].get()))
257        print("    MODE0 D2 reading  (600mV):  {}".format(efuses["ADC2_MODE0_D2"].get()))
258
259        print("    MODE1 D1 reading  (250mV):  {}".format(efuses["ADC2_MODE1_D1"].get()))
260        print("    MODE1 D2 reading  (800mV):  {}".format(efuses["ADC2_MODE1_D2"].get()))
261
262        print("    MODE2 D1 reading  (250mV):  {}".format(efuses["ADC2_MODE2_D1"].get()))
263        print("    MODE2 D2 reading  (1000mV): {}".format(efuses["ADC2_MODE2_D2"].get()))
264
265        print("    MODE3 D1 reading  (250mV):  {}".format(efuses["ADC2_MODE3_D1"].get()))
266        print("    MODE3 D2 reading  (2000mV): {}".format(efuses["ADC2_MODE3_D2"].get()))
267    else:
268        print("BLK_VERSION_MINOR = {}".format(efuses["BLK_VERSION_MINOR"].get_meaning()))
269    # fmt: on
270
271
272def key_block_is_unused(block, key_purpose_block):
273    if not block.is_readable() or not block.is_writeable():
274        return False
275
276    if key_purpose_block.get() != "USER" or not key_purpose_block.is_writeable():
277        return False
278
279    if not block.get_bitstring().all(False):
280        return False
281
282    return True
283
284
285def get_next_key_block(efuses, current_key_block, block_name_list):
286    key_blocks = [b for b in efuses.blocks if b.key_purpose_name]
287    start = key_blocks.index(current_key_block)
288
289    # Sort key blocks so that we pick the next free block (and loop around if necessary)
290    key_blocks = key_blocks[start:] + key_blocks[0:start]
291
292    # Exclude any other blocks that will be be burned
293    key_blocks = [b for b in key_blocks if b.name not in block_name_list]
294
295    for block in key_blocks:
296        key_purpose_block = efuses[block.key_purpose_name]
297        if key_block_is_unused(block, key_purpose_block):
298            return block
299
300    return None
301
302
303def split_512_bit_key(efuses, block_name_list, datafile_list, keypurpose_list):
304    i = keypurpose_list.index("XTS_AES_256_KEY")
305    block_name = block_name_list[i]
306
307    block_num = efuses.get_index_block_by_name(block_name)
308    block = efuses.blocks[block_num]
309
310    data = datafile_list[i].read()
311    if len(data) != 64:
312        raise esptool.FatalError(
313            "Incorrect key file size %d, XTS_AES_256_KEY should be 64 bytes" % len(data)
314        )
315
316    key_block_2 = get_next_key_block(efuses, block, block_name_list)
317    if not key_block_2:
318        raise esptool.FatalError("XTS_AES_256_KEY requires two free keyblocks")
319
320    keypurpose_list.append("XTS_AES_256_KEY_1")
321    datafile_list.append(io.BytesIO(data[:32]))
322    block_name_list.append(block_name)
323
324    keypurpose_list.append("XTS_AES_256_KEY_2")
325    datafile_list.append(io.BytesIO(data[32:]))
326    block_name_list.append(key_block_2.name)
327
328    keypurpose_list.pop(i)
329    datafile_list.pop(i)
330    block_name_list.pop(i)
331
332
333def burn_key(esp, efuses, args, digest=None):
334    if digest is None:
335        datafile_list = args.keyfile[
336            0 : len([name for name in args.keyfile if name is not None]) :
337        ]
338    else:
339        datafile_list = digest[0 : len([name for name in digest if name is not None]) :]
340    efuses.force_write_always = args.force_write_always
341    block_name_list = args.block[
342        0 : len([name for name in args.block if name is not None]) :
343    ]
344    keypurpose_list = args.keypurpose[
345        0 : len([name for name in args.keypurpose if name is not None]) :
346    ]
347
348    if "XTS_AES_256_KEY" in keypurpose_list:
349        # XTS_AES_256_KEY is not an actual HW key purpose, needs to be split into
350        # XTS_AES_256_KEY_1 and XTS_AES_256_KEY_2
351        split_512_bit_key(efuses, block_name_list, datafile_list, keypurpose_list)
352
353    util.check_duplicate_name_in_list(block_name_list)
354    if len(block_name_list) != len(datafile_list) or len(block_name_list) != len(
355        keypurpose_list
356    ):
357        raise esptool.FatalError(
358            "The number of blocks (%d), datafile (%d) and keypurpose (%d) "
359            "should be the same."
360            % (len(block_name_list), len(datafile_list), len(keypurpose_list))
361        )
362
363    print("Burn keys to blocks:")
364    for block_name, datafile, keypurpose in zip(
365        block_name_list, datafile_list, keypurpose_list
366    ):
367        efuse = None
368        for block in efuses.blocks:
369            if block_name == block.name or block_name in block.alias:
370                efuse = efuses[block.name]
371        if efuse is None:
372            raise esptool.FatalError("Unknown block name - %s" % (block_name))
373        num_bytes = efuse.bit_len // 8
374
375        block_num = efuses.get_index_block_by_name(block_name)
376        block = efuses.blocks[block_num]
377
378        if digest is None:
379            data = datafile.read()
380        else:
381            data = datafile
382
383        print(" - %s" % (efuse.name), end=" ")
384        revers_msg = None
385        if efuses[block.key_purpose_name].need_reverse(keypurpose):
386            revers_msg = "\tReversing byte order for AES-XTS hardware peripheral"
387            data = data[::-1]
388        print("-> [%s]" % (util.hexify(data, " ")))
389        if revers_msg:
390            print(revers_msg)
391        if len(data) != num_bytes:
392            raise esptool.FatalError(
393                "Incorrect key file size %d. Key file must be %d bytes (%d bits) "
394                "of raw binary key data." % (len(data), num_bytes, num_bytes * 8)
395            )
396
397        if efuses[block.key_purpose_name].need_rd_protect(keypurpose):
398            read_protect = False if args.no_read_protect else True
399        else:
400            read_protect = False
401        write_protect = not args.no_write_protect
402
403        # using efuse instead of a block gives the advantage of
404        # checking it as the whole field.
405        efuse.save(data)
406
407        disable_wr_protect_key_purpose = False
408        if efuses[block.key_purpose_name].get() != keypurpose:
409            if efuses[block.key_purpose_name].is_writeable():
410                print(
411                    "\t'%s': '%s' -> '%s'."
412                    % (
413                        block.key_purpose_name,
414                        efuses[block.key_purpose_name].get(),
415                        keypurpose,
416                    )
417                )
418                efuses[block.key_purpose_name].save(keypurpose)
419                disable_wr_protect_key_purpose = True
420            else:
421                raise esptool.FatalError(
422                    "It is not possible to change '%s' to '%s' because "
423                    "write protection bit is set."
424                    % (block.key_purpose_name, keypurpose)
425                )
426        else:
427            print("\t'%s' is already '%s'." % (block.key_purpose_name, keypurpose))
428            if efuses[block.key_purpose_name].is_writeable():
429                disable_wr_protect_key_purpose = True
430
431        if disable_wr_protect_key_purpose:
432            print("\tDisabling write to '%s'." % block.key_purpose_name)
433            efuses[block.key_purpose_name].disable_write()
434
435        if read_protect:
436            print("\tDisabling read to key block")
437            efuse.disable_read()
438
439        if write_protect:
440            print("\tDisabling write to key block")
441            efuse.disable_write()
442        print("")
443
444    if not write_protect:
445        print("Keys will remain writeable (due to --no-write-protect)")
446    if args.no_read_protect:
447        print("Keys will remain readable (due to --no-read-protect)")
448
449    if not efuses.burn_all(check_batch_mode=True):
450        return
451    print("Successful")
452
453
454def burn_key_digest(esp, efuses, args):
455    digest_list = []
456    datafile_list = args.keyfile[
457        0 : len([name for name in args.keyfile if name is not None]) :
458    ]
459    block_list = args.block[
460        0 : len([block for block in args.block if block is not None]) :
461    ]
462    for block_name, datafile in zip(block_list, datafile_list):
463        efuse = None
464        for block in efuses.blocks:
465            if block_name == block.name or block_name in block.alias:
466                efuse = efuses[block.name]
467        if efuse is None:
468            raise esptool.FatalError("Unknown block name - %s" % (block_name))
469        num_bytes = efuse.bit_len // 8
470        digest = espsecure._digest_sbv2_public_key(datafile)
471        if len(digest) != num_bytes:
472            raise esptool.FatalError(
473                "Incorrect digest size %d. Digest must be %d bytes (%d bits) of raw "
474                "binary key data." % (len(digest), num_bytes, num_bytes * 8)
475            )
476        digest_list.append(digest)
477    burn_key(esp, efuses, args, digest=digest_list)
478
479
480def espefuse(esp, efuses, args, command):
481    parser = argparse.ArgumentParser()
482    subparsers = parser.add_subparsers(dest="operation")
483    add_commands(subparsers, efuses)
484    try:
485        cmd_line_args = parser.parse_args(command.split())
486    except SystemExit:
487        traceback.print_stack()
488        raise esptool.FatalError('"{}" - incorrect command'.format(command))
489    if cmd_line_args.operation == "execute_scripts":
490        configfiles = cmd_line_args.configfiles
491        index = cmd_line_args.index
492    # copy arguments from args to cmd_line_args
493    vars(cmd_line_args).update(vars(args))
494    if cmd_line_args.operation == "execute_scripts":
495        cmd_line_args.configfiles = configfiles
496        cmd_line_args.index = index
497    if cmd_line_args.operation is None:
498        parser.print_help()
499        parser.exit(1)
500    operation_func = globals()[cmd_line_args.operation]
501    # each 'operation' is a module-level function of the same name
502    operation_func(esp, efuses, cmd_line_args)
503
504
505def execute_scripts(esp, efuses, args):
506    efuses.batch_mode_cnt += 1
507    del args.operation
508    scripts = args.scripts
509    del args.scripts
510
511    for file in scripts:
512        with open(file.name, "r") as file:
513            exec(compile(file.read(), file.name, "exec"))
514
515    if args.debug:
516        for block in efuses.blocks:
517            data = block.get_bitstring(from_read=False)
518            block.print_block(data, "regs_for_burn", args.debug)
519
520    efuses.batch_mode_cnt -= 1
521    if not efuses.burn_all(check_batch_mode=True):
522        return
523    print("Successful")
524