#!/usr/bin/env python # # ESP8266 ROM Bootloader Utility # https://github.com/themadinventor/esptool # # Copyright (C) 2014 Fredrik Ahlberg # # This program is free software; you can redistribute it and/or modify it under # the terms of the GNU General Public License as published by the Free Software # Foundation; either version 2 of the License, or (at your option) any later version. # # This program is distributed in the hope that it will be useful, but WITHOUT # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS # FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. # # You should have received a copy of the GNU General Public License along with # this program; if not, write to the Free Software Foundation, Inc., 51 Franklin # Street, Fifth Floor, Boston, MA 02110-1301 USA. import sys import struct import serial import math import time import argparse import os import subprocess class ESPROM: # These are the currently known commands supported by the ROM ESP_FLASH_BEGIN = 0x02 ESP_FLASH_DATA = 0x03 ESP_FLASH_END = 0x04 ESP_MEM_BEGIN = 0x05 ESP_MEM_END = 0x06 ESP_MEM_DATA = 0x07 ESP_SYNC = 0x08 ESP_WRITE_REG = 0x09 ESP_READ_REG = 0x0a # Maximum block sized for RAM and Flash writes, respectively. ESP_RAM_BLOCK = 0x1800 ESP_FLASH_BLOCK = 0x400 # Default baudrate. The ROM auto-bauds, so we can use more or less whatever we want. ESP_ROM_BAUD = 115200 # First byte of the application image ESP_IMAGE_MAGIC = 0xe9 # Initial state for the checksum routine ESP_CHECKSUM_MAGIC = 0xef # OTP ROM addresses ESP_OTP_MAC0 = 0x3ff00050 ESP_OTP_MAC1 = 0x3ff00054 # Sflash stub: an assembly routine to read from spi flash and send to host SFLASH_STUB = "\x80\x3c\x00\x40\x1c\x4b\x00\x40\x21\x11\x00\x40\x00\x80" \ "\xfe\x3f\xc1\xfb\xff\xd1\xf8\xff\x2d\x0d\x31\xfd\xff\x41\xf7\xff\x4a" \ "\xdd\x51\xf9\xff\xc0\x05\x00\x21\xf9\xff\x31\xf3\xff\x41\xf5\xff\xc0" \ "\x04\x00\x0b\xcc\x56\xec\xfd\x06\xff\xff\x00\x00" def __init__(self, port = 0, baud = ESP_ROM_BAUD): self._port = serial.Serial(port, baud) """ Read bytes from the serial port while performing SLIP unescaping """ def read(self, length = 1): b = '' while len(b) < length: c = self._port.read(1) if c == '\xdb': c = self._port.read(1) if c == '\xdc': b = b + '\xc0' elif c == '\xdd': b = b + '\xdb' else: raise Exception('Invalid SLIP escape') else: b = b + c return b """ Write bytes to the serial port while performing SLIP escaping """ def write(self, packet): buf = '\xc0'+(packet.replace('\xdb','\xdb\xdd').replace('\xc0','\xdb\xdc'))+'\xc0' self._port.write(buf) """ Calculate checksum of a blob, as it is defined by the ROM """ @staticmethod def checksum(data, state = ESP_CHECKSUM_MAGIC): for b in data: state ^= ord(b) return state """ Send a request and read the response """ def command(self, op = None, data = None, chk = 0): if op: # Construct and send request pkt = struct.pack('> 16) & 0xff) == 0: oui = (0x18, 0xfe, 0x34) elif ((mac1 >> 16) & 0xff) == 1: oui = (0xac, 0xd0, 0x74) else: raise Exception("Unknown OUI") return oui + ((mac1 >> 8) & 0xff, mac1 & 0xff, (mac0 >> 24) & 0xff) """ Read SPI flash manufacturer and device id """ def flash_id(self): self.flash_begin(0, 0) self.write_reg(0x60000240, 0x0, 0xffffffff) self.write_reg(0x60000200, 0x10000000, 0xffffffff) flash_id = esp.read_reg(0x60000240) self.flash_finish(False) return flash_id """ Read SPI flash """ def flash_read(self, offset, size, count = 1): # Create a custom stub stub = struct.pack(' 16: raise Exception('Invalid firmware image') for i in xrange(segments): (offset, size) = struct.unpack(' 0x40200000 or offset < 0x3ffe0000 or size > 65536: raise Exception('Suspicious segment %x,%d' % (offset, size)) self.segments.append((offset, size, f.read(size))) # Skip the padding. The checksum is stored in the last byte so that the # file is a multiple of 16 bytes. align = 15-(f.tell() % 16) f.seek(align, 1) self.checksum = ord(f.read(1)) def add_segment(self, addr, data): # Data should be aligned on word boundary l = len(data) if l % 4: data += b"\x00" * (4 - l % 4) self.segments.append((addr, len(data), data)) def save(self, filename): f = file(filename, 'wb') f.write(struct.pack(' 0: esp.mem_block(data[0:esp.ESP_RAM_BLOCK], seq) data = data[esp.ESP_RAM_BLOCK:] seq += 1 print 'done!' print 'All segments done, executing at %08x' % image.entrypoint esp.mem_finish(image.entrypoint) elif args.operation == 'read_mem': print '0x%08x = 0x%08x' % (args.address, esp.read_reg(args.address)) elif args.operation == 'write_mem': esp.write_reg(args.address, args.value, args.mask, 0) print 'Wrote %08x, mask %08x to %08x' % (args.value, args.mask, args.address) elif args.operation == 'dump_mem': f = file(args.filename, 'wb') for i in xrange(args.size/4): d = esp.read_reg(args.address+(i*4)) f.write(struct.pack(' 0: print '\rWriting at 0x%08x... (%d %%)' % (address + seq*esp.ESP_FLASH_BLOCK, 100*(seq+1)/blocks), sys.stdout.flush() block = image[0:esp.ESP_FLASH_BLOCK] # Fix sflash config data if address == 0 and seq == 0 and block[0] == '\xe9': block = block[0:2] + flash_info + block[4:] # Pad the last block block = block + '\xff' * (esp.ESP_FLASH_BLOCK-len(block)) esp.flash_block(block, seq) image = image[esp.ESP_FLASH_BLOCK:] seq += 1 print print '\nLeaving...' if args.flash_mode == 'dio': esp.flash_unlock_dio() else: esp.flash_finish(False) elif args.operation == 'run': esp.run() elif args.operation == 'image_info': image = ESPFirmwareImage(args.filename) print ('Entry point: %08x' % image.entrypoint) if image.entrypoint != 0 else 'Entry point not set' print '%d segments' % len(image.segments) print checksum = ESPROM.ESP_CHECKSUM_MAGIC for (idx, (offset, size, data)) in enumerate(image.segments): print 'Segment %d: %5d bytes at %08x' % (idx+1, size, offset) checksum = ESPROM.checksum(data, checksum) print print 'Checksum: %02x (%s)' % (image.checksum, 'valid' if image.checksum == checksum else 'invalid!') elif args.operation == 'make_image': image = ESPFirmwareImage() if len(args.segfile) == 0: raise Exception('No segments specified') if len(args.segfile) != len(args.segaddr): raise Exception('Number of specified files does not match number of specified addresses') for (seg, addr) in zip(args.segfile, args.segaddr): data = file(seg, 'rb').read() image.add_segment(addr, data) image.entrypoint = args.entrypoint image.save(args.output) elif args.operation == 'elf2image': if args.output is None: args.output = args.input + '-' e = ELFFile(args.input) image = ESPFirmwareImage() image.entrypoint = e.get_entry_point() for section, start in ((".text", "_text_start"), (".data", "_data_start"), (".rodata", "_rodata_start")): data = e.load_section(section) image.add_segment(e.get_symbol_addr(start), data) image.flash_mode = {'qio':0, 'qout':1, 'dio':2, 'dout': 3}[args.flash_mode] image.flash_size_freq = {'4m':0x00, '2m':0x10, '8m':0x20, '16m':0x30, '32m':0x40}[args.flash_size] image.flash_size_freq += {'40m':0, '26m':1, '20m':2, '80m': 0xf}[args.flash_freq] image.save(args.output + "0x00000.bin") data = e.load_section(".irom0.text") off = e.get_symbol_addr("_irom0_text_start") - 0x40200000 assert off >= 0 f = open(args.output + "0x%05x.bin" % off, "wb") f.write(data) f.close() elif args.operation == 'read_mac': mac = esp.read_mac() print 'MAC: %s' % ':'.join(map(lambda x: '%02x'%x, mac)) elif args.operation == 'flash_id': flash_id = esp.flash_id() print 'Manufacturer: %02x' % (flash_id & 0xff) print 'Device: %02x%02x' % ((flash_id >> 8) & 0xff, (flash_id >> 16) & 0xff) elif args.operation == 'read_flash': print 'Please wait...' file(args.filename, 'wb').write(esp.flash_read(args.address, 1024, int(math.ceil(args.size / 1024.)))[:args.size]) elif args.operation == 'erase_flash': esp.flash_erase()