1# SPDX-FileCopyrightText: 2014-2022 Fredrik Ahlberg, Angus Gratton,
2# Espressif Systems (Shanghai) CO LTD, other contributors as noted.
3#
4# SPDX-License-Identifier: GPL-2.0-or-later
5
6import os
7import re
8import struct
9import sys
10
11
12def byte(bitstr, index):
13    return bitstr[index]
14
15
16def mask_to_shift(mask):
17    """Return the index of the least significant bit in the mask"""
18    shift = 0
19    while mask & 0x1 == 0:
20        shift += 1
21        mask >>= 1
22    return shift
23
24
25def div_roundup(a, b):
26    """Return a/b rounded up to nearest integer,
27    equivalent result to int(math.ceil(float(int(a)) / float(int(b))), only
28    without possible floating point accuracy errors.
29    """
30    return (int(a) + int(b) - 1) // int(b)
31
32
33def flash_size_bytes(size):
34    """Given a flash size of the type passed in args.flash_size
35    (ie 512KB or 1MB) then return the size in bytes.
36    """
37    if size is None:
38        return None
39    if "MB" in size:
40        return int(size[: size.index("MB")]) * 1024 * 1024
41    elif "KB" in size:
42        return int(size[: size.index("KB")]) * 1024
43    else:
44        raise FatalError("Unknown size %s" % size)
45
46
47def hexify(s, uppercase=True):
48    format_str = "%02X" if uppercase else "%02x"
49    return "".join(format_str % c for c in s)
50
51
52def pad_to(data, alignment, pad_character=b"\xFF"):
53    """Pad to the next alignment boundary"""
54    pad_mod = len(data) % alignment
55    if pad_mod != 0:
56        data += pad_character * (alignment - pad_mod)
57    return data
58
59
60def print_overwrite(message, last_line=False):
61    """Print a message, overwriting the currently printed line.
62
63    If last_line is False, don't append a newline at the end
64    (expecting another subsequent call will overwrite this one.)
65
66    After a sequence of calls with last_line=False, call once with last_line=True.
67
68    If output is not a TTY (for example redirected a pipe),
69    no overwriting happens and this function is the same as print().
70    """
71    if hasattr(sys.stdout, "isatty") and sys.stdout.isatty():
72        print("\r%s" % message, end="\n" if last_line else "")
73    else:
74        print(message)
75
76
77def expand_chip_name(chip_name):
78    """Change chip name to official form, e.g. `esp32s3beta2` -> `ESP32-S3(beta2)`"""
79    # Put "-" after "esp32"
80    chip_name = re.sub(r"(esp32)(?!$)", r"\1-", chip_name)
81    # Put "()" around "betaN"
82    chip_name = re.sub(r"(beta\d*)", r"(\1)", chip_name)
83    # Uppercase everything before "(betaN)"
84    chip_name = re.sub(r"^[^\(]+", lambda x: x.group(0).upper(), chip_name)
85    return chip_name
86
87
88def strip_chip_name(chip_name):
89    """Strip chip name to normalized form, e.g. `ESP32-S3(beta2)` -> `esp32s3beta2`"""
90    return re.sub(r"[-()]", "", chip_name.lower())
91
92
93def get_file_size(path_to_file):
94    """Returns the file size in bytes"""
95    file_size = 0
96    with open(path_to_file, "rb") as f:
97        f.seek(0, os.SEEK_END)
98        file_size = f.tell()
99    return file_size
100
101
102class PrintOnce:
103    """
104    Class for printing messages just once. Can be useful when running in a loop
105    """
106
107    def __init__(self) -> None:
108        self.already_printed = False
109
110    def __call__(self, text) -> None:
111        if not self.already_printed:
112            print(text)
113            self.already_printed = True
114
115
116class FatalError(RuntimeError):
117    """
118    Wrapper class for runtime errors that aren't caused by internal bugs, but by
119    ESP ROM responses or input content.
120    """
121
122    def __init__(self, message):
123        RuntimeError.__init__(self, message)
124
125    @staticmethod
126    def WithResult(message, result):
127        """
128        Return a fatal error object that appends the hex values of
129        'result' and its meaning as a string formatted argument.
130        """
131
132        err_defs = {
133            # ROM error codes
134            0x101: "Out of memory",
135            0x102: "Invalid argument",
136            0x103: "Invalid state",
137            0x104: "Invalid size",
138            0x105: "Requested resource not found",
139            0x106: "Operation or feature not supported",
140            0x107: "Operation timed out",
141            0x108: "Received response was invalid",
142            0x109: "CRC or checksum was invalid",
143            0x10A: "Version was invalid",
144            0x10B: "MAC address was invalid",
145            0x6001: "Flash operation failed",
146            0x6002: "Flash operation timed out",
147            0x6003: "Flash not initialised properly",
148            0x6004: "Operation not supported by the host SPI bus",
149            0x6005: "Operation not supported by the flash chip",
150            0x6006: "Can't write, protection enabled",
151            # Flasher stub error codes
152            0xC000: "Bad data length",
153            0xC100: "Bad data checksum",
154            0xC200: "Bad blocksize",
155            0xC300: "Invalid command",
156            0xC400: "Failed SPI operation",
157            0xC500: "Failed SPI unlock",
158            0xC600: "Not in flash mode",
159            0xC700: "Inflate error",
160            0xC800: "Not enough data",
161            0xC900: "Too much data",
162            0xFF00: "Command not implemented",
163        }
164
165        err_code = struct.unpack(">H", result[:2])
166        message += " (result was {}: {})".format(
167            hexify(result), err_defs.get(err_code[0], "Unknown result")
168        )
169        return FatalError(message)
170
171
172class NotImplementedInROMError(FatalError):
173    """
174    Wrapper class for the error thrown when a particular ESP bootloader function
175    is not implemented in the ROM bootloader.
176    """
177
178    def __init__(self, bootloader, func):
179        FatalError.__init__(
180            self,
181            "%s ROM does not support function %s."
182            % (bootloader.CHIP_NAME, func.__name__),
183        )
184
185
186class NotSupportedError(FatalError):
187    def __init__(self, esp, function_name):
188        FatalError.__init__(
189            self,
190            f"{function_name} is not supported by {esp.CHIP_NAME}.",
191        )
192
193
194class UnsupportedCommandError(RuntimeError):
195    """
196    Wrapper class for when ROM loader returns an invalid command response.
197
198    Usually this indicates the loader is running in Secure Download Mode.
199    """
200
201    def __init__(self, esp, op):
202        if esp.secure_download_mode:
203            msg = "This command (0x%x) is not supported in Secure Download Mode" % op
204        else:
205            msg = "Invalid (unsupported) command 0x%x" % op
206        RuntimeError.__init__(self, msg)
207