1# This file describes eFuses controller for ESP32 chip
2#
3# SPDX-FileCopyrightText: 2020-2022 Espressif Systems (Shanghai) CO LTD
4#
5# SPDX-License-Identifier: GPL-2.0-or-later
6
7import re
8
9from bitstring import BitStream
10
11
12class EmulateEfuseControllerBase(object):
13    """The class for virtual efuse operations. Using for HOST_TEST."""
14
15    CHIP_NAME = ""
16    mem = None
17    debug = False
18    Blocks = None
19    Fields = None
20    REGS = None
21
22    def __init__(self, efuse_file=None, debug=False):
23        self.debug = debug
24        self.efuse_file = efuse_file
25        if self.efuse_file:
26            try:
27                self.mem = BitStream(
28                    bytes=open(self.efuse_file, "rb").read(),
29                    length=self.REGS.EFUSE_MEM_SIZE * 8,
30                )
31            except (ValueError, FileNotFoundError):
32                # the file is empty or does not fit the length.
33                self.mem = BitStream(length=self.REGS.EFUSE_MEM_SIZE * 8)
34                self.mem.set(0)
35                self.mem.tofile(open(self.efuse_file, "a+b"))
36        else:
37            # efuse_file is not provided
38            #  it means we do not want to keep the result of efuse operations
39            self.mem = BitStream(self.REGS.EFUSE_MEM_SIZE * 8)
40            self.mem.set(0)
41
42    """ esptool method start >> """
43
44    def get_chip_description(self):
45        major_rev = self.get_major_chip_version()
46        minor_rev = self.get_minor_chip_version()
47        return f"{self.CHIP_NAME} (revision v{major_rev}.{minor_rev})"
48
49    def get_chip_revision(self):
50        return self.get_major_chip_version() * 100 + self.get_minor_chip_version()
51
52    def read_efuse(self, n, block=0):
53        """Read the nth word of the ESP3x EFUSE region."""
54        blk = self.Blocks.get(self.Blocks.BLOCKS[block])
55        return self.read_reg(blk.rd_addr + (4 * n))
56
57    def read_reg(self, addr):
58        self.mem.pos = self.mem.length - ((addr - self.REGS.DR_REG_EFUSE_BASE) * 8 + 32)
59        return self.mem.read("uint:32")
60
61    def write_reg(self, addr, value, mask=0xFFFFFFFF, delay_us=0, delay_after_us=0):
62        self.mem.pos = self.mem.length - ((addr - self.REGS.DR_REG_EFUSE_BASE) * 8 + 32)
63        self.mem.overwrite("uint:32={}".format(value & mask))
64        self.handle_writing_event(addr, value)
65
66    def update_reg(self, addr, mask, new_val):
67        position = self.mem.length - ((addr - self.REGS.DR_REG_EFUSE_BASE) * 8 + 32)
68        self.mem.pos = position
69        cur_val = self.mem.read("uint:32")
70        self.mem.pos = position
71        self.mem.overwrite("uint:32={}".format(cur_val | (new_val & mask)))
72
73    def write_efuse(self, n, value, block=0):
74        """Write the nth word of the ESP3x EFUSE region."""
75        blk = self.Blocks.get(self.Blocks.BLOCKS[block])
76        self.write_reg(blk.wr_addr + (4 * n), value)
77
78    """ << esptool method end """
79
80    def handle_writing_event(self, addr, value):
81        self.save_to_file()
82
83    def save_to_file(self):
84        if self.efuse_file:
85            with open(self.efuse_file, "wb") as f:
86                self.mem.tofile(f)
87
88    def handle_coding_scheme(self, blk, data):
89        return data
90
91    def copy_blocks_wr_regs_to_rd_regs(self, updated_block=None):
92        for b in reversed(self.Blocks.BLOCKS):
93            blk = self.Blocks.get(b)
94            if updated_block is not None:
95                if blk.id != updated_block:
96                    continue
97            data = self.read_block(blk.id, wr_regs=True)
98            if self.debug:
99                print(blk.name, data.hex)
100            plain_data = self.handle_coding_scheme(blk, data)
101            plain_data = self.check_wr_protection_area(blk.id, plain_data)
102            self.update_block(blk, plain_data)
103
104    def clean_blocks_wr_regs(self):
105        for b in self.Blocks.BLOCKS:
106            blk = self.Blocks.get(b)
107            for offset in range(0, blk.len * 4, 4):
108                wr_addr = blk.wr_addr + offset
109                self.write_reg(wr_addr, 0)
110
111    def read_field(self, name, bitstring=True):
112        for e in self.Fields.EFUSES:
113            field = self.Fields.get(e)
114            if field.name == name:
115                self.read_block(field.block)
116                block = self.read_block(field.block)
117                if field.type.startswith("bool"):
118                    field_len = 1
119                else:
120                    field_len = int(re.search(r"\d+", field.type).group())
121                    if field.type.startswith("bytes"):
122                        field_len *= 8
123                block.pos = block.length - (field.word * 32 + field.pos + field_len)
124                if bitstring:
125                    return block.read(field_len)
126                else:
127                    return block.read(field.type)
128        return None
129
130    def get_bitlen_of_block(self, blk, wr=False):
131        return 32 * blk.len
132
133    def read_block(self, idx, wr_regs=False):
134        block = None
135        for b in self.Blocks.BLOCKS:
136            blk = self.Blocks.get(b)
137            if blk.id == idx:
138                blk_len_bits = self.get_bitlen_of_block(blk, wr=wr_regs)
139                addr = blk.wr_addr if wr_regs else blk.rd_addr
140                self.mem.pos = self.mem.length - (
141                    (addr - self.REGS.DR_REG_EFUSE_BASE) * 8 + blk_len_bits
142                )
143                block = self.mem.read(blk_len_bits)
144                break
145        return block
146
147    def update_block(self, blk, wr_data):
148        wr_data = self.read_block(blk.id) | wr_data
149        self.overwrite_mem_from_block(blk, wr_data)
150
151    def overwrite_mem_from_block(self, blk, wr_data):
152        self.mem.pos = self.mem.length - (
153            (blk.rd_addr - self.REGS.DR_REG_EFUSE_BASE) * 8 + wr_data.len
154        )
155        self.mem.overwrite(wr_data)
156
157    def check_wr_protection_area(self, num_blk, wr_data):
158        # checks fields which have the write protection bit.
159        # if the write protection bit is set, we need to protect that area from changes.
160        write_disable_bit = self.read_field("WR_DIS", bitstring=False)
161        mask_wr_data = BitStream(len(wr_data))
162        mask_wr_data.set(0)
163        blk = self.Blocks.get(self.Blocks.BLOCKS[num_blk])
164        if blk.write_disable_bit is not None and write_disable_bit & (
165            1 << blk.write_disable_bit
166        ):
167            mask_wr_data.set(1)
168        else:
169            for e in self.Fields.EFUSES:
170                field = self.Fields.get(e)
171                if blk.id == field.block and field.block == num_blk:
172                    if field.write_disable_bit is not None and write_disable_bit & (
173                        1 << field.write_disable_bit
174                    ):
175                        data = self.read_field(field.name)
176                        data.set(1)
177                        mask_wr_data.pos = mask_wr_data.length - (
178                            field.word * 32 + field.pos + data.len
179                        )
180                        mask_wr_data.overwrite(data)
181        mask_wr_data.invert()
182        return wr_data & mask_wr_data
183
184    def check_rd_protection_area(self):
185        # checks fields which have the read protection bits.
186        # if the read protection bit is set then we need to reset this field to 0.
187        read_disable_bit = self.read_field("RD_DIS", bitstring=False)
188        for b in self.Blocks.BLOCKS:
189            blk = self.Blocks.get(b)
190            block = self.read_block(blk.id)
191            if blk.read_disable_bit is not None and read_disable_bit & (
192                1 << blk.read_disable_bit
193            ):
194                block.set(0)
195            else:
196                for e in self.Fields.EFUSES:
197                    field = self.Fields.get(e)
198                    if (
199                        blk.id == field.block
200                        and field.read_disable_bit is not None
201                        and read_disable_bit & (1 << field.read_disable_bit)
202                    ):
203                        raw_data = self.read_field(field.name)
204                        raw_data.set(0)
205                        block.pos = block.length - (
206                            field.word * 32 + field.pos + raw_data.length
207                        )
208                        block.overwrite(BitStream(raw_data.length))
209            self.overwrite_mem_from_block(blk, block)
210
211    def clean_mem(self):
212        self.mem.set(0)
213        if self.efuse_file:
214            with open(self.efuse_file, "wb") as f:
215                self.mem.tofile(f)
216
217
218class FatalError(RuntimeError):
219    """
220    Wrapper class for runtime errors that aren't caused by internal bugs
221    """
222
223    def __init__(self, message):
224        RuntimeError.__init__(self, message)
225
226    @staticmethod
227    def WithResult(message, result):
228        return FatalError(result)
229