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