2016-04-12 18:07:25 +02:00
#!/usr/bin/env python
2018-12-23 21:53:25 +01:00
# NB: Before sending a PR to change the above line to '#!/usr/bin/env python2', please read https://github.com/themadinventor/esptool/issues/21
2016-04-12 18:07:25 +02:00
#
# ESP8266 ROM Bootloader Utility
# https://github.com/themadinventor/esptool
#
2018-12-23 21:53:25 +01:00
# Copyright (C) 2014-2016 Fredrik Ahlberg, Angus Gratton, other contributors as noted.
2016-04-12 18:07:25 +02:00
#
# 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.
2018-12-23 21:53:25 +01:00
#
# This program is distributed in the hope that it will be useful, but WITHOUT
2016-04-12 18:07:25 +02:00
# 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 argparse
2018-12-23 21:53:25 +01:00
import hashlib
import inspect
import json
2016-04-12 18:07:25 +02:00
import os
2018-12-23 21:53:25 +01:00
import serial
import struct
2016-04-12 18:07:25 +02:00
import subprocess
2018-12-23 21:53:25 +01:00
import sys
import tempfile
import time
2016-04-12 18:07:25 +02:00
2018-12-23 21:53:25 +01:00
__version__ = " 1.2-dev "
2016-04-12 18:07:25 +02:00
2018-12-23 21:53:25 +01:00
class ESPROM ( object ) :
2016-04-12 18:07:25 +02:00
# 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
2018-12-23 21:53:25 +01:00
ESP_OTP_MAC3 = 0x3ff0005c
2016-04-12 18:07:25 +02:00
2018-12-23 21:53:25 +01:00
# Flash sector size, minimum unit of erase.
ESP_FLASH_SECTOR = 0x1000
def __init__ ( self , port = 0 , baud = ESP_ROM_BAUD ) :
self . _port = serial . Serial ( port )
self . _slip_reader = slip_reader ( port )
# setting baud rate in a separate step is a workaround for
# CH341 driver on some Linux versions (this opens at 9600 then
# sets), shouldn't matter for other platforms/drivers. See
# https://github.com/themadinventor/esptool/issues/44#issuecomment-107094446
self . _port . baudrate = baud
""" Read a SLIP packet from the serial port """
def read ( self ) :
return self . _slip_reader . next ( )
2016-04-12 18:07:25 +02:00
""" Write bytes to the serial port while performing SLIP escaping """
def write ( self , packet ) :
2018-12-23 21:53:25 +01:00
buf = ' \xc0 ' \
+ ( packet . replace ( ' \xdb ' , ' \xdb \xdd ' ) . replace ( ' \xc0 ' , ' \xdb \xdc ' ) ) \
+ ' \xc0 '
2016-04-12 18:07:25 +02:00
self . _port . write ( buf )
""" Calculate checksum of a blob, as it is defined by the ROM """
@staticmethod
2018-12-23 21:53:25 +01:00
def checksum ( data , state = ESP_CHECKSUM_MAGIC ) :
2016-04-12 18:07:25 +02:00
for b in data :
state ^ = ord ( b )
return state
""" Send a request and read the response """
2018-12-23 21:53:25 +01:00
def command ( self , op = None , data = None , chk = 0 ) :
if op is not None :
2016-04-12 18:07:25 +02:00
pkt = struct . pack ( ' <BBHI ' , 0x00 , op , len ( data ) , chk ) + data
self . write ( pkt )
2018-12-23 21:53:25 +01:00
# tries to get a response until that response has the
# same operation as the request or a retries limit has
# exceeded. This is needed for some esp8266s that
# reply with more sync responses than expected.
for retry in xrange ( 100 ) :
p = self . read ( )
if len ( p ) < 8 :
continue
( resp , op_ret , len_ret , val ) = struct . unpack ( ' <BBHI ' , p [ : 8 ] )
if resp != 1 :
continue
body = p [ 8 : ]
if op is None or op_ret == op :
return val , body # valid response received
raise FatalError ( " Response doesn ' t match request " )
2016-04-12 18:07:25 +02:00
""" Perform a connection test """
def sync ( self ) :
2018-12-23 21:53:25 +01:00
self . command ( ESPROM . ESP_SYNC , ' \x07 \x07 \x12 \x20 ' + 32 * ' \x55 ' )
2016-04-12 18:07:25 +02:00
for i in xrange ( 7 ) :
self . command ( )
""" Try connecting repeatedly until successful, or giving up """
def connect ( self ) :
print ' Connecting... '
2018-12-23 21:53:25 +01:00
for _ in xrange ( 4 ) :
# issue reset-to-bootloader:
# RTS = either CH_PD or nRESET (both active low = chip in reset)
# DTR = GPIO0 (active low = boot to flasher)
self . _port . setDTR ( False )
self . _port . setRTS ( True )
time . sleep ( 0.05 )
self . _port . setDTR ( True )
self . _port . setRTS ( False )
time . sleep ( 0.05 )
self . _port . setDTR ( False )
# worst-case latency timer should be 255ms (probably <20ms)
self . _port . timeout = 0.3
for _ in xrange ( 4 ) :
try :
self . _port . flushInput ( )
self . _slip_reader = slip_reader ( self . _port )
self . _port . flushOutput ( )
self . sync ( )
self . _port . timeout = 5
return
except :
time . sleep ( 0.05 )
raise FatalError ( ' Failed to connect to ESP8266 ' )
2016-04-12 18:07:25 +02:00
""" Read memory address in target """
def read_reg ( self , addr ) :
res = self . command ( ESPROM . ESP_READ_REG , struct . pack ( ' <I ' , addr ) )
if res [ 1 ] != " \0 \0 " :
2018-12-23 21:53:25 +01:00
raise FatalError ( ' Failed to read target memory ' )
2016-04-12 18:07:25 +02:00
return res [ 0 ]
""" Write to memory address in target """
2018-12-23 21:53:25 +01:00
def write_reg ( self , addr , value , mask , delay_us = 0 ) :
2016-04-12 18:07:25 +02:00
if self . command ( ESPROM . ESP_WRITE_REG ,
2018-12-23 21:53:25 +01:00
struct . pack ( ' <IIII ' , addr , value , mask , delay_us ) ) [ 1 ] != " \0 \0 " :
raise FatalError ( ' Failed to write target memory ' )
2016-04-12 18:07:25 +02:00
""" Start downloading an application image to RAM """
def mem_begin ( self , size , blocks , blocksize , offset ) :
if self . command ( ESPROM . ESP_MEM_BEGIN ,
2018-12-23 21:53:25 +01:00
struct . pack ( ' <IIII ' , size , blocks , blocksize , offset ) ) [ 1 ] != " \0 \0 " :
raise FatalError ( ' Failed to enter RAM download mode ' )
2016-04-12 18:07:25 +02:00
""" Send a block of an image to RAM """
def mem_block ( self , data , seq ) :
if self . command ( ESPROM . ESP_MEM_DATA ,
2018-12-23 21:53:25 +01:00
struct . pack ( ' <IIII ' , len ( data ) , seq , 0 , 0 ) + data ,
ESPROM . checksum ( data ) ) [ 1 ] != " \0 \0 " :
raise FatalError ( ' Failed to write to target RAM ' )
2016-04-12 18:07:25 +02:00
""" Leave download mode and run the application """
2018-12-23 21:53:25 +01:00
def mem_finish ( self , entrypoint = 0 ) :
2016-04-12 18:07:25 +02:00
if self . command ( ESPROM . ESP_MEM_END ,
2018-12-23 21:53:25 +01:00
struct . pack ( ' <II ' , int ( entrypoint == 0 ) , entrypoint ) ) [ 1 ] != " \0 \0 " :
raise FatalError ( ' Failed to leave RAM download mode ' )
2016-04-12 18:07:25 +02:00
""" Start downloading to Flash (performs an erase) """
def flash_begin ( self , size , offset ) :
old_tmo = self . _port . timeout
num_blocks = ( size + ESPROM . ESP_FLASH_BLOCK - 1 ) / ESPROM . ESP_FLASH_BLOCK
2018-12-23 21:53:25 +01:00
sectors_per_block = 16
sector_size = self . ESP_FLASH_SECTOR
num_sectors = ( size + sector_size - 1 ) / sector_size
start_sector = offset / sector_size
head_sectors = sectors_per_block - ( start_sector % sectors_per_block )
if num_sectors < head_sectors :
head_sectors = num_sectors
if num_sectors < 2 * head_sectors :
erase_size = ( num_sectors + 1 ) / 2 * sector_size
else :
erase_size = ( num_sectors - head_sectors ) * sector_size
self . _port . timeout = 20
t = time . time ( )
result = self . command ( ESPROM . ESP_FLASH_BEGIN ,
struct . pack ( ' <IIII ' , erase_size , num_blocks , ESPROM . ESP_FLASH_BLOCK , offset ) ) [ 1 ]
if size != 0 :
print " Took %.2f s to erase flash block " % ( time . time ( ) - t )
if result != " \0 \0 " :
raise FatalError . WithResult ( ' Failed to enter Flash download mode (result " %s " ) ' , result )
2016-04-12 18:07:25 +02:00
self . _port . timeout = old_tmo
""" Write block to flash """
def flash_block ( self , data , seq ) :
2018-12-23 21:53:25 +01:00
result = self . command ( ESPROM . ESP_FLASH_DATA ,
struct . pack ( ' <IIII ' , len ( data ) , seq , 0 , 0 ) + data ,
ESPROM . checksum ( data ) ) [ 1 ]
if result != " \0 \0 " :
raise FatalError . WithResult ( ' Failed to write to target Flash after seq %d (got result %% s) ' % seq , result )
2016-04-12 18:07:25 +02:00
""" Leave flash mode and run/reboot """
2018-12-23 21:53:25 +01:00
def flash_finish ( self , reboot = False ) :
2016-04-12 18:07:25 +02:00
pkt = struct . pack ( ' <I ' , int ( not reboot ) )
if self . command ( ESPROM . ESP_FLASH_END , pkt ) [ 1 ] != " \0 \0 " :
2018-12-23 21:53:25 +01:00
raise FatalError ( ' Failed to leave Flash mode ' )
2016-04-12 18:07:25 +02:00
""" Run application code in flash """
2018-12-23 21:53:25 +01:00
def run ( self , reboot = False ) :
2016-04-12 18:07:25 +02:00
# Fake flash begin immediately followed by flash end
self . flash_begin ( 0 , 0 )
self . flash_finish ( reboot )
""" Read MAC from OTP ROM """
def read_mac ( self ) :
2018-12-23 21:53:25 +01:00
mac0 = self . read_reg ( self . ESP_OTP_MAC0 )
mac1 = self . read_reg ( self . ESP_OTP_MAC1 )
mac3 = self . read_reg ( self . ESP_OTP_MAC3 )
if ( mac3 != 0 ) :
oui = ( ( mac3 >> 16 ) & 0xff , ( mac3 >> 8 ) & 0xff , mac3 & 0xff )
elif ( ( mac1 >> 16 ) & 0xff ) == 0 :
2016-04-12 18:07:25 +02:00
oui = ( 0x18 , 0xfe , 0x34 )
elif ( ( mac1 >> 16 ) & 0xff ) == 1 :
oui = ( 0xac , 0xd0 , 0x74 )
else :
2018-12-23 21:53:25 +01:00
raise FatalError ( " Unknown OUI " )
2016-04-12 18:07:25 +02:00
return oui + ( ( mac1 >> 8 ) & 0xff , mac1 & 0xff , ( mac0 >> 24 ) & 0xff )
2018-12-23 21:53:25 +01:00
""" Read Chip ID from OTP ROM - see http://esp8266-re.foogod.com/wiki/System_get_chip_id_ % 28IoT_RTOS_SDK_0.9.9 % 29 """
def chip_id ( self ) :
id0 = self . read_reg ( self . ESP_OTP_MAC0 )
id1 = self . read_reg ( self . ESP_OTP_MAC1 )
return ( id0 >> 24 ) | ( ( id1 & 0xffffff ) << 8 )
2016-04-12 18:07:25 +02:00
""" 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 )
2018-12-23 21:53:25 +01:00
flash_id = self . read_reg ( 0x60000240 )
2016-04-12 18:07:25 +02:00
self . flash_finish ( False )
return flash_id
2018-12-23 21:53:25 +01:00
""" Abuse the loader protocol to force flash to be left in write mode """
def flash_unlock_dio ( self ) :
# Enable flash write mode
2016-04-12 18:07:25 +02:00
self . flash_begin ( 0 , 0 )
2018-12-23 21:53:25 +01:00
# Reset the chip rather than call flash_finish(), which would have
# write protected the chip again (why oh why does it do that?!)
self . mem_begin ( 0 , 0 , 0 , 0x40100000 )
self . mem_finish ( 0x40000080 )
2016-04-12 18:07:25 +02:00
""" Perform a chip erase of SPI flash """
def flash_erase ( self ) :
# Trick ROM to initialize SFlash
self . flash_begin ( 0 , 0 )
# This is hacky: we don't have a custom stub, instead we trick
# the bootloader to jump to the SPIEraseChip() routine and then halt/crash
# when it tries to boot an unconfigured system.
self . mem_begin ( 0 , 0 , 0 , 0x40100000 )
self . mem_finish ( 0x40004984 )
# Yup - there's no good way to detect if we succeeded.
# It it on the other hand unlikely to fail.
2018-12-23 21:53:25 +01:00
def run_stub ( self , stub , params , read_output = True ) :
stub = dict ( stub )
stub [ ' code ' ] = unhexify ( stub [ ' code ' ] )
if ' data ' in stub :
stub [ ' data ' ] = unhexify ( stub [ ' data ' ] )
if stub [ ' num_params ' ] != len ( params ) :
raise FatalError ( ' Stub requires %d params, %d provided '
% ( stub [ ' num_params ' ] , len ( params ) ) )
params = struct . pack ( ' < ' + ( ' I ' * stub [ ' num_params ' ] ) , * params )
pc = params + stub [ ' code ' ]
# Upload
self . mem_begin ( len ( pc ) , 1 , len ( pc ) , stub [ ' params_start ' ] )
self . mem_block ( pc , 0 )
if ' data ' in stub :
self . mem_begin ( len ( stub [ ' data ' ] ) , 1 , len ( stub [ ' data ' ] ) , stub [ ' data_start ' ] )
self . mem_block ( stub [ ' data ' ] , 0 )
self . mem_finish ( stub [ ' entry ' ] )
if read_output :
print ' Stub executed, reading response: '
while True :
p = self . read ( )
print hexify ( p )
if p == ' ' :
return
class ESPBOOTLOADER ( object ) :
""" These are constants related to software ESP bootloader, working with ' v2 ' image files """
# First byte of the "v2" application image
IMAGE_V2_MAGIC = 0xea
# First 'segment' value in a "v2" application image, appears to be a constant version value?
IMAGE_V2_SEGMENT = 4
def LoadFirmwareImage ( filename ) :
""" Load a firmware image, without knowing what kind of file (v1 or v2) it is.
Returns a BaseFirmwareImage subclass , either ESPFirmwareImage ( v1 ) or OTAFirmwareImage ( v2 ) .
"""
with open ( filename , ' rb ' ) as f :
magic = ord ( f . read ( 1 ) )
f . seek ( 0 )
if magic == ESPROM . ESP_IMAGE_MAGIC :
return ESPFirmwareImage ( f )
elif magic == ESPBOOTLOADER . IMAGE_V2_MAGIC :
return OTAFirmwareImage ( f )
else :
raise FatalError ( " Invalid image magic number: %d " % magic )
class BaseFirmwareImage ( object ) :
""" Base class with common firmware image functions """
def __init__ ( self ) :
2016-04-12 18:07:25 +02:00
self . segments = [ ]
self . entrypoint = 0
2018-12-23 21:53:25 +01:00
def add_segment ( self , addr , data , pad_to = 4 ) :
""" Add a segment to the image, with specified address & data
( padded to a boundary of pad_to size ) """
# Data should be aligned on word boundary
l = len ( data )
if l % pad_to :
data + = b " \x00 " * ( pad_to - l % pad_to )
if l > 0 :
self . segments . append ( ( addr , len ( data ) , data ) )
def load_segment ( self , f , is_irom_segment = False ) :
""" Load the next segment from the image file """
( offset , size ) = struct . unpack ( ' <II ' , f . read ( 8 ) )
if not is_irom_segment :
if offset > 0x40200000 or offset < 0x3ffe0000 or size > 65536 :
raise FatalError ( ' Suspicious segment 0x %x , length %d ' % ( offset , size ) )
segment_data = f . read ( size )
if len ( segment_data ) < size :
raise FatalError ( ' End of file reading segment 0x %x , length %d (actual length %d ) ' % ( offset , size , len ( segment_data ) ) )
segment = ( offset , size , segment_data )
self . segments . append ( segment )
return segment
def save_segment ( self , f , segment , checksum = None ) :
""" Save the next segment to the image file, return next checksum value if provided """
( offset , size , data ) = segment
f . write ( struct . pack ( ' <II ' , offset , size ) )
f . write ( data )
if checksum is not None :
return ESPROM . checksum ( data , checksum )
def read_checksum ( self , f ) :
""" Return ESPROM checksum from end of just-read image """
# Skip the padding. The checksum is stored in the last byte so that the
# file is a multiple of 16 bytes.
align_file_position ( f , 16 )
return ord ( f . read ( 1 ) )
def append_checksum ( self , f , checksum ) :
""" Append ESPROM checksum to the just-written image """
align_file_position ( f , 16 )
f . write ( struct . pack ( ' B ' , checksum ) )
def write_v1_header ( self , f , segments ) :
f . write ( struct . pack ( ' <BBBBI ' , ESPROM . ESP_IMAGE_MAGIC , len ( segments ) ,
self . flash_mode , self . flash_size_freq , self . entrypoint ) )
class ESPFirmwareImage ( BaseFirmwareImage ) :
""" ' Version 1 ' firmware image, segments loaded directly by the ROM bootloader. """
def __init__ ( self , load_file = None ) :
super ( ESPFirmwareImage , self ) . __init__ ( )
2016-04-12 18:07:25 +02:00
self . flash_mode = 0
self . flash_size_freq = 0
2018-12-23 21:53:25 +01:00
self . version = 1
if load_file is not None :
( magic , segments , self . flash_mode , self . flash_size_freq , self . entrypoint ) = struct . unpack ( ' <BBBBI ' , load_file . read ( 8 ) )
2016-04-12 18:07:25 +02:00
# some sanity check
if magic != ESPROM . ESP_IMAGE_MAGIC or segments > 16 :
2018-12-23 21:53:25 +01:00
raise FatalError ( ' Invalid firmware image magic= %d segments= %d ' % ( magic , segments ) )
2016-04-12 18:07:25 +02:00
2018-12-23 21:53:25 +01:00
for i in xrange ( segments ) :
self . load_segment ( load_file )
self . checksum = self . read_checksum ( load_file )
2016-04-12 18:07:25 +02:00
def save ( self , filename ) :
2018-12-23 21:53:25 +01:00
with open ( filename , ' wb ' ) as f :
self . write_v1_header ( f , self . segments )
checksum = ESPROM . ESP_CHECKSUM_MAGIC
for segment in self . segments :
checksum = self . save_segment ( f , segment , checksum )
self . append_checksum ( f , checksum )
class OTAFirmwareImage ( BaseFirmwareImage ) :
""" ' Version 2 ' firmware image, segments loaded by software bootloader stub
( ie Espressif bootloader or rboot )
"""
def __init__ ( self , load_file = None ) :
super ( OTAFirmwareImage , self ) . __init__ ( )
self . version = 2
if load_file is not None :
( magic , segments , first_flash_mode , first_flash_size_freq , first_entrypoint ) = struct . unpack ( ' <BBBBI ' , load_file . read ( 8 ) )
2016-04-12 18:07:25 +02:00
2018-12-23 21:53:25 +01:00
# some sanity check
if magic != ESPBOOTLOADER . IMAGE_V2_MAGIC :
raise FatalError ( ' Invalid V2 image magic= %d ' % ( magic ) )
if segments != 4 :
# segment count is not really segment count here, but we expect to see '4'
print ' Warning: V2 header has unexpected " segment " count %d (usually 4) ' % segments
# irom segment comes before the second header
self . load_segment ( load_file , True )
( magic , segments , self . flash_mode , self . flash_size_freq , self . entrypoint ) = struct . unpack ( ' <BBBBI ' , load_file . read ( 8 ) )
if first_flash_mode != self . flash_mode :
print ( ' WARNING: Flash mode value in first header (0x %02x ) disagrees with second (0x %02x ). Using second value. '
% ( first_flash_mode , self . flash_mode ) )
if first_flash_size_freq != self . flash_size_freq :
print ( ' WARNING: Flash size/freq value in first header (0x %02x ) disagrees with second (0x %02x ). Using second value. '
% ( first_flash_size_freq , self . flash_size_freq ) )
if first_entrypoint != self . entrypoint :
print ( ' WARNING: Enterypoint address in first header (0x %08x ) disagrees with second header (0x %08x ). Using second value. '
% ( first_entrypoint , self . entrypoint ) )
2016-04-12 18:07:25 +02:00
2018-12-23 21:53:25 +01:00
if magic != ESPROM . ESP_IMAGE_MAGIC or segments > 16 :
raise FatalError ( ' Invalid V2 second header magic= %d segments= %d ' % ( magic , segments ) )
2016-04-12 18:07:25 +02:00
2018-12-23 21:53:25 +01:00
# load all the usual segments
for _ in xrange ( segments ) :
self . load_segment ( load_file )
self . checksum = self . read_checksum ( load_file )
2016-04-12 18:07:25 +02:00
2018-12-23 21:53:25 +01:00
def save ( self , filename ) :
with open ( filename , ' wb ' ) as f :
# Save first header for irom0 segment
f . write ( struct . pack ( ' <BBBBI ' , ESPBOOTLOADER . IMAGE_V2_MAGIC , ESPBOOTLOADER . IMAGE_V2_SEGMENT ,
self . flash_mode , self . flash_size_freq , self . entrypoint ) )
# irom0 segment identified by load address zero
irom_segments = [ segment for segment in self . segments if segment [ 0 ] == 0 ]
if len ( irom_segments ) != 1 :
raise FatalError ( ' Found %d segments that could be irom0. Bad ELF file? ' % len ( irom_segments ) )
# save irom0 segment
irom_segment = irom_segments [ 0 ]
self . save_segment ( f , irom_segment )
# second header, matches V1 header and contains loadable segments
normal_segments = [ s for s in self . segments if s != irom_segment ]
self . write_v1_header ( f , normal_segments )
checksum = ESPROM . ESP_CHECKSUM_MAGIC
for segment in normal_segments :
checksum = self . save_segment ( f , segment , checksum )
self . append_checksum ( f , checksum )
class ELFFile ( object ) :
2016-04-12 18:07:25 +02:00
def __init__ ( self , name ) :
2018-12-23 21:53:25 +01:00
self . name = binutils_safe_path ( name )
2016-04-12 18:07:25 +02:00
self . symbols = None
def _fetch_symbols ( self ) :
if self . symbols is not None :
return
self . symbols = { }
try :
tool_nm = " xtensa-lx106-elf-nm "
2018-12-23 21:53:25 +01:00
if os . getenv ( ' XTENSA_CORE ' ) == ' lx106 ' :
2016-04-12 18:07:25 +02:00
tool_nm = " xt-nm "
proc = subprocess . Popen ( [ tool_nm , self . name ] , stdout = subprocess . PIPE )
except OSError :
2018-12-23 21:53:25 +01:00
print " Error calling %s , do you have Xtensa toolchain in PATH? " % tool_nm
2016-04-12 18:07:25 +02:00
sys . exit ( 1 )
for l in proc . stdout :
fields = l . strip ( ) . split ( )
2018-12-23 21:53:25 +01:00
try :
if fields [ 0 ] == " U " :
print " Warning: ELF binary has undefined symbol %s " % fields [ 1 ]
continue
if fields [ 0 ] == " w " :
continue # can skip weak symbols
self . symbols [ fields [ 2 ] ] = int ( fields [ 0 ] , 16 )
except ValueError :
raise FatalError ( " Failed to strip symbol output from nm: %s " % fields )
2016-04-12 18:07:25 +02:00
def get_symbol_addr ( self , sym ) :
self . _fetch_symbols ( )
return self . symbols [ sym ]
2018-12-23 21:53:25 +01:00
def get_entry_point ( self ) :
tool_readelf = " xtensa-lx106-elf-readelf "
if os . getenv ( ' XTENSA_CORE ' ) == ' lx106 ' :
tool_readelf = " xt-readelf "
try :
proc = subprocess . Popen ( [ tool_readelf , " -h " , self . name ] , stdout = subprocess . PIPE )
except OSError :
print " Error calling %s , do you have Xtensa toolchain in PATH? " % tool_readelf
sys . exit ( 1 )
for l in proc . stdout :
fields = l . strip ( ) . split ( )
if fields [ 0 ] == " Entry " :
return int ( fields [ 3 ] , 0 )
2016-04-12 18:07:25 +02:00
def load_section ( self , section ) :
tool_objcopy = " xtensa-lx106-elf-objcopy "
2018-12-23 21:53:25 +01:00
if os . getenv ( ' XTENSA_CORE ' ) == ' lx106 ' :
2016-04-12 18:07:25 +02:00
tool_objcopy = " xt-objcopy "
2018-12-23 21:53:25 +01:00
tmpsection = binutils_safe_path ( tempfile . mktemp ( suffix = " .section " ) )
try :
subprocess . check_call ( [ tool_objcopy , " --only-section " , section , " -Obinary " , self . name , tmpsection ] )
with open ( tmpsection , " rb " ) as f :
data = f . read ( )
finally :
os . remove ( tmpsection )
2016-04-12 18:07:25 +02:00
return data
2018-12-23 21:53:25 +01:00
class CesantaFlasher ( object ) :
# From stub_flasher.h
CMD_FLASH_WRITE = 1
CMD_FLASH_READ = 2
CMD_FLASH_DIGEST = 3
CMD_BOOT_FW = 6
def __init__ ( self , esp , baud_rate = 0 ) :
print ' Running Cesanta flasher stub... '
if baud_rate < = ESPROM . ESP_ROM_BAUD : # don't change baud rates if we already synced at that rate
baud_rate = 0
self . _esp = esp
esp . run_stub ( json . loads ( _CESANTA_FLASHER_STUB ) , [ baud_rate ] , read_output = False )
if baud_rate > 0 :
esp . _port . baudrate = baud_rate
# Read the greeting.
p = esp . read ( )
if p != ' OHAI ' :
raise FatalError ( ' Failed to connect to the flasher (got %s ) ' % hexify ( p ) )
def flash_write ( self , addr , data , show_progress = False ) :
assert addr % self . _esp . ESP_FLASH_SECTOR == 0 , ' Address must be sector-aligned '
assert len ( data ) % self . _esp . ESP_FLASH_SECTOR == 0 , ' Length must be sector-aligned '
sys . stdout . write ( ' Writing %d @ 0x %x ... ' % ( len ( data ) , addr ) )
sys . stdout . flush ( )
self . _esp . write ( struct . pack ( ' <B ' , self . CMD_FLASH_WRITE ) )
self . _esp . write ( struct . pack ( ' <III ' , addr , len ( data ) , 1 ) )
num_sent , num_written = 0 , 0
while num_written < len ( data ) :
p = self . _esp . read ( )
if len ( p ) == 4 :
num_written = struct . unpack ( ' <I ' , p ) [ 0 ]
elif len ( p ) == 1 :
status_code = struct . unpack ( ' <B ' , p ) [ 0 ]
raise FatalError ( ' Write failure, status: %x ' % status_code )
else :
raise FatalError ( ' Unexpected packet while writing: %s ' % hexify ( p ) )
if show_progress :
progress = ' %d ( %d %% ) ' % ( num_written , num_written * 100.0 / len ( data ) )
sys . stdout . write ( progress + ' \b ' * len ( progress ) )
sys . stdout . flush ( )
while num_sent - num_written < 5120 :
self . _esp . _port . write ( data [ num_sent : num_sent + 1024 ] )
num_sent + = 1024
p = self . _esp . read ( )
if len ( p ) != 16 :
raise FatalError ( ' Expected digest, got: %s ' % hexify ( p ) )
digest = hexify ( p ) . upper ( )
expected_digest = hashlib . md5 ( data ) . hexdigest ( ) . upper ( )
print
if digest != expected_digest :
raise FatalError ( ' Digest mismatch: expected %s , got %s ' % ( expected_digest , digest ) )
p = self . _esp . read ( )
if len ( p ) != 1 :
raise FatalError ( ' Expected status, got: %s ' % hexify ( p ) )
status_code = struct . unpack ( ' <B ' , p ) [ 0 ]
if status_code != 0 :
raise FatalError ( ' Write failure, status: %x ' % status_code )
def flash_read ( self , addr , length , show_progress = False ) :
sys . stdout . write ( ' Reading %d @ 0x %x ... ' % ( length , addr ) )
sys . stdout . flush ( )
self . _esp . write ( struct . pack ( ' <B ' , self . CMD_FLASH_READ ) )
# USB may not be able to keep up with the read rate, especially at
# higher speeds. Since we don't have flow control, this will result in
# data loss. Hence, we use small packet size and only allow small
# number of bytes in flight, which we can reasonably expect to fit in
# the on-chip FIFO. max_in_flight = 64 works for CH340G, other chips may
# have longer FIFOs and could benefit from increasing max_in_flight.
self . _esp . write ( struct . pack ( ' <IIII ' , addr , length , 32 , 64 ) )
data = ' '
while True :
p = self . _esp . read ( )
data + = p
self . _esp . write ( struct . pack ( ' <I ' , len ( data ) ) )
if show_progress and ( len ( data ) % 1024 == 0 or len ( data ) == length ) :
progress = ' %d ( %d %% ) ' % ( len ( data ) , len ( data ) * 100.0 / length )
sys . stdout . write ( progress + ' \b ' * len ( progress ) )
sys . stdout . flush ( )
if len ( data ) == length :
break
if len ( data ) > length :
raise FatalError ( ' Read more than expected ' )
p = self . _esp . read ( )
if len ( p ) != 16 :
raise FatalError ( ' Expected digest, got: %s ' % hexify ( p ) )
expected_digest = hexify ( p ) . upper ( )
digest = hashlib . md5 ( data ) . hexdigest ( ) . upper ( )
print
if digest != expected_digest :
raise FatalError ( ' Digest mismatch: expected %s , got %s ' % ( expected_digest , digest ) )
p = self . _esp . read ( )
if len ( p ) != 1 :
raise FatalError ( ' Expected status, got: %s ' % hexify ( p ) )
status_code = struct . unpack ( ' <B ' , p ) [ 0 ]
if status_code != 0 :
raise FatalError ( ' Write failure, status: %x ' % status_code )
return data
def flash_digest ( self , addr , length , digest_block_size = 0 ) :
self . _esp . write ( struct . pack ( ' <B ' , self . CMD_FLASH_DIGEST ) )
self . _esp . write ( struct . pack ( ' <III ' , addr , length , digest_block_size ) )
digests = [ ]
while True :
p = self . _esp . read ( )
if len ( p ) == 16 :
digests . append ( p )
elif len ( p ) == 1 :
status_code = struct . unpack ( ' <B ' , p ) [ 0 ]
if status_code != 0 :
raise FatalError ( ' Write failure, status: %x ' % status_code )
break
else :
raise FatalError ( ' Unexpected packet: %s ' % hexify ( p ) )
return digests [ - 1 ] , digests [ : - 1 ]
def boot_fw ( self ) :
self . _esp . write ( struct . pack ( ' <B ' , self . CMD_BOOT_FW ) )
p = self . _esp . read ( )
if len ( p ) != 1 :
raise FatalError ( ' Expected status, got: %s ' % hexify ( p ) )
status_code = struct . unpack ( ' <B ' , p ) [ 0 ]
if status_code != 0 :
raise FatalError ( ' Boot failure, status: %x ' % status_code )
def slip_reader ( port ) :
""" Generator to read SLIP packets from a serial port.
Yields one full SLIP packet at a time , raises exception on timeout or invalid data .
Designed to avoid too many calls to serial . read ( 1 ) , which can bog
down on slow systems .
"""
partial_packet = None
in_escape = False
while True :
waiting = port . inWaiting ( )
read_bytes = port . read ( 1 if waiting == 0 else waiting )
if read_bytes == ' ' :
raise FatalError ( " Timed out waiting for packet %s " % ( " header " if partial_packet is None else " content " ) )
for b in read_bytes :
if partial_packet is None : # waiting for packet header
if b == ' \xc0 ' :
partial_packet = " "
else :
raise FatalError ( ' Invalid head of packet ( %r ) ' % b )
elif in_escape : # part-way through escape sequence
in_escape = False
if b == ' \xdc ' :
partial_packet + = ' \xc0 '
elif b == ' \xdd ' :
partial_packet + = ' \xdb '
else :
raise FatalError ( ' Invalid SLIP escape ( %r %r ) ' % ( ' \xdb ' , b ) )
elif b == ' \xdb ' : # start of escape sequence
in_escape = True
elif b == ' \xc0 ' : # end of packet
yield partial_packet
partial_packet = None
else : # normal byte in packet
partial_packet + = b
2016-04-12 18:07:25 +02:00
def arg_auto_int ( x ) :
return int ( x , 0 )
2018-12-23 21:53:25 +01:00
def div_roundup ( a , b ) :
""" Return a/b rounded up to nearest integer,
equivalent result to int ( math . ceil ( float ( int ( a ) ) / float ( int ( b ) ) ) , only
without possible floating point accuracy errors .
"""
return ( int ( a ) + int ( b ) - 1 ) / int ( b )
def binutils_safe_path ( p ) :
""" Returns a ' safe ' version of path ' p ' to pass to binutils
Only does anything under Cygwin Python , where cygwin paths need to
be translated to Windows paths if the binutils wasn ' t compiled
using Cygwin ( should also work with binutils compiled using
Cygwin , see #73.)
"""
if sys . platform == " cygwin " :
try :
return subprocess . check_output ( [ " cygpath " , " -w " , p ] ) . rstrip ( ' \n ' )
except subprocess . CalledProcessError :
print " WARNING: Failed to call cygpath to sanitise Cygwin path. "
return p
def align_file_position ( f , size ) :
""" Align the position in the file to the next block of specified size """
align = ( size - 1 ) - ( f . tell ( ) % size )
f . seek ( align , 1 )
def hexify ( s ) :
return ' ' . join ( ' %02X ' % ord ( c ) for c in s )
def unhexify ( hs ) :
s = ' '
for i in range ( 0 , len ( hs ) - 1 , 2 ) :
s + = chr ( int ( hs [ i ] + hs [ i + 1 ] , 16 ) )
return s
class FatalError ( RuntimeError ) :
"""
Wrapper class for runtime errors that aren ' t caused by internal bugs, but by
ESP8266 responses or input content .
"""
def __init__ ( self , message ) :
RuntimeError . __init__ ( self , message )
@staticmethod
def WithResult ( message , result ) :
"""
Return a fatal error object that includes the hex values of
' result ' as a string formatted argument .
"""
return FatalError ( message % " , " . join ( hex ( ord ( x ) ) for x in result ) )
# "Operation" commands, executable at command line. One function each
#
# Each function takes either two args (<ESPROM instance>, <args>) or a single <args>
# argument.
def load_ram ( esp , args ) :
image = LoadFirmwareImage ( args . filename )
print ' RAM boot... '
for ( offset , size , data ) in image . segments :
print ' Downloading %d bytes at %08x ... ' % ( size , offset ) ,
sys . stdout . flush ( )
esp . mem_begin ( size , div_roundup ( size , esp . ESP_RAM_BLOCK ) , esp . ESP_RAM_BLOCK , offset )
seq = 0
while len ( data ) > 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 )
def read_mem ( esp , args ) :
print ' 0x %08x = 0x %08x ' % ( args . address , esp . read_reg ( args . address ) )
def write_mem ( esp , args ) :
esp . write_reg ( args . address , args . value , args . mask , 0 )
print ' Wrote %08x , mask %08x to %08x ' % ( args . value , args . mask , args . address )
def dump_mem ( esp , args ) :
f = file ( args . filename , ' wb ' )
for i in xrange ( args . size / 4 ) :
d = esp . read_reg ( args . address + ( i * 4 ) )
f . write ( struct . pack ( ' <I ' , d ) )
if f . tell ( ) % 1024 == 0 :
print ' \r %d bytes read... ( %d %% ) ' % ( f . tell ( ) ,
f . tell ( ) * 100 / args . size ) ,
sys . stdout . flush ( )
print ' Done! '
def write_flash ( esp , args ) :
flash_mode = { ' qio ' : 0 , ' qout ' : 1 , ' dio ' : 2 , ' dout ' : 3 } [ args . flash_mode ]
flash_size_freq = { ' 4m ' : 0x00 , ' 2m ' : 0x10 , ' 8m ' : 0x20 , ' 16m ' : 0x30 , ' 32m ' : 0x40 , ' 16m-c1 ' : 0x50 , ' 32m-c1 ' : 0x60 , ' 32m-c2 ' : 0x70 , ' 64m ' : 0x80 , ' 128m ' : 0x90 } [ args . flash_size ]
flash_size_freq + = { ' 40m ' : 0 , ' 26m ' : 1 , ' 20m ' : 2 , ' 80m ' : 0xf } [ args . flash_freq ]
flash_params = struct . pack ( ' BB ' , flash_mode , flash_size_freq )
flasher = CesantaFlasher ( esp , args . baud )
for address , argfile in args . addr_filename :
image = argfile . read ( )
argfile . seek ( 0 ) # rewind in case we need it again
# Fix sflash config data.
if address == 0 and image [ 0 ] == ' \xe9 ' :
print ' Flash params set to 0x %02x %02x ' % ( flash_mode , flash_size_freq )
image = image [ 0 : 2 ] + flash_params + image [ 4 : ]
# Pad to sector size, which is the minimum unit of writing (erasing really).
if len ( image ) % esp . ESP_FLASH_SECTOR != 0 :
image + = ' \xff ' * ( esp . ESP_FLASH_SECTOR - ( len ( image ) % esp . ESP_FLASH_SECTOR ) )
t = time . time ( )
flasher . flash_write ( address , image , not args . no_progress )
t = time . time ( ) - t
print ( ' \r Wrote %d bytes at 0x %x in %.1f seconds ( %.1f kbit/s)... '
% ( len ( image ) , address , t , len ( image ) / t * 8 / 1000 ) )
print ' Leaving... '
if args . verify :
print ' Verifying just-written flash... '
_verify_flash ( flasher , args , flash_params )
flasher . boot_fw ( )
def image_info ( args ) :
image = LoadFirmwareImage ( args . filename )
print ( ' Image version: %d ' % image . version )
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 ) :
if image . version == 2 and idx == 0 :
print ' Segment 1: %d bytes IROM0 (no load address) ' % size
else :
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! ' )
def make_image ( args ) :
image = ESPFirmwareImage ( )
if len ( args . segfile ) == 0 :
raise FatalError ( ' No segments specified ' )
if len ( args . segfile ) != len ( args . segaddr ) :
raise FatalError ( ' 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 )
def elf2image ( args ) :
e = ELFFile ( args . input )
if args . version == ' 1 ' :
image = ESPFirmwareImage ( )
else :
image = OTAFirmwareImage ( )
irom_data = e . load_section ( ' .irom0.text ' )
if len ( irom_data ) == 0 :
raise FatalError ( " .irom0.text section not found in ELF file - can ' t create V2 image. " )
image . add_segment ( 0 , irom_data , 16 )
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 , ' 16m-c1 ' : 0x50 , ' 32m-c1 ' : 0x60 , ' 32m-c2 ' : 0x70 , ' 64m ' : 0x80 , ' 128m ' : 0x90 } [ args . flash_size ]
image . flash_size_freq + = { ' 40m ' : 0 , ' 26m ' : 1 , ' 20m ' : 2 , ' 80m ' : 0xf } [ args . flash_freq ]
irom_offs = e . get_symbol_addr ( " _irom0_text_start " ) - 0x40200000
if args . version == ' 1 ' :
if args . output is None :
args . output = args . input + ' - '
image . save ( args . output + " 0x00000.bin " )
data = e . load_section ( " .irom0.text " )
if irom_offs < 0 :
raise FatalError ( ' Address of symbol _irom0_text_start in ELF is located before flash mapping address. Bad linker script? ' )
if ( irom_offs & 0xFFF ) != 0 : # irom0 isn't flash sector aligned
print " WARNING: irom0 section offset is 0x %08x . ELF is probably linked for ' elf2image --version=2 ' " % irom_offs
with open ( args . output + " 0x %05x .bin " % irom_offs , " wb " ) as f :
f . write ( data )
f . close ( )
else : # V2 OTA image
if args . output is None :
args . output = " %s -0x %05x .bin " % ( os . path . splitext ( args . input ) [ 0 ] , irom_offs & ~ ( ESPROM . ESP_FLASH_SECTOR - 1 ) )
image . save ( args . output )
def read_mac ( esp , args ) :
mac = esp . read_mac ( )
print ' MAC: %s ' % ' : ' . join ( map ( lambda x : ' %02x ' % x , mac ) )
def chip_id ( esp , args ) :
chipid = esp . chip_id ( )
print ' Chip ID: 0x %08x ' % chipid
def erase_flash ( esp , args ) :
print ' Erasing flash (this may take a while)... '
esp . flash_erase ( )
def run ( esp , args ) :
esp . run ( )
def flash_id ( esp , args ) :
flash_id = esp . flash_id ( )
print ' Manufacturer: %02x ' % ( flash_id & 0xff )
print ' Device: %02x %02x ' % ( ( flash_id >> 8 ) & 0xff , ( flash_id >> 16 ) & 0xff )
def read_flash ( esp , args ) :
flasher = CesantaFlasher ( esp , args . baud )
t = time . time ( )
data = flasher . flash_read ( args . address , args . size , not args . no_progress )
t = time . time ( ) - t
print ( ' \r Read %d bytes at 0x %x in %.1f seconds ( %.1f kbit/s)... '
% ( len ( data ) , args . address , t , len ( data ) / t * 8 / 1000 ) )
file ( args . filename , ' wb ' ) . write ( data )
def _verify_flash ( flasher , args , flash_params = None ) :
differences = False
for address , argfile in args . addr_filename :
image = argfile . read ( )
argfile . seek ( 0 ) # rewind in case we need it again
if address == 0 and image [ 0 ] == ' \xe9 ' and flash_params is not None :
image = image [ 0 : 2 ] + flash_params + image [ 4 : ]
image_size = len ( image )
print ' Verifying 0x %x ( %d ) bytes @ 0x %08x in flash against %s ... ' % ( image_size , image_size , address , argfile . name )
# Try digest first, only read if there are differences.
digest , _ = flasher . flash_digest ( address , image_size )
digest = hexify ( digest ) . upper ( )
expected_digest = hashlib . md5 ( image ) . hexdigest ( ) . upper ( )
if digest == expected_digest :
print ' -- verify OK (digest matched) '
continue
else :
differences = True
if getattr ( args , ' diff ' , ' no ' ) != ' yes ' :
print ' -- verify FAILED (digest mismatch) '
continue
flash = flasher . flash_read ( address , image_size )
assert flash != image
diff = [ i for i in xrange ( image_size ) if flash [ i ] != image [ i ] ]
print ' -- verify FAILED: %d differences, first @ 0x %08x ' % ( len ( diff ) , address + diff [ 0 ] )
for d in diff :
print ' %08x %02x %02x ' % ( address + d , ord ( flash [ d ] ) , ord ( image [ d ] ) )
if differences :
raise FatalError ( " Verify failed. " )
def verify_flash ( esp , args , flash_params = None ) :
flasher = CesantaFlasher ( esp )
_verify_flash ( flasher , args , flash_params )
def version ( args ) :
print __version__
#
# End of operations functions
#
def main ( ) :
parser = argparse . ArgumentParser ( description = ' esptool.py v %s - ESP8266 ROM Bootloader Utility ' % __version__ , prog = ' esptool ' )
2016-04-12 18:07:25 +02:00
parser . add_argument (
2018-12-23 21:53:25 +01:00
' --port ' , ' -p ' ,
help = ' Serial port device ' ,
default = os . environ . get ( ' ESPTOOL_PORT ' , ' /dev/ttyUSB0 ' ) )
2016-04-12 18:07:25 +02:00
parser . add_argument (
2018-12-23 21:53:25 +01:00
' --baud ' , ' -b ' ,
help = ' Serial port baud rate used when flashing/reading ' ,
type = arg_auto_int ,
default = os . environ . get ( ' ESPTOOL_BAUD ' , ESPROM . ESP_ROM_BAUD ) )
2016-04-12 18:07:25 +02:00
subparsers = parser . add_subparsers (
2018-12-23 21:53:25 +01:00
dest = ' operation ' ,
help = ' Run esptool {command} -h for additional help ' )
2016-04-12 18:07:25 +02:00
parser_load_ram = subparsers . add_parser (
2018-12-23 21:53:25 +01:00
' load_ram ' ,
help = ' Download an image to RAM and execute ' )
parser_load_ram . add_argument ( ' filename ' , help = ' Firmware image ' )
2016-04-12 18:07:25 +02:00
parser_dump_mem = subparsers . add_parser (
2018-12-23 21:53:25 +01:00
' dump_mem ' ,
help = ' Dump arbitrary memory to disk ' )
parser_dump_mem . add_argument ( ' address ' , help = ' Base address ' , type = arg_auto_int )
parser_dump_mem . add_argument ( ' size ' , help = ' Size of region to dump ' , type = arg_auto_int )
parser_dump_mem . add_argument ( ' filename ' , help = ' Name of binary dump ' )
2016-04-12 18:07:25 +02:00
parser_read_mem = subparsers . add_parser (
2018-12-23 21:53:25 +01:00
' read_mem ' ,
help = ' Read arbitrary memory location ' )
parser_read_mem . add_argument ( ' address ' , help = ' Address to read ' , type = arg_auto_int )
2016-04-12 18:07:25 +02:00
parser_write_mem = subparsers . add_parser (
2018-12-23 21:53:25 +01:00
' write_mem ' ,
help = ' Read-modify-write to arbitrary memory location ' )
parser_write_mem . add_argument ( ' address ' , help = ' Address to write ' , type = arg_auto_int )
parser_write_mem . add_argument ( ' value ' , help = ' Value ' , type = arg_auto_int )
parser_write_mem . add_argument ( ' mask ' , help = ' Mask of bits to write ' , type = arg_auto_int )
def add_spi_flash_subparsers ( parent ) :
""" Add common parser arguments for SPI flash properties """
parent . add_argument ( ' --flash_freq ' , ' -ff ' , help = ' SPI Flash frequency ' ,
choices = [ ' 40m ' , ' 26m ' , ' 20m ' , ' 80m ' ] ,
default = os . environ . get ( ' ESPTOOL_FF ' , ' 40m ' ) )
parent . add_argument ( ' --flash_mode ' , ' -fm ' , help = ' SPI Flash mode ' ,
choices = [ ' qio ' , ' qout ' , ' dio ' , ' dout ' ] ,
default = os . environ . get ( ' ESPTOOL_FM ' , ' qio ' ) )
parent . add_argument ( ' --flash_size ' , ' -fs ' , help = ' SPI Flash size in Mbit ' , type = str . lower ,
choices = [ ' 4m ' , ' 2m ' , ' 8m ' , ' 16m ' , ' 32m ' , ' 16m-c1 ' , ' 32m-c1 ' , ' 32m-c2 ' , ' 64m ' , ' 128m ' ] ,
default = os . environ . get ( ' ESPTOOL_FS ' , ' 4m ' ) )
2016-04-12 18:07:25 +02:00
parser_write_flash = subparsers . add_parser (
2018-12-23 21:53:25 +01:00
' write_flash ' ,
help = ' Write a binary blob to flash ' )
parser_write_flash . add_argument ( ' addr_filename ' , metavar = ' <address> <filename> ' , help = ' Address followed by binary filename, separated by space ' ,
action = AddrFilenamePairAction )
add_spi_flash_subparsers ( parser_write_flash )
parser_write_flash . add_argument ( ' --no-progress ' , ' -p ' , help = ' Suppress progress output ' , action = " store_true " )
parser_write_flash . add_argument ( ' --verify ' , help = ' Verify just-written data (only necessary if very cautious, data is already CRCed ' , action = ' store_true ' )
subparsers . add_parser (
' run ' ,
help = ' Run application code in flash ' )
2016-04-12 18:07:25 +02:00
parser_image_info = subparsers . add_parser (
2018-12-23 21:53:25 +01:00
' image_info ' ,
help = ' Dump headers from an application image ' )
parser_image_info . add_argument ( ' filename ' , help = ' Image file to parse ' )
2016-04-12 18:07:25 +02:00
parser_make_image = subparsers . add_parser (
2018-12-23 21:53:25 +01:00
' make_image ' ,
help = ' Create an application image from binary files ' )
parser_make_image . add_argument ( ' output ' , help = ' Output image file ' )
parser_make_image . add_argument ( ' --segfile ' , ' -f ' , action = ' append ' , help = ' Segment input file ' )
parser_make_image . add_argument ( ' --segaddr ' , ' -a ' , action = ' append ' , help = ' Segment base address ' , type = arg_auto_int )
parser_make_image . add_argument ( ' --entrypoint ' , ' -e ' , help = ' Address of entry point ' , type = arg_auto_int , default = 0 )
2016-04-12 18:07:25 +02:00
parser_elf2image = subparsers . add_parser (
2018-12-23 21:53:25 +01:00
' elf2image ' ,
help = ' Create an application image from ELF file ' )
parser_elf2image . add_argument ( ' input ' , help = ' Input ELF file ' )
parser_elf2image . add_argument ( ' --output ' , ' -o ' , help = ' Output filename prefix (for version 1 image), or filename (for version 2 single image) ' , type = str )
parser_elf2image . add_argument ( ' --version ' , ' -e ' , help = ' Output image version ' , choices = [ ' 1 ' , ' 2 ' ] , default = ' 1 ' )
add_spi_flash_subparsers ( parser_elf2image )
2016-04-12 18:07:25 +02:00
2018-12-23 21:53:25 +01:00
subparsers . add_parser (
' read_mac ' ,
help = ' Read MAC address from OTP ROM ' )
2016-04-12 18:07:25 +02:00
2018-12-23 21:53:25 +01:00
subparsers . add_parser (
' chip_id ' ,
help = ' Read Chip ID from OTP ROM ' )
2016-04-12 18:07:25 +02:00
2018-12-23 21:53:25 +01:00
subparsers . add_parser (
' flash_id ' ,
help = ' Read SPI flash manufacturer and device ID ' )
2016-04-12 18:07:25 +02:00
2018-12-23 21:53:25 +01:00
parser_read_flash = subparsers . add_parser (
' read_flash ' ,
help = ' Read SPI flash content ' )
parser_read_flash . add_argument ( ' address ' , help = ' Start address ' , type = arg_auto_int )
parser_read_flash . add_argument ( ' size ' , help = ' Size of region to dump ' , type = arg_auto_int )
parser_read_flash . add_argument ( ' filename ' , help = ' Name of binary dump ' )
parser_read_flash . add_argument ( ' --no-progress ' , ' -p ' , help = ' Suppress progress output ' , action = " store_true " )
parser_verify_flash = subparsers . add_parser (
' verify_flash ' ,
help = ' Verify a binary blob against flash ' )
parser_verify_flash . add_argument ( ' addr_filename ' , help = ' Address and binary file to verify there, separated by space ' ,
action = AddrFilenamePairAction )
parser_verify_flash . add_argument ( ' --diff ' , ' -d ' , help = ' Show differences ' ,
choices = [ ' no ' , ' yes ' ] , default = ' no ' )
subparsers . add_parser (
' erase_flash ' ,
help = ' Perform Chip Erase on SPI flash ' )
subparsers . add_parser (
' version ' , help = ' Print esptool version ' )
# internal sanity check - every operation matches a module function of the same name
for operation in subparsers . choices . keys ( ) :
assert operation in globals ( ) , " %s should be a module function " % operation
2016-04-12 18:07:25 +02:00
2018-12-23 21:53:25 +01:00
args = parser . parse_args ( )
2016-04-12 18:07:25 +02:00
2018-12-23 21:53:25 +01:00
print ' esptool.py v %s ' % __version__
2016-04-12 18:07:25 +02:00
2018-12-23 21:53:25 +01:00
# operation function can take 1 arg (args), 2 args (esp, arg)
# or be a member function of the ESPROM class.
2016-04-12 18:07:25 +02:00
2018-12-23 21:53:25 +01:00
operation_func = globals ( ) [ args . operation ]
operation_args , _ , _ , _ = inspect . getargspec ( operation_func )
if operation_args [ 0 ] == ' esp ' : # operation function takes an ESPROM connection object
initial_baud = min ( ESPROM . ESP_ROM_BAUD , args . baud ) # don't sync faster than the default baud rate
esp = ESPROM ( args . port , initial_baud )
esp . connect ( )
operation_func ( esp , args )
else :
operation_func ( args )
2016-04-12 18:07:25 +02:00
2018-12-23 21:53:25 +01:00
class AddrFilenamePairAction ( argparse . Action ) :
""" Custom parser class for the address/filename pairs passed as arguments """
def __init__ ( self , option_strings , dest , nargs = ' + ' , * * kwargs ) :
super ( AddrFilenamePairAction , self ) . __init__ ( option_strings , dest , nargs , * * kwargs )
2016-04-12 18:07:25 +02:00
2018-12-23 21:53:25 +01:00
def __call__ ( self , parser , namespace , values , option_string = None ) :
# validate pair arguments
pairs = [ ]
for i in range ( 0 , len ( values ) , 2 ) :
try :
address = int ( values [ i ] , 0 )
except ValueError as e :
raise argparse . ArgumentError ( self , ' Address " %s " must be a number ' % values [ i ] )
try :
argfile = open ( values [ i + 1 ] , ' rb ' )
except IOError as e :
raise argparse . ArgumentError ( self , e )
except IndexError :
raise argparse . ArgumentError ( self , ' Must be pairs of an address and the binary filename to write there ' )
pairs . append ( ( address , argfile ) )
setattr ( namespace , self . dest , pairs )
# This is "wrapped" stub_flasher.c, to be loaded using run_stub.
_CESANTA_FLASHER_STUB = """ \
{ " code_start " : 1074790404 , " code " : " 080000601C000060000000601000006031FCFF71FCFF \
81 FCFFC02000680332D218C020004807404074DCC48608005823C0200098081BA5A9239245005803 \
1 B555903582337350129230B446604DFC6F3FF21EEFFC0200069020DF0000000010078480040004A \
0040 B449004012C1F0C921D911E901DD0209312020B4ED033C2C56C2073020B43C3C56420701F5FF \
C000003C4C569206CD0EEADD860300202C4101F1FFC0000056A204C2DCF0C02DC0CC6CCAE2D1EAFF \
0606002030 F456D3FD86FBFF00002020F501E8FFC00000EC82D0CCC0C02EC0C73DEB2ADC46030020 \
2 C4101E1FFC00000DC42C2DCF0C02DC056BCFEC602003C5C8601003C6C4600003C7C08312D0CD811 \
C821E80112C1100DF0000C180000140010400C0000607418000064180000801800008C1800008418 \
0000881800009018000018980040880 F0040A80F0040349800404C4A0040740F0040800F0040980F \
00400099004012 C1E091F5FFC961CD0221EFFFE941F9310971D9519011C01A223902E2D1180C0222 \
6E1 D21E4FF31E9FF2AF11A332D0F42630001EAFFC00000C030B43C2256A31621E1FF1A2228022030 \
B43C3256B31501ADFFC00000DD023C4256ED1431D6FF4D010C52D90E192E126E0101DDFFC0000021 \
D2FF32A101C020004802303420C0200039022C0201D7FFC00000463300000031CDFF1A333803D023 \
C03199FF27B31ADC7F31CBFF1A3328030198FFC0000056C20E2193FF2ADD060E000031C6FF1A3328 \
030191 FFC0000056820DD2DD10460800000021BEFF1A2228029CE231BCFFC020F51A33290331BBFF \
C02C411A332903C0F0F4222E1D22D204273D9332A3FFC02000280E27B3F721ABFF381E1A2242A400 \
01 B5FFC00000381E2D0C42A40001B3FFC0000056120801B2FFC00000C02000280EC2DC0422D2FCC0 \
2000290E01 ADFFC00000222E1D22D204226E1D281E22D204E7B204291E860000126E012198FF32A0 \
042 A21C54C003198FF222E1D1A33380337B202C6D6FF2C02019FFFC000002191FF318CFF1A223A31 \
019 CFFC00000218DFF1C031A22C549000C02060300003C528601003C624600003C72918BFF9A1108 \
71 C861D851E841F83112C1200DF00010000068100000581000007010000074100000781000007C10 \
0000801000001 C4B0040803C004091FDFF12C1E061F7FFC961E941F9310971D9519011C01A662906 \
21 F3FFC2D1101A22390231F2FF0C0F1A33590331EAFFF26C1AED045C2247B3028636002D0C016DFF \
C0000021E5FF41EAFF2A611A4469040622000021E4FF1A222802F0D2C0D7BE01DD0E31E0FF4D0D1A \
3328033 D0101E2FFC00000561209D03D2010212001DFFFC000004D0D2D0C3D01015DFFC0000041D5 \
FFDAFF1A444804D0648041D2FF1A4462640061D1FF106680622600673F1331D0FF10338028030C43 \
853 A002642164613000041CAFF222C1A1A444804202FC047328006F6FF222C1A273F3861C2FF222C \
1 A1A6668066732B921BDFF3D0C1022800148FFC0000021BAFF1C031A2201BFFFC000000C02460300 \
5 C3206020000005C424600005C5291B7FF9A110871C861D851E841F83112C1200DF0B0100000C010 \
0000 D010000012C1E091FEFFC961D951E9410971F931CD039011C0ED02DD0431A1FF9C1422A06247 \
B302062D0021F4FF1A22490286010021F1FF1A223902219CFF2AF12D0F011FFFC00000461C0022D1 \
10011 CFFC0000021E9FFFD0C1A222802C7B20621E6FF1A22F8022D0E3D014D0F0195FFC000008C52 \
22 A063C6180000218BFF3D01102280F04F200111FFC00000AC7D22D1103D014D0F010DFFC0000021 \
D6FF32D110102280010EFFC0000021D3FF1C031A220185FFC00000FAEEF0CCC056ACF821CDFF317A \
FF1A223A310105FFC0000021C9FF1C031A22017CFFC000002D0C91C8FF9A110871C861D851E841F8 \
3112 C1200DF0000200600000001040020060FFFFFF0012C1E00C02290131FAFF21FAFF026107C961 \
C02000226300C02000C80320CC10564CFF21F5FFC02000380221F4FF20231029010C432D010163FF \
C0000008712D0CC86112C1200DF00080FE3F8449004012C1D0C9A109B17CFC22C1110C13C51C0026 \
1202463000220111 C24110B68202462B0031F5FF3022A02802A002002D011C03851A0066820A2801 \
32210105 A6FF0607003C12C60500000010212032A01085180066A20F2221003811482105B3FF2241 \
10861 A004C1206FDFF2D011C03C5160066B20E280138114821583185CFFF06F7FF005C1286F5FF00 \
10212032 A01085140066A20D2221003811482105E1FF06EFFF0022A06146EDFF45F0FFC6EBFF0000 \
01 D2FFC0000006E9FF000C022241100C1322C110C50F00220111060600000022C1100C13C50E0022 \
011132 C2FA303074B6230206C8FF08B1C8A112C1300DF0000000000010404F484149007519031027 \
000000110040 A8100040BC0F0040583F0040CC2E00401CE20040D83900408000004021F4FF12C1E0 \
C961C80221F2FF097129010C02D951C91101F4FFC0000001F3FFC00000AC2C22A3E801F2FFC00000 \
21 EAFFC031412A233D0C01EFFFC000003D0222A00001EDFFC00000C1E4FF2D0C01E8FFC000002D01 \
32 A004450400C5E7FFDD022D0C01E3FFC00000666D1F4B2131DCFF4600004B22C0200048023794F5 \
31 D9FFC0200039023DF08601000001DCFFC000000871C861D85112C1200DF000000012C1F0026103 \
01 EAFEC00000083112C1100DF000643B004012C1D0E98109B1C9A1D991F97129013911E2A0C001FA \
FFC00000CD02E792F40C0DE2A0C0F2A0DB860D00000001F4FFC00000204220E71240F7921C226102 \
01 EFFFC0000052A0DC482157120952A0DD571205460500004D0C3801DA234242001BDD3811379DC5 \
C6000000000C0DC2A0C001E3FFC00000C792F608B12D0DC8A1D891E881F87112C1300DF00000 " , " \
entry " : 1074792180, " num_params " : 1, " params_start " : 1074790400, " data " : " FE0510 \
401 A0610403B0610405A0610407A061040820610408C0610408C061040 " , " data_start " : 10736 \
43520 }
"""
2016-04-12 18:07:25 +02:00
2018-12-23 21:53:25 +01:00
if __name__ == ' __main__ ' :
try :
main ( )
except FatalError as e :
print ' \n A fatal error occurred: %s ' % e
sys . exit ( 2 )