1# This file describes the common eFuses structures 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 binascii
8import sys
9
10from bitstring import BitArray, BitStream, CreationError
11
12import esptool
13
14from . import util
15from typing import List
16
17
18class CheckArgValue(object):
19    def __init__(self, efuses, name):
20        self.efuses = efuses
21        self.name = name
22
23    def __call__(self, new_value_str):
24        def check_arg_value(efuse, new_value):
25            if efuse.efuse_type.startswith("bool"):
26                new_value = 1 if new_value is None else int(new_value, 0)
27                if new_value != 1:
28                    raise esptool.FatalError(
29                        "New value is not accepted for efuse '{}' "
30                        "(will always burn 0->1), given value={}".format(
31                            efuse.name, new_value
32                        )
33                    )
34            elif efuse.efuse_type.startswith(("int", "uint")):
35                if efuse.efuse_class == "bitcount":
36                    if new_value is None:
37                        # find the first unset bit and set it
38                        old_value = efuse.get_raw()
39                        new_value = old_value
40                        bit = 1
41                        while new_value == old_value:
42                            new_value = bit | old_value
43                            bit <<= 1
44                    else:
45                        new_value = int(new_value, 0)
46                else:
47                    if new_value is None:
48                        raise esptool.FatalError(
49                            "New value required for efuse '{}' (given None)".format(
50                                efuse.name
51                            )
52                        )
53                    new_value = int(new_value, 0)
54                    if new_value == 0:
55                        raise esptool.FatalError(
56                            "New value should not be 0 for '{}' "
57                            "(given value= {})".format(efuse.name, new_value)
58                        )
59            elif efuse.efuse_type.startswith("bytes"):
60                if new_value is None:
61                    raise esptool.FatalError(
62                        "New value required for efuse '{}' (given None)".format(
63                            efuse.name
64                        )
65                    )
66                if len(new_value) * 8 != efuse.bitarray.len:
67                    raise esptool.FatalError(
68                        "The length of efuse '{}' ({} bits) "
69                        "(given len of the new value= {} bits)".format(
70                            efuse.name, efuse.bitarray.len, len(new_value) * 8
71                        )
72                    )
73            else:
74                raise esptool.FatalError(
75                    "The '{}' type for the '{}' efuse is not supported yet.".format(
76                        efuse.efuse_type, efuse.name
77                    )
78                )
79            return new_value
80
81        efuse = self.efuses[self.name]
82        new_value = efuse.check_format(new_value_str)
83        return check_arg_value(efuse, new_value)
84
85
86class EfuseProtectBase(object):
87    # This class is used by EfuseBlockBase and EfuseFieldBase
88
89    def get_read_disable_mask(self, blk_part=None):
90        """Returns mask of read protection bits
91        blk_part:
92            - None: Calculate mask for all read protection bits.
93            - a number: Calculate mask only for specific item in read protection list.
94        """
95        mask = 0
96        if isinstance(self.read_disable_bit, list):
97            if blk_part is None:
98                for i in self.read_disable_bit:
99                    mask |= 1 << i
100            else:
101                mask |= 1 << self.read_disable_bit[blk_part]
102        else:
103            mask = 1 << self.read_disable_bit
104        return mask
105
106    def get_count_read_disable_bits(self):
107        """Returns the number of read protection bits used by the field"""
108        # On the C2 chip, BLOCK_KEY0 has two read protection bits [0, 1].
109        return bin(self.get_read_disable_mask()).count("1")
110
111    def is_readable(self, blk_part=None):
112        """Return true if the efuse is readable by software"""
113        num_bit = self.read_disable_bit
114        if num_bit is None:
115            return True  # read cannot be disabled
116        return (self.parent["RD_DIS"].get() & self.get_read_disable_mask(blk_part)) == 0
117
118    def disable_read(self):
119        num_bit = self.read_disable_bit
120        if num_bit is None:
121            raise esptool.FatalError("This efuse cannot be read-disabled")
122        if not self.parent["RD_DIS"].is_writeable():
123            raise esptool.FatalError(
124                "This efuse cannot be read-disabled due the to RD_DIS field is "
125                "already write-disabled"
126            )
127        self.parent["RD_DIS"].save(self.get_read_disable_mask())
128
129    def is_writeable(self):
130        num_bit = self.write_disable_bit
131        if num_bit is None:
132            return True  # write cannot be disabled
133        return (self.parent["WR_DIS"].get() & (1 << num_bit)) == 0
134
135    def disable_write(self):
136        num_bit = self.write_disable_bit
137        if not self.parent["WR_DIS"].is_writeable():
138            raise esptool.FatalError(
139                "This efuse cannot be write-disabled due to the WR_DIS field is "
140                "already write-disabled"
141            )
142        self.parent["WR_DIS"].save(1 << num_bit)
143
144    def check_wr_rd_protect(self):
145        if not self.is_readable():
146            error_msg = "\t{} is read-protected.".format(self.name)
147            "The written value can not be read, the efuse/block looks as all 0.\n"
148            error_msg += "\tBurn in this case may damage an already written value."
149            self.parent.print_error_msg(error_msg)
150        if not self.is_writeable():
151            error_msg = "\t{} is write-protected. Burn is not possible.".format(
152                self.name
153            )
154            self.parent.print_error_msg(error_msg)
155
156
157class EfuseBlockBase(EfuseProtectBase):
158    def __init__(self, parent, param, skip_read=False):
159        self.parent = parent
160        self.name = param.name
161        self.alias = param.alias
162        self.id = param.id
163        self.rd_addr = param.rd_addr
164        self.wr_addr = param.wr_addr
165        self.write_disable_bit = param.write_disable_bit
166        self.read_disable_bit = param.read_disable_bit
167        self.len = param.len
168        self.key_purpose_name = param.key_purpose
169        bit_block_len = self.get_block_len() * 8
170        self.bitarray = BitStream(bit_block_len)
171        self.bitarray.set(0)
172        self.wr_bitarray = BitStream(bit_block_len)
173        self.wr_bitarray.set(0)
174        self.fail = False
175        self.num_errors = 0
176        if self.id == 0:
177            self.err_bitarray = BitStream(bit_block_len)
178            self.err_bitarray.set(0)
179        else:
180            self.err_bitarray = None
181
182        if not skip_read:
183            self.read()
184
185    def get_block_len(self):
186        coding_scheme = self.get_coding_scheme()
187        if coding_scheme == self.parent.REGS.CODING_SCHEME_NONE:
188            return self.len * 4
189        elif coding_scheme == self.parent.REGS.CODING_SCHEME_34:
190            return (self.len * 3 // 4) * 4
191        elif coding_scheme == self.parent.REGS.CODING_SCHEME_RS:
192            return self.len * 4
193        else:
194            raise esptool.FatalError(
195                "Coding scheme (%d) not supported" % (coding_scheme)
196            )
197
198    def get_coding_scheme(self):
199        if self.id == 0:
200            return self.parent.REGS.CODING_SCHEME_NONE
201        else:
202            return self.parent.coding_scheme
203
204    def get_raw(self, from_read=True):
205        if from_read:
206            return self.bitarray.bytes
207        else:
208            return self.wr_bitarray.bytes
209
210    def get(self, from_read=True):
211        self.get_bitstring(from_read=from_read)
212
213    def get_bitstring(self, from_read=True):
214        if from_read:
215            return self.bitarray
216        else:
217            return self.wr_bitarray
218
219    def convert_to_bitstring(self, new_data):
220        if isinstance(new_data, BitArray):
221            return new_data
222        else:
223            return BitArray(bytes=new_data, length=len(new_data) * 8)
224
225    def get_words(self):
226        def get_offsets(self):
227            return [x + self.rd_addr for x in range(0, self.get_block_len(), 4)]
228
229        return [self.parent.read_reg(offs) for offs in get_offsets(self)]
230
231    def read(self, print_info=True):
232        words = self.get_words()
233        data = BitArray()
234        for word in reversed(words):
235            data.append("uint:32=%d" % word)
236        self.bitarray.overwrite(data, pos=0)
237        if print_info:
238            self.print_block(self.bitarray, "read_regs")
239
240    def print_block(self, bit_string, comment, debug=False):
241        if self.parent.debug or debug:
242            bit_string.pos = 0
243            print(
244                "%-15s (%-16s) [%-2d] %s:"
245                % (self.name, " ".join(self.alias)[:16], self.id, comment),
246                " ".join(
247                    [
248                        "%08x" % word
249                        for word in bit_string.readlist(
250                            "%d*uint:32" % (bit_string.len / 32)
251                        )[::-1]
252                    ]
253                ),
254            )
255
256    def check_wr_data(self):
257        wr_data = self.wr_bitarray
258        if wr_data.all(False):
259            # nothing to burn
260            if self.parent.debug:
261                print("[{:02}] {:20} nothing to burn".format(self.id, self.name))
262            return False
263        if len(wr_data.bytes) != len(self.bitarray.bytes):
264            raise esptool.FatalError(
265                "Data does not fit: the block%d size is %d bytes, data is %d bytes"
266                % (self.id, len(self.bitarray.bytes), len(wr_data.bytes))
267            )
268        self.check_wr_rd_protect()
269
270        if self.get_bitstring().all(False):
271            print(
272                "[{:02}] {:20} is empty, will burn the new value".format(
273                    self.id, self.name
274                )
275            )
276        else:
277            # the written block in chip is not empty
278            if self.get_bitstring() == wr_data:
279                print(
280                    "[{:02}] {:20} is already written the same value, "
281                    "continue with EMPTY_BLOCK".format(self.id, self.name)
282                )
283                wr_data.set(0)
284            else:
285                print("[{:02}] {:20} is not empty".format(self.id, self.name))
286                print("\t(written ):", self.get_bitstring())
287                print("\t(to write):", wr_data)
288                mask = self.get_bitstring() & wr_data
289                if mask == wr_data:
290                    print(
291                        "\tAll wr_data bits are set in the written block, "
292                        "continue with EMPTY_BLOCK."
293                    )
294                    wr_data.set(0)
295                else:
296                    coding_scheme = self.get_coding_scheme()
297                    if coding_scheme == self.parent.REGS.CODING_SCHEME_NONE:
298                        print("\t(coding scheme = NONE)")
299                    elif coding_scheme == self.parent.REGS.CODING_SCHEME_RS:
300                        print("\t(coding scheme = RS)")
301                        error_msg = (
302                            "\tBurn into %s is forbidden "
303                            "(RS coding scheme does not allow this)." % (self.name)
304                        )
305                        self.parent.print_error_msg(error_msg)
306                    elif coding_scheme == self.parent.REGS.CODING_SCHEME_34:
307                        print("\t(coding scheme = 3/4)")
308                        data_can_not_be_burn = False
309                        for i in range(0, self.get_bitstring().len, 6 * 8):
310                            rd_chunk = self.get_bitstring()[i : i + 6 * 8 :]
311                            wr_chunk = wr_data[i : i + 6 * 8 :]
312                            if rd_chunk.any(True):
313                                if wr_chunk.any(True):
314                                    print(
315                                        "\twritten chunk [%d] and wr_chunk "
316                                        "are not empty. " % (i // (6 * 8)),
317                                        end="",
318                                    )
319                                    if rd_chunk == wr_chunk:
320                                        print(
321                                            "wr_chunk == rd_chunk. "
322                                            "Continue with empty chunk."
323                                        )
324                                        wr_data[i : i + 6 * 8 :].set(0)
325                                    else:
326                                        print("wr_chunk != rd_chunk. Can not burn.")
327                                        print("\twritten ", rd_chunk)
328                                        print("\tto write", wr_chunk)
329                                        data_can_not_be_burn = True
330                        if data_can_not_be_burn:
331                            error_msg = (
332                                "\tBurn into %s is forbidden "
333                                "(3/4 coding scheme does not allow this)." % (self.name)
334                            )
335                            self.parent.print_error_msg(error_msg)
336                    else:
337                        raise esptool.FatalError(
338                            "The coding scheme ({}) is not supported".format(
339                                coding_scheme
340                            )
341                        )
342
343    def save(self, new_data):
344        # new_data will be checked by check_wr_data() during burn_all()
345        # new_data (bytes)  = [0][1][2] ... [N]            (original data)
346        # in string format  = [0] [1] [2] ... [N]          (util.hexify(data, " "))
347        # in hex format     = 0x[N]....[2][1][0]           (from bitstring print(data))
348        # in reg format     = [3][2][1][0] ... [N][][][]   (as it will be in the device)
349        # in bitstring      = [N] ... [2][1][0]            (to get a correct bitstring
350        #                                                   need to reverse new_data)
351        # *[x] - means a byte.
352        data = BitStream(bytes=new_data[::-1], length=len(new_data) * 8)
353        if self.parent.debug:
354            print(
355                "\twritten : {} ->\n\tto write: {}".format(self.get_bitstring(), data)
356            )
357        self.wr_bitarray.overwrite(self.wr_bitarray | data, pos=0)
358
359    def burn_words(self, words):
360        for burns in range(3):
361            self.parent.efuse_controller_setup()
362            if self.parent.debug:
363                print("Write data to BLOCK%d" % (self.id))
364            write_reg_addr = self.wr_addr
365            for word in words:
366                # for ep32s2: using EFUSE_PGM_DATA[0..7]_REG for writing data
367                #   32 bytes to EFUSE_PGM_DATA[0..7]_REG
368                #   12 bytes to EFUSE_CHECK_VALUE[0..2]_REG. These regs are next after
369                #   EFUSE_PGM_DATA_REG
370                # for esp32:
371                #   each block has the special regs EFUSE_BLK[0..3]_WDATA[0..7]_REG
372                #   for writing data
373                if self.parent.debug:
374                    print("Addr 0x%08x, data=0x%08x" % (write_reg_addr, word))
375                self.parent.write_reg(write_reg_addr, word)
376                write_reg_addr += 4
377
378            self.parent.write_efuses(self.id)
379            for _ in range(5):
380                self.parent.efuse_read()
381                self.parent.get_coding_scheme_warnings(silent=True)
382                if self.fail or self.num_errors:
383                    print(
384                        "Error in BLOCK%d, re-burn it again (#%d), to fix it. "
385                        "fail_bit=%d, num_errors=%d"
386                        % (self.id, burns, self.fail, self.num_errors)
387                    )
388                    break
389            if not self.fail and self.num_errors == 0:
390                self.read(print_info=False)
391                if self.wr_bitarray & self.bitarray != self.wr_bitarray:
392                    # if the required bits are not set then we need to re-burn it again.
393                    if burns < 2:
394                        print(
395                            f"\nRepeat burning BLOCK{self.id} (#{burns + 2}) because not all bits were set"
396                        )
397                        continue
398                    else:
399                        print(
400                            f"\nAfter {burns + 1} attempts, the required data was not set to BLOCK{self.id}"
401                        )
402                break
403
404    def burn(self):
405        if self.wr_bitarray.all(False):
406            # nothing to burn
407            return
408        before_burn_bitarray = self.bitarray[:]
409        assert before_burn_bitarray is not self.bitarray
410        self.print_block(self.wr_bitarray, "to_write")
411        words = self.apply_coding_scheme()
412        self.burn_words(words)
413        self.read()
414        if not self.is_readable():
415            print(
416                "{} ({}) is read-protected. "
417                "Read back the burn value is not possible.".format(
418                    self.name, self.alias
419                )
420            )
421            if self.bitarray.all(False):
422                print("Read all '0'")
423            else:
424                # Should never happen
425                raise esptool.FatalError(
426                    "The {} is read-protected but not all '0' ({})".format(
427                        self.name, self.bitarray.hex
428                    )
429                )
430        else:
431            if self.wr_bitarray == self.bitarray:
432                print("BURN BLOCK%-2d - OK (write block == read block)" % self.id)
433            elif (
434                self.wr_bitarray & self.bitarray == self.wr_bitarray
435                and self.bitarray & before_burn_bitarray == before_burn_bitarray
436            ):
437                print("BURN BLOCK%-2d - OK (all write block bits are set)" % self.id)
438            else:
439                # Happens only when an efuse is written and read-protected
440                # in one command
441                self.print_block(self.wr_bitarray, "Expected")
442                self.print_block(self.bitarray, "Real    ")
443                # Read-protected BLK0 values are reported back as zeros,
444                # raise error only for other blocks
445                if self.id != 0:
446                    raise esptool.FatalError(
447                        "Burn {} ({}) was not successful".format(self.name, self.alias)
448                    )
449        self.wr_bitarray.set(0)
450
451
452class EspEfusesBase(object):
453    """
454    Wrapper object to manage the efuse fields in a connected ESP bootloader
455    """
456
457    _esp = None
458    blocks: List[EfuseBlockBase] = []
459    efuses: List = []
460    coding_scheme = None
461    force_write_always = None
462    batch_mode_cnt = 0
463    postpone = False
464
465    def __iter__(self):
466        return self.efuses.__iter__()
467
468    def get_crystal_freq(self):
469        return self._esp.get_crystal_freq()
470
471    def read_efuse(self, n):
472        """Read the nth word of the ESP3x EFUSE region."""
473        return self._esp.read_efuse(n)
474
475    def read_reg(self, addr):
476        return self._esp.read_reg(addr)
477
478    def write_reg(self, addr, value, mask=0xFFFFFFFF, delay_us=0, delay_after_us=0):
479        return self._esp.write_reg(addr, value, mask, delay_us, delay_after_us)
480
481    def update_reg(self, addr, mask, new_val):
482        return self._esp.update_reg(addr, mask, new_val)
483
484    def efuse_controller_setup(self):
485        pass
486
487    def reconnect_chip(self, esp):
488        print("Re-connecting...")
489        baudrate = esp._port.baudrate
490        port = esp._port.port
491        esp._port.close()
492        return esptool.cmds.detect_chip(port, baudrate)
493
494    def get_index_block_by_name(self, name):
495        for block in self.blocks:
496            if block.name == name or name in block.alias:
497                return block.id
498        return None
499
500    def read_blocks(self):
501        for block in self.blocks:
502            block.read()
503
504    def update_efuses(self):
505        for efuse in self.efuses:
506            efuse.update(self.blocks[efuse.block].bitarray)
507
508    def postpone_efuses_from_block0_to_burn(self, block):
509        postpone_efuses = {}
510
511        if block.id != 0:
512            return postpone_efuses
513
514        # We need to check this list of efuses. If we are going to burn an efuse
515        # from this list, then we need to split the burn operation into two
516        # steps. The first step involves burning efuses not in this list. In
517        # case of an error during this step, we can recover by burning the
518        # efuses from this list at the very end. This approach provides the
519        # ability to recover efuses if an error occurs during the initial burn
520        # operation.
521
522        # List the efuses here that must be burned at the very end, such as read
523        # and write protection fields, as well as efuses that disable
524        # communication with the espefuse tool.
525        efuses_list = ["WR_DIS", "RD_DIS"]
526        if self._esp.CHIP_NAME == "ESP32":
527            # Efuses below disables communication with the espefuse tool.
528            efuses_list.append("UART_DOWNLOAD_DIS")
529            # other efuses that are better to burn at the very end.
530            efuses_list.append("ABS_DONE_1")
531            efuses_list.append("FLASH_CRYPT_CNT")
532        else:
533            # Efuses below disables communication with the espefuse tool.
534            efuses_list.append("ENABLE_SECURITY_DOWNLOAD")
535            efuses_list.append("DIS_DOWNLOAD_MODE")
536            # other efuses that are better to burn at the very end.
537            efuses_list.append("SPI_BOOT_CRYPT_CNT")
538            efuses_list.append("SECURE_BOOT_EN")
539
540        def get_raw_value_from_write(self, efuse_name):
541            return self[efuse_name].get_bitstring(from_read=False)
542
543        for efuse_name in efuses_list:
544            postpone_efuses[efuse_name] = get_raw_value_from_write(self, efuse_name)
545
546        if any(value != 0 for value in postpone_efuses.values()):
547            if self.debug:
548                print("These BLOCK0 efuses will be burned later at the very end:")
549                print(postpone_efuses)
550            # exclude these efuses from the first burn (postpone them till the end).
551            for key_name in postpone_efuses.keys():
552                self[key_name].reset()
553        return postpone_efuses
554
555    def recover_postponed_efuses_from_block0_to_burn(self, postpone_efuses):
556        if any(value != 0 for value in postpone_efuses.values()):
557            print("Burn postponed efuses from BLOCK0.")
558            for key_name in postpone_efuses.keys():
559                self[key_name].save(postpone_efuses[key_name])
560
561    def burn_all(self, check_batch_mode=False):
562        if check_batch_mode:
563            if self.batch_mode_cnt != 0:
564                print(
565                    "\nBatch mode is enabled, "
566                    "the burn will be done at the end of the command."
567                )
568                return False
569        print("\nCheck all blocks for burn...")
570        print("idx, BLOCK_NAME,          Conclusion")
571        have_wr_data_for_burn = False
572        for block in self.blocks:
573            block.check_wr_data()
574            if not have_wr_data_for_burn and block.get_bitstring(from_read=False).any(
575                True
576            ):
577                have_wr_data_for_burn = True
578        if not have_wr_data_for_burn:
579            print("Nothing to burn, see messages above.")
580            return True
581        EspEfusesBase.confirm("", self.do_not_confirm)
582
583        def burn_block(block, postponed_efuses):
584            old_fail = block.fail
585            old_num_errors = block.num_errors
586            block.burn()
587            if (block.fail and old_fail != block.fail) or (
588                block.num_errors and block.num_errors > old_num_errors
589            ):
590                if postponed_efuses:
591                    print("The postponed efuses were not burned due to an error.")
592                    print("\t1. Try to fix a coding error by this cmd:")
593                    print("\t   'espefuse.py check_error --recovery'")
594                    command_string = " ".join(
595                        f"{key} {value}"
596                        for key, value in postponed_efuses.items()
597                        if value.any(True)
598                    )
599                    print("\t2. Then run the cmd to burn all postponed efuses:")
600                    print(f"\t   'espefuse.py burn_efuse {command_string}'")
601
602                raise esptool.FatalError("Error(s) were detected in eFuses")
603
604        # Burn from BLKn -> BLK0. Because BLK0 can set rd or/and wr protection bits.
605        for block in reversed(self.blocks):
606            postponed_efuses = (
607                self.postpone_efuses_from_block0_to_burn(block)
608                if self.postpone
609                else None
610            )
611
612            burn_block(block, postponed_efuses)
613
614            if postponed_efuses:
615                self.recover_postponed_efuses_from_block0_to_burn(postponed_efuses)
616                burn_block(block, postponed_efuses)
617
618        print("Reading updated efuses...")
619        self.read_coding_scheme()
620        self.read_blocks()
621        self.update_efuses()
622        return True
623
624    @staticmethod
625    def confirm(action, do_not_confirm):
626        print(
627            "%s%s\nThis is an irreversible operation!"
628            % (action, "" if action.endswith("\n") else ". ")
629        )
630        if not do_not_confirm:
631            print("Type 'BURN' (all capitals) to continue.")
632            # required for Pythons which disable line buffering, ie mingw in mintty
633            sys.stdout.flush()
634            yes = input()
635            if yes != "BURN":
636                print("Aborting.")
637                sys.exit(0)
638
639    def print_error_msg(self, error_msg):
640        if self.force_write_always is not None:
641            if not self.force_write_always:
642                error_msg += "(use '--force-write-always' option to ignore it)"
643        if self.force_write_always:
644            print(error_msg, "Skipped because '--force-write-always' option.")
645        else:
646            raise esptool.FatalError(error_msg)
647
648    def get_block_errors(self, block_num):
649        """Returns (error count, failure boolean flag)"""
650        return self.blocks[block_num].num_errors, self.blocks[block_num].fail
651
652
653class EfuseFieldBase(EfuseProtectBase):
654    def __init__(self, parent, param):
655        self.category = param.category
656        self.parent = parent
657        self.block = param.block
658        self.word = param.word
659        self.pos = param.pos
660        self.write_disable_bit = param.write_disable_bit
661        self.read_disable_bit = param.read_disable_bit
662        self.name = param.name
663        self.efuse_class = param.class_type
664        self.efuse_type = param.type
665        self.description = param.description
666        self.dict_value = param.dictionary
667        self.bit_len = param.bit_len
668        self.alt_names = param.alt_names
669        self.fail = False
670        self.num_errors = 0
671        self.bitarray = BitStream(self.bit_len)
672        self.bitarray.set(0)
673        self.update(self.parent.blocks[self.block].bitarray)
674
675    def is_field_calculated(self):
676        return self.word is None or self.pos is None
677
678    def check_format(self, new_value_str):
679        if new_value_str is None:
680            return new_value_str
681        else:
682            if self.efuse_type.startswith("bytes"):
683                if new_value_str.startswith("0x"):
684                    # cmd line: 0x0102030405060708 .... 112233ff      (hex)
685                    # regs: 112233ff ... 05060708 01020304
686                    # BLK: ff 33 22 11 ... 08 07 06 05 04 03 02 01
687                    return binascii.unhexlify(new_value_str[2:])[::-1]
688                else:
689                    # cmd line: 0102030405060708 .... 112233ff        (string)
690                    # regs: 04030201 08070605 ... ff332211
691                    # BLK: 01 02 03 04 05 06 07 08 ... 11 22 33 ff
692                    return binascii.unhexlify(new_value_str)
693            else:
694                return new_value_str
695
696    def convert_to_bitstring(self, new_value):
697        if isinstance(new_value, BitArray):
698            return new_value
699        else:
700            if self.efuse_type.startswith("bytes"):
701                # new_value (bytes) = [0][1][2] ... [N]
702                #                                                        (original data)
703                # in string format  = [0] [1] [2] ... [N]
704                #                                               (util.hexify(data, " "))
705                # in hex format     = 0x[N]....[2][1][0]
706                #                                           (from bitstring print(data))
707                # in reg format     = [3][2][1][0] ... [N][][][]
708                #                                          (as it will be in the device)
709                # in bitstring      = [N] ... [2][1][0]
710                #                 (to get a correct bitstring need to reverse new_value)
711                # *[x] - means a byte.
712                return BitArray(bytes=new_value[::-1], length=len(new_value) * 8)
713            else:
714                try:
715                    return BitArray(self.efuse_type + "={}".format(new_value))
716                except CreationError as err:
717                    print(
718                        "New value '{}' is not suitable for {} ({})".format(
719                            new_value, self.name, self.efuse_type
720                        )
721                    )
722                    raise esptool.FatalError(err)
723
724    def check_new_value(self, bitarray_new_value):
725        bitarray_old_value = self.get_bitstring() | self.get_bitstring(from_read=False)
726
727        if not bitarray_new_value.any(True) and not bitarray_old_value.any(True):
728            return
729
730        if bitarray_new_value.len != bitarray_old_value.len:
731            raise esptool.FatalError(
732                "For {} efuse, the length of the new value is wrong, "
733                "expected {} bits, was {} bits.".format(
734                    self.name, bitarray_old_value.len, bitarray_new_value.len
735                )
736            )
737        if (
738            bitarray_new_value == bitarray_old_value
739            or bitarray_new_value & self.get_bitstring() == bitarray_new_value
740        ):
741            error_msg = "\tThe same value for {} ".format(self.name)
742            error_msg += "is already burned. Do not change the efuse."
743            print(error_msg)
744            bitarray_new_value.set(0)
745        elif bitarray_new_value == self.get_bitstring(from_read=False):
746            error_msg = "\tThe same value for {} ".format(self.name)
747            error_msg += "is already prepared for the burn operation."
748            print(error_msg)
749            bitarray_new_value.set(0)
750        else:
751            if self.name not in ["WR_DIS", "RD_DIS"]:
752                # WR_DIS, RD_DIS fields can have already set bits.
753                # Do not need to check below condition for them.
754                if bitarray_new_value | bitarray_old_value != bitarray_new_value:
755                    error_msg = "\tNew value contains some bits that cannot be cleared "
756                    error_msg += "(value will be {})".format(
757                        bitarray_old_value | bitarray_new_value
758                    )
759                    self.parent.print_error_msg(error_msg)
760            self.check_wr_rd_protect()
761
762    def save_to_block(self, bitarray_field):
763        block = self.parent.blocks[self.block]
764        wr_bitarray_temp = block.wr_bitarray.copy()
765        position = wr_bitarray_temp.length - (
766            self.word * 32 + self.pos + bitarray_field.len
767        )
768        wr_bitarray_temp.overwrite(bitarray_field, pos=position)
769        block.wr_bitarray |= wr_bitarray_temp
770
771    def save(self, new_value):
772        bitarray_field = self.convert_to_bitstring(new_value)
773        self.check_new_value(bitarray_field)
774        self.save_to_block(bitarray_field)
775
776    def update(self, bit_array_block):
777        if self.is_field_calculated():
778            self.bitarray.overwrite(
779                self.convert_to_bitstring(self.check_format(self.get())), pos=0
780            )
781            return
782        field_len = self.bitarray.len
783        bit_array_block.pos = bit_array_block.length - (
784            self.word * 32 + self.pos + field_len
785        )
786        self.bitarray.overwrite(bit_array_block.read(field_len), pos=0)
787        err_bitarray = self.parent.blocks[self.block].err_bitarray
788        if err_bitarray is not None:
789            err_bitarray.pos = err_bitarray.length - (
790                self.word * 32 + self.pos + field_len
791            )
792            self.fail = not err_bitarray.read(field_len).all(False)
793        else:
794            self.fail = self.parent.blocks[self.block].fail
795            self.num_errors = self.parent.blocks[self.block].num_errors
796
797    def get_raw(self, from_read=True):
798        """Return the raw (unformatted) numeric value of the efuse bits
799
800        Returns a simple integer or (for some subclasses) a bitstring.
801        type: int or bool -> int
802        type: bytes -> bytearray
803        """
804        return self.get_bitstring(from_read).read(self.efuse_type)
805
806    def get(self, from_read=True):
807        """Get a formatted version of the efuse value, suitable for display
808        type: int or bool -> int
809        type: bytes -> string  "01 02 03 04 05 06 07 08 ... ".
810        Byte order [0] ... [N]. dump regs: 0x04030201 0x08070605 ...
811        """
812        if self.efuse_type.startswith("bytes"):
813            return util.hexify(self.get_bitstring(from_read).bytes[::-1], " ")
814        else:
815            return self.get_raw(from_read)
816
817    def get_meaning(self, from_read=True):
818        """Get the meaning of efuse from dict if possible, suitable for display"""
819        if self.dict_value:
820            try:
821                return self.dict_value[self.get_raw(from_read)]
822            except KeyError:
823                pass
824        return self.get(from_read)
825
826    def get_bitstring(self, from_read=True):
827        if from_read:
828            self.bitarray.pos = 0
829            return self.bitarray
830        else:
831            field_len = self.bitarray.len
832            block = self.parent.blocks[self.block]
833            block.wr_bitarray.pos = block.wr_bitarray.length - (
834                self.word * 32 + self.pos + field_len
835            )
836            return block.wr_bitarray.read(self.bitarray.len)
837
838    def burn(self, new_value):
839        # Burn a efuse. Added for compatibility reason.
840        self.save(new_value)
841        self.parent.burn_all()
842
843    def get_info(self):
844        output = f"{self.name} (BLOCK{self.block})"
845        if self.block == 0:
846            if self.fail:
847                output += "[error]"
848        else:
849            errs, fail = self.parent.get_block_errors(self.block)
850            if errs != 0 or fail:
851                output += "[error]"
852        if self.efuse_class == "keyblock":
853            name = self.parent.blocks[self.block].key_purpose_name
854            if name is not None:
855                output += f"\n  Purpose: {self.parent[name].get()}\n "
856        return output
857
858    def reset(self):
859        # resets a efuse that is prepared for burning
860        bitarray_field = self.convert_to_bitstring(0)
861        block = self.parent.blocks[self.block]
862        wr_bitarray_temp = block.wr_bitarray.copy()
863        position = wr_bitarray_temp.length - (
864            self.word * 32 + self.pos + bitarray_field.len
865        )
866        wr_bitarray_temp.overwrite(bitarray_field, pos=position)
867        block.wr_bitarray = wr_bitarray_temp
868