diff --git a/Main.py b/Main.py index b4dd2b2..41b9f92 100644 --- a/Main.py +++ b/Main.py @@ -74,47 +74,25 @@ class FlashingThread(threading.Thread): def run(self): try: - print("esptool.py v%s" % esptool.__version__) - initial_baud = min(ESPLoader.ESP_ROM_BAUD, self._config.baud) + command = "" - if self._config.port.startswith(__auto_select__): - esp = self.connect_to_esp(initial_baud) - else: - esp = ESPLoader.detect_chip(self._config.port, initial_baud) + if not self._config.port.startswith(__auto_select__): + command += "--port %s " % self._config.port - print("Chip is %s" % (esp.get_chip_description())) - print("Features: %s" % ", ".join(esp.get_chip_features())) - esptool.read_mac(esp, Namespace()) - - esp = esp.run_stub() - - if self._config.baud > initial_baud: - try: - esp.change_baud(self._config.baud) - except NotImplementedInROMError: - print("WARNING: ROM doesn't support changing baud rate. Keeping initial baud rate %d." % - initial_baud) - - args = Namespace() - args.flash_size = "detect" - args.flash_mode = self._config.mode - args.flash_freq = "40m" - args.no_progress = False - args.no_stub = False - args.verify = False # TRUE is deprecated - args.compress = True - args.addr_filename = [[int("0x00000", 0), open(self._config.firmware_path, 'rb')]] - - print("Configuring flash size...") - esptool.detect_flash_size(esp, args) - esp.flash_set_parameters(esptool.flash_size_bytes(args.flash_size)) + command += "--baud %s --after no_reset write_flash --flash_mode %s 0x00000 %s" % \ + (self._config.baud, self._config.mode, self._config.firmware_path) if self._config.erase_before_flash: - esptool.erase_flash(esp, args) - esptool.write_flash(esp, args) - # The last line printed by esptool is "Leaving..." -> some indication that the process is done is needed - print("\nDone. Unplug/replug or reset device.") - esp._port.close() + command += " --erase-all" + + print("Command: esptool.py %s\n" % command) + + esptool.main(command.split(" ")) + + # The last line printed by esptool is "Staying in bootloader." -> some indication that the process is + # done is needed + print("\nFirmware successfully flashed. Unplug/replug or reset device \nto switch back to normal boot " + "mode.") except SerialException as e: self._parent.report_error(e.strerror) raise e diff --git a/esptool.py b/esptool.py index 581275c..7561c4b 100755 --- a/esptool.py +++ b/esptool.py @@ -32,9 +32,12 @@ import sys import time import zlib import string -import serial.tools.list_ports as list_ports -import serial +try: + import serial +except ImportError: + print("Pyserial is not installed for %s. Check the README for installation instructions." % (sys.executable)) + raise # check 'serial' is 'pyserial' and not 'serial' https://github.com/espressif/esptool/issues/269 try: @@ -50,8 +53,14 @@ See https://github.com/espressif/esptool/issues/269#issuecomment-385298196 for d except TypeError: pass # __doc__ returns None for pyserial +try: + import serial.tools.list_ports as list_ports +except ImportError: + print("The installed version (%s) of pyserial appears to be too old for esptool.py (Python interpreter %s). " + "Check the README for installation instructions." % (sys.VERSION, sys.executable)) + raise -__version__ = "2.5.1" +__version__ = "2.6" MAX_UINT32 = 0xffffffff MAX_UINT24 = 0xffffff @@ -221,7 +230,12 @@ class ESPLoader(object): self._set_port_baudrate(baud) self._trace_enabled = trace_enabled # set write timeout, to prevent esptool blocked at write forever. - self._port.write_timeout = DEFAULT_SERIAL_WRITE_TIMEOUT + try: + self._port.write_timeout = DEFAULT_SERIAL_WRITE_TIMEOUT + except NotImplementedError: + # no write timeout for RFC2217 ports + # need to set the property back to None or it will continue to fail + self._port.write_timeout = None def _set_port_baudrate(self, baud): try: @@ -1120,6 +1134,18 @@ class ESP32ROM(ESPLoader): if adc_vref: features += ["VRef calibration in efuse"] + blk3_part_res = word3 >> 14 & 0x1 + if blk3_part_res: + features += ["BLK3 partially reserved"] + + word6 = self.read_efuse(6) + coding_scheme = word6 & 0x3 + features += ["Coding Scheme %s" % { + 0: "None", + 1: "3/4", + 2: "Repeat (UNSUPPORTED)", + 3: "Invalid"}[coding_scheme]] + return features def read_efuse(self, n): @@ -1262,19 +1288,26 @@ class ELFSection(ImageSegment): class BaseFirmwareImage(object): SEG_HEADER_LEN = 8 + SHA256_DIGEST_LEN = 32 """ Base class with common firmware image functions """ def __init__(self): self.segments = [] self.entrypoint = 0 + self.elf_sha256 = None + self.elf_sha256_offset = 0 def load_common_header(self, load_file, expected_magic): (magic, segments, self.flash_mode, self.flash_size_freq, self.entrypoint) = struct.unpack(' 16: - raise FatalError('Invalid firmware image magic=%d segments=%d' % (magic, segments)) + if magic != expected_magic: + raise FatalError('Invalid firmware image magic=0x%x' % (magic)) return segments + def verify(self): + if len(self.segments) > 16: + raise FatalError('Invalid segment count %d (max 16). Usually this indicates a linker script problem.' % len(self.segments)) + def load_segment(self, f, is_irom_segment=False): """ Load the next segment from the image file """ file_offs = f.tell() @@ -1292,12 +1325,33 @@ class BaseFirmwareImage(object): if offset > 0x40200000 or offset < 0x3ffe0000 or size > 65536: print('WARNING: Suspicious segment 0x%x, length %d' % (offset, size)) + def maybe_patch_segment_data(self, f, segment_data): + """If SHA256 digest of the ELF file needs to be inserted into this segment, do so. Returns segment data.""" + segment_len = len(segment_data) + file_pos = f.tell() + if self.elf_sha256_offset >= file_pos and self.elf_sha256_offset < file_pos + segment_len: + # SHA256 digest needs to be patched into this segment, + # calculate offset of the digest inside the segment. + patch_offset = self.elf_sha256_offset - file_pos + # Sanity checks + if patch_offset < self.SEG_HEADER_LEN or patch_offset + self.SHA256_DIGEST_LEN > segment_len: + raise FatalError('Can not place SHA256 digest on segment boundary' + + '(elf_sha256_offset=%d, file_pos=%d, segment_size=%d)' % + (self.elf_sha256_offset, file_pos, segment_len)) + assert(len(self.elf_sha256) == self.SHA256_DIGEST_LEN) + # offset relative to the data part + patch_offset -= self.SEG_HEADER_LEN + segment_data = segment_data[0:patch_offset] + self.elf_sha256 + \ + segment_data[patch_offset + self.SHA256_DIGEST_LEN:] + return segment_data + def save_segment(self, f, segment, checksum=None): """ Save the next segment to the image file, return next checksum value if provided """ - f.write(struct.pack(' 0: last_addr = flash_segments[0].addr for segment in flash_segments[1:]: - if segment.addr // IROM_ALIGN == last_addr // IROM_ALIGN: + if segment.addr // self.IROM_ALIGN == last_addr // self.IROM_ALIGN: raise FatalError(("Segment loaded at 0x%08x lands in same 64KB flash mapping as segment loaded at 0x%08x. " + "Can't generate binary. Suggest changing linker script or ELF to merge sections.") % (segment.addr, last_addr)) @@ -1584,15 +1644,15 @@ class ESP32FirmwareImage(BaseFirmwareImage): # # (this is because the segment's vaddr may not be IROM_ALIGNed, more likely is aligned # IROM_ALIGN+0x18 to account for the binary file header - align_past = (segment.addr % IROM_ALIGN) - self.SEG_HEADER_LEN - pad_len = (IROM_ALIGN - (f.tell() % IROM_ALIGN)) + align_past - if pad_len == 0 or pad_len == IROM_ALIGN: + align_past = (segment.addr % self.IROM_ALIGN) - self.SEG_HEADER_LEN + pad_len = (self.IROM_ALIGN - (f.tell() % self.IROM_ALIGN)) + align_past + if pad_len == 0 or pad_len == self.IROM_ALIGN: return 0 # already aligned # subtract SEG_HEADER_LEN a second time, as the padding block has a header as well pad_len -= self.SEG_HEADER_LEN if pad_len < 0: - pad_len += IROM_ALIGN + pad_len += self.IROM_ALIGN return pad_len # try to fit each flash segment on a 64kB aligned boundary @@ -1611,8 +1671,8 @@ class ESP32FirmwareImage(BaseFirmwareImage): total_segments += 1 else: # write the flash segment - assert (f.tell() + 8) % IROM_ALIGN == segment.addr % IROM_ALIGN - checksum = self.save_segment(f, segment, checksum) + assert (f.tell() + 8) % self.IROM_ALIGN == segment.addr % self.IROM_ALIGN + checksum = self.save_flash_segment(f, segment, checksum) flash_segments.pop(0) total_segments += 1 @@ -1626,12 +1686,12 @@ class ESP32FirmwareImage(BaseFirmwareImage): # This ensures all mapped flash content will be verified. if not self.append_digest: raise FatalError("secure_pad only applies if a SHA-256 digest is also appended to the image") - align_past = (f.tell() + self.SEG_HEADER_LEN) % IROM_ALIGN + align_past = (f.tell() + self.SEG_HEADER_LEN) % self.IROM_ALIGN # 16 byte aligned checksum (force the alignment to simplify calculations) checksum_space = 16 # after checksum: SHA-256 digest + (to be added by signing process) version, signature + 12 trailing bytes due to alignment space_after_checksum = 32 + 4 + 64 + 12 - pad_len = (IROM_ALIGN - align_past - checksum_space - space_after_checksum) % IROM_ALIGN + pad_len = (self.IROM_ALIGN - align_past - checksum_space - space_after_checksum) % self.IROM_ALIGN pad_segment = ImageSegment(0, b'\x00' * pad_len, f.tell()) checksum = self.save_segment(f, pad_segment, checksum) @@ -1642,7 +1702,7 @@ class ESP32FirmwareImage(BaseFirmwareImage): image_length = f.tell() if self.secure_pad: - assert ((image_length + space_after_checksum) % IROM_ALIGN) == 0 + assert ((image_length + space_after_checksum) % self.IROM_ALIGN) == 0 # kinda hacky: go back to the initial header and write the new segment count # that includes padding segments. This header is not checksummed @@ -1662,6 +1722,16 @@ class ESP32FirmwareImage(BaseFirmwareImage): with open(filename, 'wb') as real_file: real_file.write(f.getvalue()) + def save_flash_segment(self, f, segment, checksum=None): + """ Save the next segment to the image file, return next checksum value if provided """ + segment_end_pos = f.tell() + len(segment.data) + self.SEG_HEADER_LEN + segment_len_remainder = segment_end_pos % self.IROM_ALIGN + if segment_len_remainder < 0x24: + # Work around a bug in ESP-IDF 2nd stage bootloader, that it didn't map the + # last MMU page, if an IROM/DROM segment was < 0x24 bytes over the page boundary. + segment.data += b'\x00' * (0x24 - segment_len_remainder) + return self.save_segment(f, segment, checksum) + def load_extended_header(self, load_file): def split_byte(n): return (n & 0x0F, (n >> 4) & 0x0F) @@ -1778,9 +1848,16 @@ class ELFFile(object): return f.read(size) prog_sections = [ELFSection(lookup_string(n_offs), lma, read_data(offs, size)) for (n_offs, _type, lma, size, offs) in prog_sections - if lma != 0] + if lma != 0 and size > 0] self.sections = prog_sections + def sha256(self): + # return SHA256 hash of the input ELF file + sha256 = hashlib.sha256() + with open(self.name, 'rb') as f: + sha256.update(f.read()) + return sha256.digest() + def slip_reader(port, trace_function): """Generator to read SLIP packets from a serial port. @@ -1979,15 +2056,15 @@ def write_mem(esp, args): def dump_mem(esp, args): - f = open(args.filename, 'wb') - for i in range(args.size // 4): - d = esp.read_reg(args.address + (i * 4)) - f.write(struct.pack(b'