1# This file describes eFuses for ESP32-C3 chip
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 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-C3":
71            raise esptool.FatalError(
72                "Expected the 'esp' param for ESP32-C3 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_MAJOR"].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            self.efuses += [
116                EfuseField.from_tuple(
117                    self, self.Fields.get(efuse), self.Fields.get(efuse).class_type
118                )
119                for efuse in self.Fields.CALC
120            ]
121
122    def __getitem__(self, efuse_name):
123        """Return the efuse field with the given name"""
124        for e in self.efuses:
125            if efuse_name == e.name:
126                return e
127        new_fields = False
128        for efuse in self.Fields.BLOCK2_CALIBRATION_EFUSES:
129            e = self.Fields.get(efuse)
130            if e.name == efuse_name:
131                self.efuses += [
132                    EfuseField.from_tuple(
133                        self, self.Fields.get(efuse), self.Fields.get(efuse).class_type
134                    )
135                    for efuse in self.Fields.BLOCK2_CALIBRATION_EFUSES
136                ]
137                new_fields = True
138        if new_fields:
139            for e in self.efuses:
140                if efuse_name == e.name:
141                    return e
142        raise KeyError
143
144    def read_coding_scheme(self):
145        self.coding_scheme = self.REGS.CODING_SCHEME_RS
146
147    def print_status_regs(self):
148        print("")
149        self.blocks[0].print_block(self.blocks[0].err_bitarray, "err__regs", debug=True)
150        print(
151            "{:27} 0x{:08x}".format(
152                "EFUSE_RD_RS_ERR0_REG", self.read_reg(self.REGS.EFUSE_RD_RS_ERR0_REG)
153            )
154        )
155        print(
156            "{:27} 0x{:08x}".format(
157                "EFUSE_RD_RS_ERR1_REG", self.read_reg(self.REGS.EFUSE_RD_RS_ERR1_REG)
158            )
159        )
160
161    def get_block_errors(self, block_num):
162        """Returns (error count, failure boolean flag)"""
163        return self.blocks[block_num].num_errors, self.blocks[block_num].fail
164
165    def efuse_controller_setup(self):
166        self.set_efuse_timing()
167        self.clear_pgm_registers()
168        self.wait_efuse_idle()
169
170    def write_efuses(self, block):
171        self.efuse_program(block)
172        return self.get_coding_scheme_warnings(silent=True)
173
174    def clear_pgm_registers(self):
175        self.wait_efuse_idle()
176        for r in range(
177            self.REGS.EFUSE_PGM_DATA0_REG, self.REGS.EFUSE_PGM_DATA0_REG + 32, 4
178        ):
179            self.write_reg(r, 0)
180
181    def wait_efuse_idle(self):
182        deadline = time.time() + self.REGS.EFUSE_BURN_TIMEOUT
183        while time.time() < deadline:
184            # if self.read_reg(self.REGS.EFUSE_CMD_REG) == 0:
185            if self.read_reg(self.REGS.EFUSE_STATUS_REG) & 0x7 == 1:
186                return
187        raise esptool.FatalError(
188            "Timed out waiting for Efuse controller command to complete"
189        )
190
191    def efuse_program(self, block):
192        self.wait_efuse_idle()
193        self.write_reg(self.REGS.EFUSE_CONF_REG, self.REGS.EFUSE_WRITE_OP_CODE)
194        self.write_reg(self.REGS.EFUSE_CMD_REG, self.REGS.EFUSE_PGM_CMD | (block << 2))
195        self.wait_efuse_idle()
196        self.clear_pgm_registers()
197        self.efuse_read()
198
199    def efuse_read(self):
200        self.wait_efuse_idle()
201        self.write_reg(self.REGS.EFUSE_CONF_REG, self.REGS.EFUSE_READ_OP_CODE)
202        # need to add a delay after triggering EFUSE_READ_CMD, as ROM loader checks some
203        # efuse registers after each command is completed
204        # if ENABLE_SECURITY_DOWNLOAD or DIS_DOWNLOAD_MODE is enabled by the current cmd, then we need to try to reconnect to the chip.
205        try:
206            self.write_reg(
207                self.REGS.EFUSE_CMD_REG, self.REGS.EFUSE_READ_CMD, delay_after_us=1000
208            )
209            self.wait_efuse_idle()
210        except esptool.FatalError:
211            secure_download_mode_before = self._esp.secure_download_mode
212
213            try:
214                self._esp = self.reconnect_chip(self._esp)
215            except esptool.FatalError:
216                print("Can not re-connect to the chip")
217                if not self["DIS_DOWNLOAD_MODE"].get() and self[
218                    "DIS_DOWNLOAD_MODE"
219                ].get(from_read=False):
220                    print(
221                        "This is the correct behavior as we are actually burning "
222                        "DIS_DOWNLOAD_MODE which disables the connection to the chip"
223                    )
224                    print("DIS_DOWNLOAD_MODE is enabled")
225                    print("Successful")
226                    exit(0)  # finish without errors
227                raise
228
229            print("Established a connection with the chip")
230            if self._esp.secure_download_mode and not secure_download_mode_before:
231                print("Secure download mode is enabled")
232                if not self["ENABLE_SECURITY_DOWNLOAD"].get() and self[
233                    "ENABLE_SECURITY_DOWNLOAD"
234                ].get(from_read=False):
235                    print(
236                        "espefuse tool can not continue to work in Secure download mode"
237                    )
238                    print("ENABLE_SECURITY_DOWNLOAD is enabled")
239                    print("Successful")
240                    exit(0)  # finish without errors
241            raise
242
243    def set_efuse_timing(self):
244        """Set timing registers for burning efuses"""
245        # Configure clock
246        apb_freq = self.get_crystal_freq()
247        if apb_freq != 40:
248            raise esptool.FatalError(
249                "The eFuse supports only xtal=40M (xtal was %d)" % apb_freq
250            )
251
252        self.update_reg(
253            self.REGS.EFUSE_WR_TIM_CONF2_REG, self.REGS.EFUSE_PWR_OFF_NUM_M, 0x190
254        )
255
256    def get_coding_scheme_warnings(self, silent=False):
257        """Check if the coding scheme has detected any errors."""
258        ret_fail = False
259        for block in self.blocks:
260            if block.id == 0:
261                words = [
262                    self.read_reg(self.REGS.EFUSE_RD_REPEAT_ERR0_REG + offs * 4)
263                    for offs in range(5)
264                ]
265                data = BitArray()
266                for word in reversed(words):
267                    data.append("uint:32=%d" % word)
268                # pos=32 because EFUSE_WR_DIS goes first it is 32bit long
269                # and not under error control
270                block.err_bitarray.overwrite(data, pos=32)
271                block.num_errors = block.err_bitarray.count(True)
272                block.fail = block.num_errors != 0
273            else:
274                addr_reg_f, fail_bit = self.REGS.BLOCK_FAIL_BIT[block.id]
275                if fail_bit is None:
276                    block.fail = False
277                else:
278                    block.fail = self.read_reg(addr_reg_f) & (1 << fail_bit) != 0
279
280                addr_reg_n, num_mask, num_offs = self.REGS.BLOCK_NUM_ERRORS[block.id]
281                if num_mask is None or num_offs is None:
282                    block.num_errors = 0
283                else:
284                    block.num_errors = (
285                        self.read_reg(addr_reg_n) >> num_offs
286                    ) & num_mask
287
288            ret_fail |= block.fail
289            if not silent and (block.fail or block.num_errors):
290                print(
291                    "Error(s) in BLOCK%d [ERRORS:%d FAIL:%d]"
292                    % (block.id, block.num_errors, block.fail)
293                )
294        if (self.debug or ret_fail) and not silent:
295            self.print_status_regs()
296        return ret_fail
297
298    def summary(self):
299        # TODO add support set_flash_voltage - "Flash voltage (VDD_SPI)"
300        return ""
301
302
303class EfuseField(base_fields.EfuseFieldBase):
304    @staticmethod
305    def from_tuple(parent, efuse_tuple, type_class):
306        return {
307            "mac": EfuseMacField,
308            "keypurpose": EfuseKeyPurposeField,
309            "t_sensor": EfuseTempSensor,
310            "adc_tp": EfuseAdcPointCalibration,
311            "wafer": EfuseWafer,
312        }.get(type_class, EfuseField)(parent, efuse_tuple)
313
314    def get_info(self):
315        output = "%s (BLOCK%d)" % (self.name, self.block)
316        errs, fail = self.parent.get_block_errors(self.block)
317        if errs != 0 or fail:
318            output += (
319                "[FAIL:%d]" % (fail)
320                if self.block == 0
321                else "[ERRS:%d FAIL:%d]" % (errs, fail)
322            )
323        if self.efuse_class == "keyblock":
324            name = self.parent.blocks[self.block].key_purpose_name
325            if name is not None:
326                output += "\n  Purpose: %s\n " % (self.parent[name].get())
327        return output
328
329
330class EfuseWafer(EfuseField):
331    def get(self, from_read=True):
332        hi_bits = self.parent["WAFER_VERSION_MINOR_HI"].get(from_read)
333        lo_bits = self.parent["WAFER_VERSION_MINOR_LO"].get(from_read)
334        return (hi_bits << 3) + lo_bits
335
336    def save(self, new_value):
337        raise esptool.FatalError("Burning %s is not supported" % self.name)
338
339
340class EfuseTempSensor(EfuseField):
341    def get(self, from_read=True):
342        value = self.get_bitstring(from_read)
343        sig = -1 if value[0] else 1
344        return sig * value[1:].uint * 0.1
345
346
347class EfuseAdcPointCalibration(EfuseField):
348    def get(self, from_read=True):
349        STEP_SIZE = 4
350        value = self.get_bitstring(from_read)
351        sig = -1 if value[0] else 1
352        return sig * value[1:].uint * STEP_SIZE
353
354
355class EfuseMacField(EfuseField):
356    def check_format(self, new_value_str):
357        if new_value_str is None:
358            raise esptool.FatalError(
359                "Required MAC Address in AA:CD:EF:01:02:03 format!"
360            )
361        if new_value_str.count(":") != 5:
362            raise esptool.FatalError(
363                "MAC Address needs to be a 6-byte hexadecimal format "
364                "separated by colons (:)!"
365            )
366        hexad = new_value_str.replace(":", "")
367        if len(hexad) != 12:
368            raise esptool.FatalError(
369                "MAC Address needs to be a 6-byte hexadecimal number "
370                "(12 hexadecimal characters)!"
371            )
372        # order of bytearray = b'\xaa\xcd\xef\x01\x02\x03',
373        bindata = binascii.unhexlify(hexad)
374        # unicast address check according to
375        # https://tools.ietf.org/html/rfc7042#section-2.1
376        if esptool.util.byte(bindata, 0) & 0x01:
377            raise esptool.FatalError("Custom MAC must be a unicast MAC!")
378        return bindata
379
380    def check(self):
381        errs, fail = self.parent.get_block_errors(self.block)
382        if errs != 0 or fail:
383            output = "Block%d has ERRORS:%d FAIL:%d" % (self.block, errs, fail)
384        else:
385            output = "OK"
386        return "(" + output + ")"
387
388    def get(self, from_read=True):
389        if self.name == "CUSTOM_MAC":
390            mac = self.get_raw(from_read)[::-1]
391        else:
392            mac = self.get_raw(from_read)
393        return "%s %s" % (util.hexify(mac, ":"), self.check())
394
395    def save(self, new_value):
396        def print_field(e, new_value):
397            print(
398                "    - '{}' ({}) {} -> {}".format(
399                    e.name, e.description, e.get_bitstring(), new_value
400                )
401            )
402
403        if self.name == "CUSTOM_MAC":
404            bitarray_mac = self.convert_to_bitstring(new_value)
405            print_field(self, bitarray_mac)
406            super(EfuseMacField, self).save(new_value)
407        else:
408            # Writing the BLOCK1 (MAC_SPI_8M_0) default MAC is not possible,
409            # as it's written in the factory.
410            raise esptool.FatalError("Writing Factory MAC address is not supported")
411
412
413# fmt: off
414class EfuseKeyPurposeField(EfuseField):
415    KEY_PURPOSES = [
416        ("USER",                         0,  None,       None,      "no_need_rd_protect"),   # User purposes (software-only use)
417        ("RESERVED",                     1,  None,       None,      "no_need_rd_protect"),   # Reserved
418        ("XTS_AES_128_KEY",              4,  None,       "Reverse", "need_rd_protect"),      # XTS_AES_128_KEY (flash/PSRAM encryption)
419        ("HMAC_DOWN_ALL",                5,  None,       None,      "need_rd_protect"),      # HMAC Downstream mode
420        ("HMAC_DOWN_JTAG",               6,  None,       None,      "need_rd_protect"),      # JTAG soft enable key (uses HMAC Downstream mode)
421        ("HMAC_DOWN_DIGITAL_SIGNATURE",  7,  None,       None,      "need_rd_protect"),      # Digital Signature peripheral key (uses HMAC Downstream mode)
422        ("HMAC_UP",                      8,  None,       None,      "need_rd_protect"),      # HMAC Upstream mode
423        ("SECURE_BOOT_DIGEST0",          9,  "DIGEST",   None,      "no_need_rd_protect"),   # SECURE_BOOT_DIGEST0 (Secure Boot key digest)
424        ("SECURE_BOOT_DIGEST1",          10, "DIGEST",   None,      "no_need_rd_protect"),   # SECURE_BOOT_DIGEST1 (Secure Boot key digest)
425        ("SECURE_BOOT_DIGEST2",          11, "DIGEST",   None,      "no_need_rd_protect"),   # SECURE_BOOT_DIGEST2 (Secure Boot key digest)
426    ]
427# fmt: on
428    KEY_PURPOSES_NAME = [name[0] for name in KEY_PURPOSES]
429    DIGEST_KEY_PURPOSES = [name[0] for name in KEY_PURPOSES if name[2] == "DIGEST"]
430
431    def check_format(self, new_value_str):
432        # str convert to int: "XTS_AES_128_KEY" - > str(4)
433        # if int: 4 -> str(4)
434        raw_val = new_value_str
435        for purpose_name in self.KEY_PURPOSES:
436            if purpose_name[0] == new_value_str:
437                raw_val = str(purpose_name[1])
438                break
439        if raw_val.isdigit():
440            if int(raw_val) not in [p[1] for p in self.KEY_PURPOSES if p[1] > 0]:
441                raise esptool.FatalError("'%s' can not be set (value out of range)" % raw_val)
442        else:
443            raise esptool.FatalError("'%s' unknown name" % raw_val)
444        return raw_val
445
446    def need_reverse(self, new_key_purpose):
447        for key in self.KEY_PURPOSES:
448            if key[0] == new_key_purpose:
449                return key[3] == "Reverse"
450
451    def need_rd_protect(self, new_key_purpose):
452        for key in self.KEY_PURPOSES:
453            if key[0] == new_key_purpose:
454                return key[4] == "need_rd_protect"
455
456    def get(self, from_read=True):
457        for p in self.KEY_PURPOSES:
458            if p[1] == self.get_raw(from_read):
459                return p[0]
460        return "FORBIDDEN_STATE"
461
462    def save(self, new_value):
463        raw_val = int(self.check_format(str(new_value)))
464        return super(EfuseKeyPurposeField, self).save(raw_val)
465