#!/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 = 0x100 # 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 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' for b in packet: if b == '\xc0': buf += '\xdb\xdc' elif b == '\xdb': buf += '\xdb\xdd' else: buf += b buf += '\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: 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] block = block + '\xe0' * (esp.ESP_FLASH_BLOCK-len(block)) esp.flash_block(block, seq) image = image[esp.ESP_FLASH_BLOCK:] seq += 1 print print '\nLeaving...' 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_symbol_addr("call_user_start") 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.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': mac0 = esp.read_reg(esp.ESP_OTP_MAC0) mac1 = esp.read_reg(esp.ESP_OTP_MAC1) print 'MAC: 18:fe:34:%02x:%02x:%02x' % ((mac1 >> 8) & 0xff, mac1 & 0xff, (mac0 >> 24) & 0xff)