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