1# This file includes the common operations with eFuses for chips
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 json
9import sys
10
11from bitstring import BitStream
12
13import esptool
14
15from . import base_fields
16from . import util
17
18
19def add_common_commands(subparsers, efuses):
20    class ActionEfuseValuePair(argparse.Action):
21        def __init__(self, option_strings, dest, nargs=None, **kwargs):
22            self._nargs = nargs
23            self._choices = kwargs.get("efuse_choices")
24            self.efuses = kwargs.get("efuses")
25            del kwargs["efuse_choices"]
26            del kwargs["efuses"]
27            super(ActionEfuseValuePair, self).__init__(
28                option_strings, dest, nargs=nargs, **kwargs
29            )
30
31        def __call__(self, parser, namespace, values, option_string=None):
32            def check_efuse_name(efuse_name, efuse_list):
33                if efuse_name not in self._choices:
34                    raise esptool.FatalError(
35                        "Invalid the efuse name '{}'. "
36                        "Available the efuse names: {}".format(
37                            efuse_name, self._choices
38                        )
39                    )
40
41            efuse_value_pairs = {}
42            if len(values) > 1:
43                if len(values) % 2:
44                    raise esptool.FatalError(
45                        "The list does not have a valid pair (name value) {}".format(
46                            values
47                        )
48                    )
49                for i in range(0, len(values), 2):
50                    efuse_name, new_value = values[i : i + 2 :]
51                    check_efuse_name(efuse_name, self._choices)
52                    check_arg = base_fields.CheckArgValue(self.efuses, efuse_name)
53                    efuse_value_pairs[efuse_name] = check_arg(new_value)
54            else:
55                # For the case of compatibility, when only the efuse_name is given
56                # Fields with 'bitcount' and 'bool' types can be without new_value arg
57                efuse_name = values[0]
58                check_efuse_name(efuse_name, self._choices)
59                check_arg = base_fields.CheckArgValue(self.efuses, efuse_name)
60                efuse_value_pairs[efuse_name] = check_arg(None)
61            setattr(namespace, self.dest, efuse_value_pairs)
62
63    burn = subparsers.add_parser(
64        "burn_efuse", help="Burn the efuse with the specified name"
65    )
66    burn.add_argument(
67        "name_value_pairs",
68        help="Name of efuse register and New value pairs to burn",
69        action=ActionEfuseValuePair,
70        nargs="+",
71        metavar="[EFUSE_NAME VALUE] [{} VALUE".format(
72            " VALUE] [".join([e.name for e in efuses.efuses])
73        ),
74        efuse_choices=[e.name for e in efuses.efuses],
75        efuses=efuses,
76    )
77
78    read_protect_efuse = subparsers.add_parser(
79        "read_protect_efuse",
80        help="Disable readback for the efuse with the specified name",
81    )
82    read_protect_efuse.add_argument(
83        "efuse_name",
84        help="Name of efuse register to burn",
85        nargs="+",
86        choices=[e.name for e in efuses.efuses if e.read_disable_bit is not None],
87    )
88
89    write_protect_efuse = subparsers.add_parser(
90        "write_protect_efuse",
91        help="Disable writing to the efuse with the specified name",
92    )
93    write_protect_efuse.add_argument(
94        "efuse_name",
95        help="Name of efuse register to burn",
96        nargs="+",
97        choices=[e.name for e in efuses.efuses if e.write_disable_bit is not None],
98    )
99
100    burn_block_data = subparsers.add_parser(
101        "burn_block_data",
102        help="Burn non-key data to EFUSE blocks. "
103        "(Don't use this command to burn key data for Flash Encryption or "
104        "ESP32 Secure Boot V1, as the byte order of keys is swapped (use burn_key)).",
105    )
106    add_force_write_always(burn_block_data)
107    burn_block_data.add_argument(
108        "--offset", "-o", help="Byte offset in the efuse block", type=int, default=0
109    )
110    burn_block_data.add_argument(
111        "block",
112        help="Efuse block to burn.",
113        action="append",
114        choices=efuses.BURN_BLOCK_DATA_NAMES,
115    )
116    burn_block_data.add_argument(
117        "datafile",
118        help="File containing data to burn into the efuse block",
119        action="append",
120        type=argparse.FileType("rb"),
121    )
122    for _ in range(0, len(efuses.BURN_BLOCK_DATA_NAMES)):
123        burn_block_data.add_argument(
124            "block",
125            help="Efuse block to burn.",
126            metavar="BLOCK",
127            nargs="?",
128            action="append",
129            choices=efuses.BURN_BLOCK_DATA_NAMES,
130        )
131        burn_block_data.add_argument(
132            "datafile",
133            nargs="?",
134            help="File containing data to burn into the efuse block",
135            metavar="DATAFILE",
136            action="append",
137            type=argparse.FileType("rb"),
138        )
139
140    set_bit_cmd = subparsers.add_parser("burn_bit", help="Burn bit in the efuse block.")
141    add_force_write_always(set_bit_cmd)
142    set_bit_cmd.add_argument(
143        "block", help="Efuse block to burn.", choices=efuses.BURN_BLOCK_DATA_NAMES
144    )
145    set_bit_cmd.add_argument(
146        "bit_number",
147        help="Bit number in the efuse block [0..BLK_LEN-1]",
148        nargs="+",
149        type=int,
150    )
151
152    subparsers.add_parser(
153        "adc_info",
154        help="Display information about ADC calibration data stored in efuse.",
155    )
156
157    dump_cmd = subparsers.add_parser("dump", help="Dump raw hex values of all efuses")
158    dump_cmd.add_argument(
159        "--file_name",
160        help="Saves dump for each block into separate file. Provide the common "
161        "path name /path/blk.bin, it will create: blk0.bin, blk1.bin ... blkN.bin. "
162        "Use burn_block_data to write it back to another chip.",
163    )
164
165    summary_cmd = subparsers.add_parser(
166        "summary", help="Print human-readable summary of efuse values"
167    )
168    summary_cmd.add_argument(
169        "--format",
170        help="Select the summary format",
171        choices=["summary", "json"],
172        default="summary",
173    )
174    summary_cmd.add_argument(
175        "--file",
176        help="File to save the efuse summary",
177        type=argparse.FileType("w"),
178        default=sys.stdout,
179    )
180
181    execute_scripts = subparsers.add_parser(
182        "execute_scripts", help="Executes scripts to burn at one time."
183    )
184    execute_scripts.add_argument(
185        "scripts",
186        help="The special format of python scripts.",
187        nargs="+",
188        type=argparse.FileType("r"),
189    )
190    execute_scripts.add_argument(
191        "--index",
192        help="integer index. "
193        "It allows to retrieve unique data per chip from configfiles "
194        "and then burn them (ex. CUSTOM_MAC, UNIQUE_ID).",
195        type=int,
196    )
197    execute_scripts.add_argument(
198        "--configfiles",
199        help="List of configfiles with data",
200        nargs="?",
201        action="append",
202        type=argparse.FileType("r"),
203    )
204
205    check_error_cmd = subparsers.add_parser("check_error", help="Checks eFuse errors")
206    check_error_cmd.add_argument(
207        "--recovery",
208        help="Recovery of BLOCKs after encoding errors",
209        action="store_true",
210    )
211
212
213def add_force_write_always(p):
214    p.add_argument(
215        "--force-write-always",
216        help="Write the efuse even if it looks like it's already been written, "
217        "or is write protected. Note that this option can't disable write protection, "
218        "or clear any bit which has already been set.",
219        action="store_true",
220    )
221
222
223def summary(esp, efuses, args):
224    """Print a human-readable summary of efuse contents"""
225    ROW_FORMAT = "%-50s %-50s%s = %s %s %s"
226    human_output = args.format == "summary"
227    json_efuse = {}
228    if args.file != sys.stdout:
229        print("Saving efuse values to " + args.file.name)
230    if human_output:
231        print(
232            ROW_FORMAT.replace("-50", "-12")
233            % (
234                "EFUSE_NAME (Block)",
235                "Description",
236                "",
237                "[Meaningful Value]",
238                "[Readable/Writeable]",
239                "(Hex Value)",
240            ),
241            file=args.file,
242        )
243        print("-" * 88, file=args.file)
244    for category in sorted(set(e.category for e in efuses), key=lambda c: c.title()):
245        if human_output:
246            print("%s fuses:" % category.title(), file=args.file)
247        for e in (e for e in efuses if e.category == category):
248            if e.efuse_type.startswith("bytes"):
249                raw = ""
250            else:
251                raw = "({})".format(e.get_bitstring())
252            (readable, writeable) = (e.is_readable(), e.is_writeable())
253            if readable and writeable:
254                perms = "R/W"
255            elif readable:
256                perms = "R/-"
257            elif writeable:
258                perms = "-/W"
259            else:
260                perms = "-/-"
261            base_value = e.get_meaning()
262            value = str(base_value)
263            if not readable:
264                value = value.replace("0", "?")
265            if human_output:
266                print(
267                    ROW_FORMAT
268                    % (
269                        e.get_info(),
270                        e.description[:50],
271                        "\n  " if len(value) > 20 else "",
272                        value,
273                        perms,
274                        raw,
275                    ),
276                    file=args.file,
277                )
278                desc_len = len(e.description[50:])
279                if desc_len:
280                    desc_len += 50
281                    for i in range(50, desc_len, 50):
282                        print(
283                            "%-50s %-50s" % ("", e.description[i : (50 + i)]),
284                            file=args.file,
285                        )
286            if args.format == "json":
287                json_efuse[e.name] = {
288                    "name": e.name,
289                    "value": base_value if readable else value,
290                    "readable": readable,
291                    "writeable": writeable,
292                    "description": e.description,
293                    "category": e.category,
294                    "block": e.block,
295                    "word": e.word,
296                    "pos": e.pos,
297                    "efuse_type": e.efuse_type,
298                    "bit_len": e.bit_len,
299                }
300        if human_output:
301            print("", file=args.file)
302    if human_output:
303        print(efuses.summary(), file=args.file)
304        warnings = efuses.get_coding_scheme_warnings()
305        if warnings:
306            print(
307                "WARNING: Coding scheme has encoding bit error warnings", file=args.file
308            )
309        if args.file != sys.stdout:
310            args.file.close()
311            print("Done")
312    if args.format == "json":
313        json.dump(json_efuse, args.file, sort_keys=True, indent=4)
314        print("")
315
316
317def dump(esp, efuses, args):
318    """Dump raw efuse data registers"""
319    # Using --debug option allows to print dump.
320    # Nothing to do here. The log will be printed
321    # during EspEfuses.__init__() in self.read_blocks()
322    if args.file_name:
323        # save dump to the file
324        for block in efuses.blocks:
325            file_dump_name = args.file_name
326            place_for_index = file_dump_name.find(".bin")
327            file_dump_name = (
328                file_dump_name[:place_for_index]
329                + str(block.id)
330                + file_dump_name[place_for_index:]
331            )
332            print(file_dump_name)
333            with open(file_dump_name, "wb") as f:
334                block.get_bitstring().byteswap()
335                block.get_bitstring().tofile(f)
336
337
338def burn_efuse(esp, efuses, args):
339    def print_attention(blocked_efuses_after_burn):
340        if len(blocked_efuses_after_burn):
341            print(
342                "    ATTENTION! This BLOCK uses NOT the NONE coding scheme "
343                "and after 'BURN', these efuses can not be burned in the feature:"
344            )
345            for i in range(0, len(blocked_efuses_after_burn), 5):
346                print(
347                    "              ",
348                    "".join("{}".format(blocked_efuses_after_burn[i : i + 5 :])),
349                )
350
351    efuse_name_list = [name for name in args.name_value_pairs.keys()]
352    burn_efuses_list = [efuses[name] for name in efuse_name_list]
353    old_value_list = [efuses[name].get_raw() for name in efuse_name_list]
354    new_value_list = [value for value in args.name_value_pairs.values()]
355    util.check_duplicate_name_in_list(efuse_name_list)
356
357    attention = ""
358    print("The efuses to burn:")
359    for block in efuses.blocks:
360        burn_list_a_block = [e for e in burn_efuses_list if e.block == block.id]
361        if len(burn_list_a_block):
362            print("  from BLOCK%d" % (block.id))
363            for field in burn_list_a_block:
364                print("     - %s" % (field.name))
365                if (
366                    efuses.blocks[field.block].get_coding_scheme()
367                    != efuses.REGS.CODING_SCHEME_NONE
368                ):
369                    using_the_same_block_names = [
370                        e.name for e in efuses if e.block == field.block
371                    ]
372                    wr_names = [e.name for e in burn_list_a_block]
373                    blocked_efuses_after_burn = [
374                        name
375                        for name in using_the_same_block_names
376                        if name not in wr_names
377                    ]
378                    attention = " (see 'ATTENTION!' above)"
379            if attention:
380                print_attention(blocked_efuses_after_burn)
381
382    print("\nBurning efuses{}:".format(attention))
383    for efuse, new_value in zip(burn_efuses_list, new_value_list):
384        print(
385            "\n    - '{}' ({}) {} -> {}".format(
386                efuse.name,
387                efuse.description,
388                efuse.get_bitstring(),
389                efuse.convert_to_bitstring(new_value),
390            )
391        )
392        efuse.save(new_value)
393
394    print()
395    if "ENABLE_SECURITY_DOWNLOAD" in efuse_name_list:
396        print(
397            "ENABLE_SECURITY_DOWNLOAD -> 1: eFuses will not be read back "
398            "for confirmation because this mode disables "
399            "any SRAM and register operations."
400        )
401        print("                               espefuse will not work.")
402        print("                               esptool can read/write only flash.")
403
404    if "DIS_DOWNLOAD_MODE" in efuse_name_list:
405        print(
406            "DIS_DOWNLOAD_MODE -> 1: eFuses will not be read back for "
407            "confirmation because this mode disables any communication with the chip."
408        )
409        print(
410            "                        espefuse/esptool will not work because "
411            "they will not be able to connect to the chip."
412        )
413
414    if (
415        esp.CHIP_NAME == "ESP32"
416        and esp.get_chip_revision() >= 300
417        and "UART_DOWNLOAD_DIS" in efuse_name_list
418    ):
419        print(
420            "UART_DOWNLOAD_DIS -> 1: eFuses will be read for confirmation, "
421            "but after that connection to the chip will become impossible."
422        )
423        print("                        espefuse/esptool will not work.")
424
425    if not efuses.burn_all(check_batch_mode=True):
426        return
427
428    print("Checking efuses...")
429    raise_error = False
430    for efuse, old_value, new_value in zip(
431        burn_efuses_list, old_value_list, new_value_list
432    ):
433        if not efuse.is_readable():
434            print(
435                "Efuse %s is read-protected. Read back the burn value is not possible."
436                % efuse.name
437            )
438        else:
439            new_value = efuse.convert_to_bitstring(new_value)
440            burned_value = efuse.get_bitstring()
441            if burned_value != new_value:
442                print(
443                    burned_value,
444                    "->",
445                    new_value,
446                    "Efuse %s failed to burn. Protected?" % efuse.name,
447                )
448                raise_error = True
449    if raise_error:
450        raise esptool.FatalError("The burn was not successful.")
451    else:
452        print("Successful")
453
454
455def read_protect_efuse(esp, efuses, args):
456    util.check_duplicate_name_in_list(args.efuse_name)
457
458    for efuse_name in args.efuse_name:
459        efuse = efuses[efuse_name]
460        if not efuse.is_readable():
461            print("Efuse %s is already read protected" % efuse.name)
462        else:
463            if esp.CHIP_NAME == "ESP32":
464                if (
465                    efuse_name == "BLOCK2"
466                    and not efuses["ABS_DONE_0"].get()
467                    and esp.get_chip_revision() >= 300
468                ):
469                    if efuses["ABS_DONE_1"].get():
470                        raise esptool.FatalError(
471                            "Secure Boot V2 is on (ABS_DONE_1 = True), "
472                            "BLOCK2 must be readable, stop this operation!"
473                        )
474                    else:
475                        print(
476                            "If Secure Boot V2 is used, BLOCK2 must be readable, "
477                            "please stop this operation!"
478                        )
479            elif esp.CHIP_NAME == "ESP32-C2":
480                error = (
481                    not efuses["XTS_KEY_LENGTH_256"].get()
482                    and efuse_name == "BLOCK_KEY0"
483                )
484                error |= efuses["SECURE_BOOT_EN"].get() and efuse_name in [
485                    "BLOCK_KEY0",
486                    "BLOCK_KEY0_HI_128",
487                ]
488                if error:
489                    raise esptool.FatalError(
490                        "%s must be readable, stop this operation!" % efuse_name
491                    )
492            else:
493                for block in efuses.Blocks.BLOCKS:
494                    block = efuses.Blocks.get(block)
495                    if block.name == efuse_name and block.key_purpose is not None:
496                        if not efuses[block.key_purpose].need_rd_protect(
497                            efuses[block.key_purpose].get()
498                        ):
499                            raise esptool.FatalError(
500                                "%s must be readable, stop this operation!" % efuse_name
501                            )
502                        break
503            # make full list of which efuses will be disabled
504            # (ie share a read disable bit)
505            all_disabling = [
506                e for e in efuses if e.read_disable_bit == efuse.read_disable_bit
507            ]
508            names = ", ".join(e.name for e in all_disabling)
509            print(
510                "Permanently read-disabling efuse%s %s"
511                % ("s" if len(all_disabling) > 1 else "", names)
512            )
513            efuse.disable_read()
514
515    if not efuses.burn_all(check_batch_mode=True):
516        return
517
518    print("Checking efuses...")
519    raise_error = False
520    for efuse_name in args.efuse_name:
521        efuse = efuses[efuse_name]
522        if efuse.is_readable():
523            print("Efuse %s is not read-protected." % efuse.name)
524            raise_error = True
525    if raise_error:
526        raise esptool.FatalError("The burn was not successful.")
527    else:
528        print("Successful")
529
530
531def write_protect_efuse(esp, efuses, args):
532    util.check_duplicate_name_in_list(args.efuse_name)
533    for efuse_name in args.efuse_name:
534        efuse = efuses[efuse_name]
535        if not efuse.is_writeable():
536            print("Efuse %s is already write protected" % efuse.name)
537        else:
538            # make full list of which efuses will be disabled
539            # (ie share a write disable bit)
540            all_disabling = [
541                e for e in efuses if e.write_disable_bit == efuse.write_disable_bit
542            ]
543            names = ", ".join(e.name for e in all_disabling)
544            print(
545                "Permanently write-disabling efuse%s %s"
546                % ("s" if len(all_disabling) > 1 else "", names)
547            )
548            efuse.disable_write()
549
550    if not efuses.burn_all(check_batch_mode=True):
551        return
552
553    print("Checking efuses...")
554    raise_error = False
555    for efuse_name in args.efuse_name:
556        efuse = efuses[efuse_name]
557        if efuse.is_writeable():
558            print("Efuse %s is not write-protected." % efuse.name)
559            raise_error = True
560    if raise_error:
561        raise esptool.FatalError("The burn was not successful.")
562    else:
563        print("Successful")
564
565
566def burn_block_data(esp, efuses, args):
567    block_name_list = args.block[
568        0 : len([name for name in args.block if name is not None]) :
569    ]
570    datafile_list = args.datafile[
571        0 : len([name for name in args.datafile if name is not None]) :
572    ]
573    efuses.force_write_always = args.force_write_always
574
575    util.check_duplicate_name_in_list(block_name_list)
576    if args.offset and len(block_name_list) > 1:
577        raise esptool.FatalError(
578            "The 'offset' option is not applicable when a few blocks are passed. "
579            "With 'offset', should only one block be used."
580        )
581    else:
582        offset = args.offset
583        if offset:
584            num_block = efuses.get_index_block_by_name(block_name_list[0])
585            block = efuses.blocks[num_block]
586            num_bytes = block.get_block_len()
587            if offset >= num_bytes:
588                raise esptool.FatalError(
589                    "Invalid offset: the block%d only holds %d bytes."
590                    % (block.id, num_bytes)
591                )
592    if len(block_name_list) != len(datafile_list):
593        raise esptool.FatalError(
594            "The number of block_name (%d) and datafile (%d) should be the same."
595            % (len(block_name_list), len(datafile_list))
596        )
597
598    for block_name, datafile in zip(block_name_list, datafile_list):
599        num_block = efuses.get_index_block_by_name(block_name)
600        block = efuses.blocks[num_block]
601        data = datafile.read()
602        num_bytes = block.get_block_len()
603        if offset != 0:
604            data = (b"\x00" * offset) + data
605            data = data + (b"\x00" * (num_bytes - len(data)))
606        if len(data) != num_bytes:
607            raise esptool.FatalError(
608                "Data does not fit: the block%d size is %d bytes, "
609                "data file is %d bytes, offset %d"
610                % (block.id, num_bytes, len(data), offset)
611            )
612        print(
613            "[{:02}] {:20} size={:02} bytes, offset={:02} - > [{}].".format(
614                block.id, block.name, len(data), offset, util.hexify(data, " ")
615            )
616        )
617        block.save(data)
618
619    if not efuses.burn_all(check_batch_mode=True):
620        return
621    print("Successful")
622
623
624def burn_bit(esp, efuses, args):
625    efuses.force_write_always = args.force_write_always
626    num_block = efuses.get_index_block_by_name(args.block)
627    block = efuses.blocks[num_block]
628    data_block = BitStream(block.get_block_len() * 8)
629    data_block.set(0)
630    try:
631        data_block.set(True, args.bit_number)
632    except IndexError:
633        raise esptool.FatalError(
634            "%s has bit_number in [0..%d]" % (args.block, data_block.len - 1)
635        )
636    data_block.reverse()
637    print(
638        "bit_number:   "
639        "[%-03d]........................................................[0]"
640        % (data_block.len - 1)
641    )
642    print("BLOCK%-2d   :" % block.id, data_block)
643    block.print_block(data_block, "regs_to_write", debug=True)
644    block.save(data_block.bytes[::-1])
645
646    if not efuses.burn_all(check_batch_mode=True):
647        return
648    print("Successful")
649
650
651def check_error(esp, efuses, args):
652    error_in_blocks = efuses.get_coding_scheme_warnings()
653    if args.recovery:
654        if error_in_blocks:
655            confirmed = False
656            for block in reversed(efuses.blocks):
657                if block.fail or block.num_errors > 0:
658                    if not block.get_bitstring().all(False):
659                        block.save(block.get_bitstring().bytes[::-1])
660                        if not confirmed:
661                            confirmed = True
662                            efuses.confirm(
663                                "Recovery of block coding errors", args.do_not_confirm
664                            )
665                        block.burn()
666            # Reset the recovery flag to run check_error() without it,
667            # just to check the new state of eFuse blocks.
668            args.recovery = False
669            check_error(esp, efuses, args)
670    else:
671        if error_in_blocks:
672            raise esptool.FatalError("Error(s) were detected in eFuses")
673    print("No errors detected")
674