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