1# This file describes eFuses 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
7import binascii
8import struct
9import time
10
11import esptool
12
13from .mem_definition import EfuseDefineBlocks, EfuseDefineFields, EfuseDefineRegisters
14from .. import base_fields
15from .. import util
16
17
18class EfuseBlock(base_fields.EfuseBlockBase):
19    def len_of_burn_unit(self):
20        # The writing register window is the same as len of a block.
21        return self.len
22
23    def __init__(self, parent, param, skip_read=False):
24        if skip_read:
25            parent.coding_scheme = parent.REGS.CODING_SCHEME_NONE
26        else:
27            if parent.coding_scheme is None:
28                parent.read_coding_scheme()
29        super(EfuseBlock, self).__init__(parent, param, skip_read=skip_read)
30
31    def apply_coding_scheme(self):
32        data = self.get_raw(from_read=False)[::-1]
33        if self.get_coding_scheme() == self.parent.REGS.CODING_SCHEME_34:
34            # CODING_SCHEME 3/4 applied only for BLK1..3
35            # Takes 24 byte sequence to be represented in 3/4 encoding,
36            # returns 8 words suitable for writing "encoded" to an efuse block
37            if len(data) != 24:
38                raise esptool.FatalError("Should take 24 bytes for 3/4 encoding.")
39            data = data[:24]
40            outbits = b""
41            while len(data) > 0:  # process in chunks of 6 bytes
42                bits = data[0:6]
43                data = data[6:]
44                xor_res = 0
45                mul_res = 0
46                index = 1
47                for b in struct.unpack("B" * 6, bits):
48                    xor_res ^= b
49                    mul_res += index * util.popcnt(b)
50                    index += 1
51                outbits += bits
52                outbits += struct.pack("BB", xor_res, mul_res)
53            words = struct.unpack("<" + "I" * (len(outbits) // 4), outbits)
54            # returns 8 words
55        else:
56            # CODING_SCHEME NONE applied for BLK0 and BLK1..3
57            # BLK0 len = 7 words, BLK1..3 len = 8 words.
58            words = struct.unpack("<" + ("I" * (len(data) // 4)), data)
59            # returns 7 words for BLK0 or 8 words for BLK1..3
60        return words
61
62
63class EspEfuses(base_fields.EspEfusesBase):
64    """
65    Wrapper object to manage the efuse fields in a connected ESP bootloader
66    """
67
68    debug = False
69    do_not_confirm = False
70
71    def __init__(
72        self,
73        esp,
74        skip_connect=False,
75        debug=False,
76        do_not_confirm=False,
77        extend_efuse_table=None,
78    ):
79        self.Blocks = EfuseDefineBlocks()
80        self.Fields = EfuseDefineFields(extend_efuse_table)
81        self.REGS = EfuseDefineRegisters
82        self.BURN_BLOCK_DATA_NAMES = self.Blocks.get_burn_block_data_names()
83        self.BLOCKS_FOR_KEYS = self.Blocks.get_blocks_for_keys()
84        self._esp = esp
85        self.debug = debug
86        self.do_not_confirm = do_not_confirm
87        if esp.CHIP_NAME != "ESP32":
88            raise esptool.FatalError(
89                "Expected the 'esp' param for ESP32 chip but got for '%s'."
90                % (esp.CHIP_NAME)
91            )
92        self.blocks = [
93            EfuseBlock(self, self.Blocks.get(block), skip_read=skip_connect)
94            for block in self.Blocks.BLOCKS
95        ]
96        if not skip_connect:
97            self.get_coding_scheme_warnings()
98        self.efuses = [EfuseField.convert(self, efuse) for efuse in self.Fields.EFUSES]
99        if skip_connect:
100            self.efuses += [
101                EfuseField.convert(self, efuse) for efuse in self.Fields.KEYBLOCKS_256
102            ]
103            self.efuses += [
104                EfuseField.convert(self, efuse) for efuse in self.Fields.CUSTOM_MAC
105            ]
106            self.efuses += [
107                EfuseField.convert(self, efuse) for efuse in self.Fields.ADC_CALIBRATION
108            ]
109        else:
110            if self.coding_scheme == self.REGS.CODING_SCHEME_NONE:
111                self.efuses += [
112                    EfuseField.convert(self, efuse)
113                    for efuse in self.Fields.KEYBLOCKS_256
114                ]
115            elif self.coding_scheme == self.REGS.CODING_SCHEME_34:
116                self.efuses += [
117                    EfuseField.convert(self, efuse)
118                    for efuse in self.Fields.KEYBLOCKS_192
119                ]
120            else:
121                raise esptool.FatalError(
122                    "The coding scheme (%d) - is not supported" % self.coding_scheme
123                )
124            if self["MAC_VERSION"].get() == 1:
125                self.efuses += [
126                    EfuseField.convert(self, efuse) for efuse in self.Fields.CUSTOM_MAC
127                ]
128            if self["BLK3_PART_RESERVE"].get():
129                self.efuses += [
130                    EfuseField.convert(self, efuse)
131                    for efuse in self.Fields.ADC_CALIBRATION
132                ]
133            self.efuses += [
134                EfuseField.convert(self, efuse) for efuse in self.Fields.CALC
135            ]
136
137    def __getitem__(self, efuse_name):
138        """Return the efuse field with the given name"""
139        for e in self.efuses:
140            if efuse_name == e.name or any(x == efuse_name for x in e.alt_names):
141                return e
142        new_fields = False
143        for efuse in self.Fields.CUSTOM_MAC:
144            if efuse.name == efuse_name or any(
145                x == efuse_name for x in efuse.alt_names
146            ):
147                self.efuses += [
148                    EfuseField.convert(self, efuse) for efuse in self.Fields.CUSTOM_MAC
149                ]
150                new_fields = True
151        for efuse in self.Fields.ADC_CALIBRATION:
152            if efuse.name == efuse_name or any(
153                x == efuse_name for x in efuse.alt_names
154            ):
155                self.efuses += [
156                    EfuseField.convert(self, efuse)
157                    for efuse in self.Fields.ADC_CALIBRATION
158                ]
159                new_fields = True
160        if new_fields:
161            for e in self.efuses:
162                if efuse_name == e.name or any(x == efuse_name for x in e.alt_names):
163                    return e
164        raise KeyError
165
166    def read_coding_scheme(self):
167        coding_scheme = (
168            self.read_efuse(self.REGS.EFUSE_CODING_SCHEME_WORD)
169            & self.REGS.EFUSE_CODING_SCHEME_MASK
170        )
171        if coding_scheme == self.REGS.CODING_SCHEME_NONE_RECOVERY:
172            self.coding_scheme = self.REGS.CODING_SCHEME_NONE
173        else:
174            self.coding_scheme = coding_scheme
175
176    def print_status_regs(self):
177        print("")
178        print(
179            "{:27} 0x{:08x}".format(
180                "EFUSE_REG_DEC_STATUS", self.read_reg(self.REGS.EFUSE_REG_DEC_STATUS)
181            )
182        )
183
184    def write_efuses(self, block):
185        """Write the values in the efuse write registers to
186        the efuse hardware, then refresh the efuse read registers.
187        """
188
189        # Configure clock
190        apb_freq = self.get_crystal_freq()
191        clk_sel0, clk_sel1, dac_clk_div = self.REGS.EFUSE_CLK_SETTINGS[apb_freq]
192
193        self.update_reg(
194            self.REGS.EFUSE_DAC_CONF_REG, self.REGS.EFUSE_DAC_CLK_DIV_MASK, dac_clk_div
195        )
196        self.update_reg(
197            self.REGS.EFUSE_CLK_REG, self.REGS.EFUSE_CLK_SEL0_MASK, clk_sel0
198        )
199        self.update_reg(
200            self.REGS.EFUSE_CLK_REG, self.REGS.EFUSE_CLK_SEL1_MASK, clk_sel1
201        )
202
203        self.write_reg(self.REGS.EFUSE_REG_CONF, self.REGS.EFUSE_CONF_WRITE)
204        self.write_reg(self.REGS.EFUSE_REG_CMD, self.REGS.EFUSE_CMD_WRITE)
205
206        self.efuse_read()
207        return self.get_coding_scheme_warnings(silent=True)
208
209    def wait_efuse_idle(self):
210        deadline = time.time() + self.REGS.EFUSE_BURN_TIMEOUT
211        while time.time() < deadline:
212            if self.read_reg(self.REGS.EFUSE_REG_CMD) == 0:
213                return
214        raise esptool.FatalError(
215            "Timed out waiting for Efuse controller command to complete"
216        )
217
218    def efuse_read(self):
219        self.wait_efuse_idle()
220        self.write_reg(self.REGS.EFUSE_REG_CONF, self.REGS.EFUSE_CONF_READ)
221        self.write_reg(self.REGS.EFUSE_REG_CMD, self.REGS.EFUSE_CMD_READ)
222        self.wait_efuse_idle()
223
224    def get_coding_scheme_warnings(self, silent=False):
225        """Check if the coding scheme has detected any errors.
226        Meaningless for default coding scheme (0)
227        """
228        err = (
229            self.read_reg(self.REGS.EFUSE_REG_DEC_STATUS)
230            & self.REGS.EFUSE_REG_DEC_STATUS_MASK
231        )
232        for block in self.blocks:
233            if block.id != 0:
234                block.num_errors = 0
235                block.fail = err != 0
236            if not silent and block.fail:
237                print(
238                    "Error(s) in BLOCK%d [ERRORS:%d FAIL:%d]"
239                    % (block.id, block.num_errors, block.fail)
240                )
241        if (self.debug or err) and not silent:
242            self.print_status_regs()
243        return err != 0
244
245    def summary(self):
246        if self["XPD_SDIO_FORCE"].get() == 0:
247            output = "Flash voltage (VDD_SDIO) determined by GPIO12 on reset (High for 1.8V, Low/NC for 3.3V)"
248        elif self["XPD_SDIO_REG"].get() == 0:
249            output = "Flash voltage (VDD_SDIO) internal regulator disabled by efuse."
250        elif self["XPD_SDIO_TIEH"].get() == 0:
251            output = "Flash voltage (VDD_SDIO) set to 1.8V by efuse."
252        else:
253            output = "Flash voltage (VDD_SDIO) set to 3.3V by efuse."
254        return output
255
256
257class EfuseField(base_fields.EfuseFieldBase):
258    @staticmethod
259    def convert(parent, efuse):
260        return {
261            "mac": EfuseMacField,
262            "spipin": EfuseSpiPinField,
263            "vref": EfuseVRefField,
264            "adc_tp": EfuseAdcPointCalibration,
265            "wafer": EfuseWafer,
266            "pkg": EfusePkg,
267        }.get(efuse.class_type, EfuseField)(parent, efuse)
268
269
270class EfuseMacField(EfuseField):
271    """
272    Supports: MAC and CUSTOM_MAC fields.
273    (if MAC_VERSION == 1 then the CUSTOM_MAC is used)
274    """
275
276    def check_format(self, new_value_str):
277        if new_value_str is None:
278            raise esptool.FatalError(
279                "Required MAC Address in AA:CD:EF:01:02:03 format!"
280            )
281        if new_value_str.count(":") != 5:
282            raise esptool.FatalError(
283                "MAC Address needs to be a 6-byte hexadecimal format "
284                "separated by colons (:)!"
285            )
286        hexad = new_value_str.replace(":", "")
287        if len(hexad) != 12:
288            raise esptool.FatalError(
289                "MAC Address needs to be a 6-byte hexadecimal number "
290                "(12 hexadecimal characters)!"
291            )
292        # order of bytearray = b'\xaa\xcd\xef\x01\x02\x03',
293        bindata = binascii.unhexlify(hexad)
294        # unicast address check according to
295        # https://tools.ietf.org/html/rfc7042#section-2.1
296        if esptool.util.byte(bindata, 0) & 0x01:
297            raise esptool.FatalError("Custom MAC must be a unicast MAC!")
298        return bindata
299
300    @staticmethod
301    def get_and_check(raw_mac, stored_crc):
302        computed_crc = EfuseMacField.calc_crc(raw_mac)
303        if computed_crc == stored_crc:
304            valid_msg = "(CRC 0x%02x OK)" % stored_crc
305        else:
306            valid_msg = "(CRC 0x%02x invalid - calculated 0x%02x)" % (
307                stored_crc,
308                computed_crc,
309            )
310        return "%s %s" % (util.hexify(raw_mac, ":"), valid_msg)
311
312    @staticmethod
313    def calc_crc(raw_mac):
314        """
315        This algorithm is the equivalent of esp_crc8() in ESP32 ROM code
316
317        This is CRC-8 w/ inverted polynomial value 0x8C & initial value 0x00.
318        """
319        result = 0x00
320        for b in struct.unpack("B" * 6, raw_mac):
321            result ^= b
322            for _ in range(8):
323                lsb = result & 1
324                result >>= 1
325                if lsb != 0:
326                    result ^= 0x8C
327        return result
328
329    def get(self, from_read=True):
330        if self.name == "CUSTOM_MAC":
331            mac = self.get_raw(from_read)[::-1]
332            stored_crc = self.parent["CUSTOM_MAC_CRC"].get(from_read)
333        else:
334            mac = self.get_raw(from_read)
335            stored_crc = self.parent["MAC_CRC"].get(from_read)
336        return EfuseMacField.get_and_check(mac, stored_crc)
337
338    def save(self, new_value):
339        def print_field(e, new_value):
340            print(
341                "    - '{}' ({}) {} -> {}".format(
342                    e.name, e.description, e.get_bitstring(), new_value
343                )
344            )
345
346        if self.name == "CUSTOM_MAC":
347            # Writing the BLK3:
348            #  - MAC_VERSION = 1
349            #  - CUSTOM_MAC = AB:CD:EF:01:02:03
350            #  - CUSTOM_MAC_CRC = crc8(CUSTOM_MAC)
351            mac_version = self.parent["MAC_VERSION"]
352            if mac_version.get() == 0:
353                mac_version_value = 1
354                print_field(mac_version, hex(mac_version_value))
355                mac_version.save(mac_version_value)
356            else:
357                if mac_version.get() != 1:
358                    if not self.parent.force_write_always:
359                        raise esptool.FatalError(
360                            "MAC_VERSION = {}, should be 0 or 1.".format(
361                                mac_version.get()
362                            )
363                        )
364
365            bitarray_mac = self.convert_to_bitstring(new_value)
366            print_field(self, bitarray_mac)
367            super(EfuseMacField, self).save(new_value)
368
369            crc_val = self.calc_crc(new_value)
370            crc_field = self.parent["CUSTOM_MAC_CRC"]
371            print_field(crc_field, hex(crc_val))
372            crc_field.save(crc_val)
373        else:
374            # Writing the BLK0 default MAC is not possible,
375            # as it's written in the factory.
376            raise esptool.FatalError("Writing Factory MAC address is not supported")
377
378
379class EfuseWafer(EfuseField):
380    def get(self, from_read=True):
381        rev_bit0 = self.parent["CHIP_VER_REV1"].get(from_read)
382        assert self.parent["CHIP_VER_REV1"].bit_len == 1
383        rev_bit1 = self.parent["CHIP_VER_REV2"].get(from_read)
384        assert self.parent["CHIP_VER_REV2"].bit_len == 1
385        apb_ctl_date = self.parent.read_reg(self.parent.REGS.APB_CTL_DATE_ADDR)
386        rev_bit2 = (
387            apb_ctl_date >> self.parent.REGS.APB_CTL_DATE_S
388        ) & self.parent.REGS.APB_CTL_DATE_V
389        combine_value = (rev_bit2 << 2) | (rev_bit1 << 1) | rev_bit0
390
391        revision = {
392            0: 0,
393            1: 1,
394            3: 2,
395            7: 3,
396        }.get(combine_value, 0)
397        return revision
398
399    def save(self, new_value):
400        raise esptool.FatalError("Burning %s is not supported" % self.name)
401
402
403class EfusePkg(EfuseField):
404    def get(self, from_read=True):
405        lo_bits = self.parent["CHIP_PACKAGE"].get(from_read)
406        hi_bits = self.parent["CHIP_PACKAGE_4BIT"].get(from_read)
407        return (hi_bits << 3) + lo_bits
408
409    def save(self, new_value):
410        raise esptool.FatalError("Burning %s is not supported" % self.name)
411
412
413class EfuseSpiPinField(EfuseField):
414    def get(self, from_read=True):
415        val = self.get_raw(from_read)
416        if val >= 30:
417            val += 2  # values 30,31 map to 32, 33
418        return val
419
420    def check_format(self, new_value_str):
421        if new_value_str is None:
422            return new_value_str
423
424        new_value_int = int(new_value_str, 0)
425
426        if new_value_int in [30, 31]:
427            raise esptool.FatalError(
428                "IO pins 30 & 31 cannot be set for SPI flash. 0-29, 32 & 33 only."
429            )
430        elif new_value_int > 33:
431            raise esptool.FatalError(
432                "IO pin %d cannot be set for SPI flash. 0-29, 32 & 33 only."
433                % new_value_int
434            )
435        elif new_value_int in [32, 33]:
436            return str(new_value_int - 2)
437        else:
438            return new_value_str
439
440
441class EfuseVRefField(EfuseField):
442    VREF_OFFSET = 1100  # ideal efuse value in mV
443    VREF_STEP_SIZE = 7  # 1 count in efuse == 7mV
444    VREF_SIGN_BIT = 0x10
445    VREF_MAG_BITS = 0x0F
446
447    def get(self, from_read=True):
448        val = self.get_raw(from_read)
449        # sign-magnitude format
450        if val & self.VREF_SIGN_BIT:
451            val = -(val & self.VREF_MAG_BITS)
452        else:
453            val = val & self.VREF_MAG_BITS
454        val *= self.VREF_STEP_SIZE
455        return self.VREF_OFFSET + val
456
457    def save(self, new_value):
458        raise esptool.FatalError("Writing to VRef is not supported.")
459
460
461class EfuseAdcPointCalibration(EfuseField):
462    TP_OFFSET = {  # See TP_xxxx_OFFSET in esp_adc_cal.c in ESP-IDF
463        "ADC1_TP_LOW": 278,
464        "ADC2_TP_LOW": 421,
465        "ADC1_TP_HIGH": 3265,
466        "ADC2_TP_HIGH": 3406,
467    }
468    SIGN_BIT = (0x40, 0x100)  # LOW, HIGH (2s complement format)
469    STEP_SIZE = 4
470
471    def get(self, from_read=True):
472        idx = 0 if self.name.endswith("LOW") else 1
473        sign_bit = self.SIGN_BIT[idx]
474        offset = self.TP_OFFSET[self.name]
475        raw = self.get_raw()
476        delta = (raw & (sign_bit - 1)) - (raw & sign_bit)
477        return offset + (delta * self.STEP_SIZE)
478