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