1# This file describes eFuses for ESP32-C2 chip
2#
3# SPDX-FileCopyrightText: 2021-2022 Espressif Systems (Shanghai) CO LTD
4#
5# SPDX-License-Identifier: GPL-2.0-or-later
6
7import binascii
8import struct
9import time
10
11from bitstring import BitArray
12
13import esptool
14
15import reedsolo
16
17from .mem_definition import EfuseDefineBlocks, EfuseDefineFields, EfuseDefineRegisters
18from .. import base_fields
19from .. import util
20
21
22class EfuseBlock(base_fields.EfuseBlockBase):
23    def len_of_burn_unit(self):
24        # The writing register window is 8 registers for any blocks.
25        # len in bytes
26        return 8 * 4
27
28    def __init__(self, parent, param, skip_read=False):
29        parent.read_coding_scheme()
30        super(EfuseBlock, self).__init__(parent, param, skip_read=skip_read)
31
32    def apply_coding_scheme(self):
33        data = self.get_raw(from_read=False)[::-1]
34        if len(data) < self.len_of_burn_unit():
35            add_empty_bytes = self.len_of_burn_unit() - len(data)
36            data = data + (b"\x00" * add_empty_bytes)
37        if self.get_coding_scheme() == self.parent.REGS.CODING_SCHEME_RS:
38            # takes 32 bytes
39            # apply RS encoding
40            rs = reedsolo.RSCodec(12)
41            # 32 byte of data + 12 bytes RS
42            encoded_data = rs.encode([x for x in data])
43            words = struct.unpack("<" + "I" * 11, encoded_data)
44            # returns 11 words (8 words of data + 3 words of RS coding)
45        else:
46            # takes 32 bytes
47            words = struct.unpack("<" + ("I" * (len(data) // 4)), data)
48            # returns 8 words
49        return words
50
51
52class EspEfuses(base_fields.EspEfusesBase):
53    """
54    Wrapper object to manage the efuse fields in a connected ESP bootloader
55    """
56
57    Blocks = EfuseDefineBlocks()
58    Fields = EfuseDefineFields()
59    REGS = EfuseDefineRegisters
60    BURN_BLOCK_DATA_NAMES = Blocks.get_burn_block_data_names()
61    BLOCKS_FOR_KEYS = Blocks.get_blocks_for_keys()
62
63    debug = False
64    do_not_confirm = False
65
66    def __init__(self, esp, skip_connect=False, debug=False, do_not_confirm=False):
67        self._esp = esp
68        self.debug = debug
69        self.do_not_confirm = do_not_confirm
70        if esp.CHIP_NAME != "ESP32-C2":
71            raise esptool.FatalError(
72                "Expected the 'esp' param for ESP32-C2 chip but got for '%s'."
73                % (esp.CHIP_NAME)
74            )
75        if not skip_connect:
76            flags = self._esp.get_security_info()["flags"]
77            GET_SECURITY_INFO_FLAG_SECURE_DOWNLOAD_ENABLE = 1 << 2
78            if flags & GET_SECURITY_INFO_FLAG_SECURE_DOWNLOAD_ENABLE:
79                raise esptool.FatalError(
80                    "Secure Download Mode is enabled. The tool can not read eFuses."
81                )
82        self.blocks = [
83            EfuseBlock(self, self.Blocks.get(block), skip_read=skip_connect)
84            for block in self.Blocks.BLOCKS
85        ]
86        if not skip_connect:
87            self.get_coding_scheme_warnings()
88        self.efuses = [
89            EfuseField.from_tuple(
90                self, self.Fields.get(efuse), self.Fields.get(efuse).class_type
91            )
92            for efuse in self.Fields.EFUSES
93        ]
94        self.efuses += [
95            EfuseField.from_tuple(
96                self, self.Fields.get(efuse), self.Fields.get(efuse).class_type
97            )
98            for efuse in self.Fields.KEYBLOCKS
99        ]
100        if skip_connect:
101            self.efuses += [
102                EfuseField.from_tuple(
103                    self, self.Fields.get(efuse), self.Fields.get(efuse).class_type
104                )
105                for efuse in self.Fields.BLOCK2_CALIBRATION_EFUSES
106            ]
107        else:
108            if self["BLK_VERSION_MINOR"].get() == 1:
109                self.efuses += [
110                    EfuseField.from_tuple(
111                        self, self.Fields.get(efuse), self.Fields.get(efuse).class_type
112                    )
113                    for efuse in self.Fields.BLOCK2_CALIBRATION_EFUSES
114                ]
115
116    def __getitem__(self, efuse_name):
117        """Return the efuse field with the given name"""
118        for e in self.efuses:
119            if efuse_name == e.name:
120                return e
121        new_fields = False
122        for efuse in self.Fields.BLOCK2_CALIBRATION_EFUSES:
123            e = self.Fields.get(efuse)
124            if e.name == efuse_name:
125                self.efuses += [
126                    EfuseField.from_tuple(
127                        self, self.Fields.get(efuse), self.Fields.get(efuse).class_type
128                    )
129                    for efuse in self.Fields.BLOCK2_CALIBRATION_EFUSES
130                ]
131                new_fields = True
132        if new_fields:
133            for e in self.efuses:
134                if efuse_name == e.name:
135                    return e
136        raise KeyError
137
138    def read_coding_scheme(self):
139        self.coding_scheme = self.REGS.CODING_SCHEME_RS
140
141    def print_status_regs(self):
142        print("")
143        self.blocks[0].print_block(self.blocks[0].err_bitarray, "err__regs", debug=True)
144        print(
145            "{:27} 0x{:08x}".format(
146                "EFUSE_RD_RS_ERR_REG", self.read_reg(self.REGS.EFUSE_RD_RS_ERR_REG)
147            )
148        )
149
150    def get_block_errors(self, block_num):
151        """Returns (error count, failure boolean flag)"""
152        return self.blocks[block_num].num_errors, self.blocks[block_num].fail
153
154    def efuse_controller_setup(self):
155        self.set_efuse_timing()
156        self.clear_pgm_registers()
157        self.wait_efuse_idle()
158
159    def write_efuses(self, block):
160        self.efuse_program(block)
161        return self.get_coding_scheme_warnings(silent=True)
162
163    def clear_pgm_registers(self):
164        self.wait_efuse_idle()
165        for r in range(
166            self.REGS.EFUSE_PGM_DATA0_REG, self.REGS.EFUSE_PGM_DATA0_REG + 32, 4
167        ):
168            self.write_reg(r, 0)
169
170    def wait_efuse_idle(self):
171        deadline = time.time() + self.REGS.EFUSE_BURN_TIMEOUT
172        while time.time() < deadline:
173            # if self.read_reg(self.REGS.EFUSE_CMD_REG) == 0:
174            if self.read_reg(self.REGS.EFUSE_STATUS_REG) & 0x7 == 1:
175                return
176        raise esptool.FatalError(
177            "Timed out waiting for Efuse controller command to complete"
178        )
179
180    def efuse_program(self, block):
181        self.wait_efuse_idle()
182        self.write_reg(self.REGS.EFUSE_CONF_REG, self.REGS.EFUSE_WRITE_OP_CODE)
183        self.write_reg(self.REGS.EFUSE_CMD_REG, self.REGS.EFUSE_PGM_CMD | (block << 2))
184        self.wait_efuse_idle()
185        self.clear_pgm_registers()
186        self.efuse_read()
187
188    def efuse_read(self):
189        self.wait_efuse_idle()
190        self.write_reg(self.REGS.EFUSE_CONF_REG, self.REGS.EFUSE_READ_OP_CODE)
191        # need to add a delay after triggering EFUSE_READ_CMD, as ROM loader checks some
192        # efuse registers after each command is completed
193        # if ENABLE_SECURITY_DOWNLOAD or DIS_DOWNLOAD_MODE is enabled by the current cmd, then we need to try to reconnect to the chip.
194        try:
195            self.write_reg(
196                self.REGS.EFUSE_CMD_REG, self.REGS.EFUSE_READ_CMD, delay_after_us=1000
197            )
198            self.wait_efuse_idle()
199        except esptool.FatalError:
200            secure_download_mode_before = self._esp.secure_download_mode
201
202            try:
203                self._esp = self.reconnect_chip(self._esp)
204            except esptool.FatalError:
205                print("Can not re-connect to the chip")
206                if not self["DIS_DOWNLOAD_MODE"].get() and self[
207                    "DIS_DOWNLOAD_MODE"
208                ].get(from_read=False):
209                    print(
210                        "This is the correct behavior as we are actually burning "
211                        "DIS_DOWNLOAD_MODE which disables the connection to the chip"
212                    )
213                    print("DIS_DOWNLOAD_MODE is enabled")
214                    print("Successful")
215                    exit(0)  # finish without errors
216                raise
217
218            print("Established a connection with the chip")
219            if self._esp.secure_download_mode and not secure_download_mode_before:
220                print("Secure download mode is enabled")
221                if not self["ENABLE_SECURITY_DOWNLOAD"].get() and self[
222                    "ENABLE_SECURITY_DOWNLOAD"
223                ].get(from_read=False):
224                    print(
225                        "espefuse tool can not continue to work in Secure download mode"
226                    )
227                    print("ENABLE_SECURITY_DOWNLOAD is enabled")
228                    print("Successful")
229                    exit(0)  # finish without errors
230            raise
231
232    def set_efuse_timing(self):
233        """Set timing registers for burning efuses"""
234        # Configure clock
235        xtal_freq = self.get_crystal_freq()
236        if xtal_freq not in [26, 40]:
237            raise esptool.FatalError(
238                "The eFuse supports only xtal=26M and 40M (xtal was %d)" % xtal_freq
239            )
240
241        self.update_reg(
242            self.REGS.EFUSE_WR_TIM_CONF2_REG, self.REGS.EFUSE_PWR_OFF_NUM_M, 0x190
243        )
244
245        tpgm_inactive_val = 200 if xtal_freq == 40 else 130
246        self.update_reg(
247            self.REGS.EFUSE_WR_TIM_CONF0_REG,
248            self.REGS.EFUSE_TPGM_INACTIVE_M,
249            tpgm_inactive_val,
250        )
251
252    def get_coding_scheme_warnings(self, silent=False):
253        """Check if the coding scheme has detected any errors."""
254        old_addr_reg = 0
255        reg_value = 0
256        ret_fail = False
257        for block in self.blocks:
258            if block.id == 0:
259                words = [
260                    self.read_reg(self.REGS.EFUSE_RD_REPEAT_ERR_REG + offs * 4)
261                    for offs in range(1)
262                ]
263                data = BitArray()
264                for word in reversed(words):
265                    data.append("uint:32=%d" % word)
266                # pos=32 because EFUSE_WR_DIS goes first it is 32bit long
267                # and not under error control
268                block.err_bitarray.overwrite(data, pos=32)
269                block.num_errors = block.err_bitarray.count(True)
270                block.fail = block.num_errors != 0
271            else:
272                addr_reg, err_num_mask, err_num_offs, fail_bit = self.REGS.BLOCK_ERRORS[
273                    block.id
274                ]
275                if err_num_mask is None or err_num_offs is None or fail_bit is None:
276                    continue
277                if addr_reg != old_addr_reg:
278                    old_addr_reg = addr_reg
279                    reg_value = self.read_reg(addr_reg)
280                block.fail = reg_value & (1 << fail_bit) != 0
281                block.num_errors = (reg_value >> err_num_offs) & err_num_mask
282            ret_fail |= block.fail
283            if not silent and (block.fail or block.num_errors):
284                print(
285                    "Error(s) in BLOCK%d [ERRORS:%d FAIL:%d]"
286                    % (block.id, block.num_errors, block.fail)
287                )
288        if (self.debug or ret_fail) and not silent:
289            self.print_status_regs()
290        return ret_fail
291
292    def summary(self):
293        # TODO add support set_flash_voltage - "Flash voltage (VDD_SPI)"
294        return ""
295
296
297class EfuseField(base_fields.EfuseFieldBase):
298    @staticmethod
299    def from_tuple(parent, efuse_tuple, type_class):
300        return {
301            "mac": EfuseMacField,
302            "keypurpose": EfuseKeyPurposeField,
303            "t_sensor": EfuseTempSensor,
304            "adc_tp": EfuseAdcPointCalibration,
305        }.get(type_class, EfuseField)(parent, efuse_tuple)
306
307    def get_info(self):
308        output = "%s (BLOCK%d)" % (self.name, self.block)
309        errs, fail = self.parent.get_block_errors(self.block)
310        if errs != 0 or fail:
311            output += (
312                "[FAIL:%d]" % (fail)
313                if self.block == 0
314                else "[ERRS:%d FAIL:%d]" % (errs, fail)
315            )
316        if self.efuse_class == "keyblock":
317            name = self.parent.blocks[self.block].key_purpose_name
318            if name is not None:
319                output += "\n  Purpose: %s\n " % (self.parent[name].get())
320        return output
321
322
323class EfuseTempSensor(EfuseField):
324    def get(self, from_read=True):
325        value = self.get_bitstring(from_read)
326        sig = -1 if value[0] else 1
327        return sig * value[1:].uint * 0.1
328
329
330class EfuseAdcPointCalibration(EfuseField):
331    def get(self, from_read=True):
332        STEP_SIZE = 4
333        value = self.get_bitstring(from_read)
334        sig = -1 if value[0] else 1
335        return sig * value[1:].uint * STEP_SIZE
336
337
338class EfuseMacField(EfuseField):
339    def check_format(self, new_value_str):
340        if new_value_str is None:
341            raise esptool.FatalError(
342                "Required MAC Address in AA:CD:EF:01:02:03 format!"
343            )
344        if new_value_str.count(":") != 5:
345            raise esptool.FatalError(
346                "MAC Address needs to be a 6-byte hexadecimal format "
347                "separated by colons (:)!"
348            )
349        hexad = new_value_str.replace(":", "")
350        if len(hexad) != 12:
351            raise esptool.FatalError(
352                "MAC Address needs to be a 6-byte hexadecimal number "
353                "(12 hexadecimal characters)!"
354            )
355        # order of bytearray = b'\xaa\xcd\xef\x01\x02\x03',
356        bindata = binascii.unhexlify(hexad)
357        # unicast address check according to
358        # https://tools.ietf.org/html/rfc7042#section-2.1
359        if esptool.util.byte(bindata, 0) & 0x01:
360            raise esptool.FatalError("Custom MAC must be a unicast MAC!")
361        return bindata
362
363    def check(self):
364        errs, fail = self.parent.get_block_errors(self.block)
365        if errs != 0 or fail:
366            output = "Block%d has ERRORS:%d FAIL:%d" % (self.block, errs, fail)
367        else:
368            output = "OK"
369        return "(" + output + ")"
370
371    def get(self, from_read=True):
372        if self.name == "CUSTOM_MAC":
373            mac = self.get_raw(from_read)[::-1]
374        else:
375            mac = self.get_raw(from_read)
376        return "%s %s" % (util.hexify(mac, ":"), self.check())
377
378    def save(self, new_value):
379        def print_field(e, new_value):
380            print(
381                "    - '{}' ({}) {} -> {}".format(
382                    e.name, e.description, e.get_bitstring(), new_value
383                )
384            )
385
386        if self.name == "CUSTOM_MAC":
387            bitarray_mac = self.convert_to_bitstring(new_value)
388            print_field(self, bitarray_mac)
389            super(EfuseMacField, self).save(new_value)
390        else:
391            raise esptool.FatalError("Writing Factory MAC address is not supported")
392
393
394class EfuseKeyPurposeField(EfuseField):
395    KEY_PURPOSES = [
396        ("USER", 0, None),  # User purposes (software-only use)
397        (
398            "XTS_AES_128_KEY",
399            1,
400            None,
401        ),  # (whole 256bits) XTS_AES_128_KEY (flash/PSRAM encryption)
402        (
403            "XTS_AES_128_KEY_DERIVED_FROM_128_EFUSE_BITS",
404            2,
405            None,
406        ),  # (lo 128bits) XTS_AES_128_KEY (flash/PSRAM encryption)
407        (
408            "SECURE_BOOT_DIGEST",
409            3,
410            "DIGEST",
411        ),  # (hi 128bits)SECURE_BOOT_DIGEST (Secure Boot key digest)
412    ]
413
414    KEY_PURPOSES_NAME = [name[0] for name in KEY_PURPOSES]
415    DIGEST_KEY_PURPOSES = [name[0] for name in KEY_PURPOSES if name[2] == "DIGEST"]
416