1# This file describes eFuses fields and registers 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
7from collections import Counter, namedtuple
8import esptool
9from typing import Optional, List
10
11from .csv_table_parser import CSVFuseTable
12
13
14class EfuseRegistersBase(object):
15    # Coding Scheme values
16    CODING_SCHEME_NONE = 0
17    CODING_SCHEME_34 = 1
18    CODING_SCHEME_REPEAT = 2
19    CODING_SCHEME_NONE_RECOVERY = 3
20    CODING_SCHEME_RS = 4
21
22    EFUSE_BURN_TIMEOUT = 0.250  # seconds
23
24
25class EfuseBlocksBase(object):
26    BLOCKS: Optional[List] = None
27    NamedtupleBlock = namedtuple(
28        "NamedtupleBlock",
29        "name alias id rd_addr wr_addr write_disable_bit "
30        "read_disable_bit len key_purpose",
31    )
32
33    @staticmethod
34    def get(tuple_block):
35        return EfuseBlocksBase.NamedtupleBlock._make(tuple_block)
36
37    def get_blocks_for_keys(self):
38        list_of_names = []
39        for block in self.BLOCKS:
40            blk = self.get(block)
41            if blk.id > 0:
42                if blk.name:
43                    list_of_names.append(blk.name)
44                if blk.alias:
45                    for alias in blk.alias:
46                        list_of_names.append(alias)
47        return list_of_names
48
49
50class Field:
51    name = ""
52    block = 0
53    word = None
54    pos = None
55    bit_len = 0
56    alt_names: List[str] = []
57    type = ""
58    write_disable_bit = None
59    read_disable_bit = None
60    category = "config"
61    class_type = ""
62    description = ""
63    dictionary = None
64
65
66class EfuseFieldsBase(object):
67    def __init__(self, e_desc, extend_efuse_table_file) -> None:
68        self.ALL_EFUSES: List = []
69
70        def set_category_and_class_type(efuse, name):
71            def includes(name, names):
72                return any([word in name for word in names])
73
74            if name.startswith("SPI_PAD_CONFIG"):
75                efuse.category = "spi pad"
76
77            elif "USB" in name:
78                efuse.category = "usb"
79
80            elif "WDT" in name:
81                efuse.category = "wdt"
82
83            elif "JTAG" in name:
84                efuse.category = "jtag"
85
86            elif includes(name, ["FLASH", "FORCE_SEND_RESUME"]):
87                efuse.category = "flash"
88
89            elif includes(name, ["VDD_SPI_", "XPD"]):
90                efuse.category = "vdd"
91
92            elif "MAC" in name:
93                efuse.category = "MAC"
94                if name in ["MAC", "CUSTOM_MAC", "MAC_EXT"]:
95                    efuse.class_type = "mac"
96
97            elif includes(
98                name,
99                [
100                    "BLOCK_KEY0",
101                    "BLOCK_KEY1",
102                    "BLOCK_KEY2",
103                    "BLOCK_KEY3",
104                    "BLOCK_KEY4",
105                    "BLOCK_KEY5",
106                    "BLOCK1",
107                    "BLOCK2",
108                ],
109            ):
110                efuse.category = "security"
111                efuse.class_type = "keyblock"
112
113            elif includes(
114                name,
115                [
116                    "KEY",
117                    "SECURE",
118                    "DOWNLOAD",
119                    "SPI_BOOT_CRYPT_CNT",
120                    "KEY_PURPOSE",
121                    "SECURE_VERSION",
122                    "DPA",
123                    "ECDSA",
124                    "FLASH_CRYPT_CNT",
125                    "ENCRYPT",
126                    "DECRYPT",
127                    "ABS_DONE",
128                ],
129            ):
130                efuse.category = "security"
131                if name.startswith("KEY_PURPOSE"):
132                    efuse.class_type = "keypurpose"
133                elif includes(
134                    name, ["FLASH_CRYPT_CNT", "SPI_BOOT_CRYPT_CNT", "SECURE_VERSION"]
135                ):
136                    efuse.class_type = "bitcount"
137
138            elif includes(name, ["VERSION", "WAFER", "_ID", "PKG", "PACKAGE", "REV"]):
139                efuse.category = "identity"
140                if name == "OPTIONAL_UNIQUE_ID":
141                    efuse.class_type = "keyblock"
142
143            elif includes(name, ["ADC", "LDO", "DBIAS", "_HVT", "CALIB", "OCODE"]):
144                efuse.category = "calibration"
145                if name == "ADC_VREF":
146                    efuse.class_type = "vref"
147                    return
148                if includes(name, ["ADC", "LDO", "DBIAS", "_HVT"]):
149                    efuse.class_type = "adc_tp"
150                elif name == "TEMP_CALIB":
151                    efuse.class_type = "t_sensor"
152
153        for e_name in e_desc["EFUSES"]:
154            data_dict = e_desc["EFUSES"][e_name]
155            if data_dict["show"] == "y":
156                d = Field()
157                d.name = e_name
158                d.block = data_dict["blk"]
159                d.word = data_dict["word"]
160                d.pos = data_dict["pos"]
161                d.bit_len = data_dict["len"]
162                d.type = data_dict["type"]
163                d.write_disable_bit = data_dict["wr_dis"]
164                d.read_disable_bit = (
165                    [int(x) for x in data_dict["rd_dis"].split(" ")]
166                    if isinstance(data_dict["rd_dis"], str)
167                    else data_dict["rd_dis"]
168                )
169                d.description = data_dict["desc"]
170                d.alt_names = data_dict["alt"].split(" ") if data_dict["alt"] else []
171                d.dictionary = (
172                    eval(data_dict["dict"]) if data_dict["dict"] != "" else None
173                )
174                set_category_and_class_type(d, e_name)
175                self.ALL_EFUSES.append(d)
176
177        if self.extend_efuses(extend_efuse_table_file):
178            self.check_name_duplicates()
179
180    def check_name_duplicates(self):
181        names = [n.name for n in self.ALL_EFUSES]
182        for n in self.ALL_EFUSES:
183            if n.alt_names:
184                names.extend(n.alt_names)
185
186        name_counts = Counter(names)
187        duplicates = {name for name, count in name_counts.items() if count > 1}
188        if duplicates:
189            print("Names that are not unique: " + ", ".join(duplicates))
190            raise esptool.FatalError("Duplicate names found in eFuses")
191
192    def extend_efuses(self, extend_efuse_table_file):
193        if extend_efuse_table_file:
194            table = CSVFuseTable.from_csv(extend_efuse_table_file.read())
195            for p in table:
196                item = Field()
197                item.name = p.field_name
198                item.block = p.efuse_block
199                item.word = p.bit_start // 32
200                item.pos = p.bit_start % 32
201                item.bit_len = p.bit_count
202                if p.bit_count == 1:
203                    str_type = "bool"
204                else:
205                    if p.bit_count > 32 and p.bit_count % 8 == 0:
206                        str_type = f"bytes:{p.bit_count // 8}"
207                    else:
208                        str_type = f"uint:{p.bit_count}"
209                item.type = str_type
210                item.write_disable_bit = None
211                item.read_disable_bit = None
212                if item.block != 0:
213                    # look for an already configured field associated with this field
214                    # to take the WR_DIS and RID_DIS bits
215                    for field in self.ALL_EFUSES:
216                        if field.block == item.block:
217                            if field.write_disable_bit is not None:
218                                item.write_disable_bit = field.write_disable_bit
219                            if field.read_disable_bit is not None:
220                                item.read_disable_bit = field.read_disable_bit
221                            if (
222                                item.read_disable_bit is not None
223                                and item.write_disable_bit is not None
224                            ):
225                                break
226                item.category = "User"
227                item.description = p.comment
228                item.alt_names = p.alt_names.split(" ") if p.alt_names else []
229                item.dictionary = ""
230                self.ALL_EFUSES.append(item)
231            return True
232        return False
233