1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930193119321933193419351936193719381939194019411942194319441945194619471948194919501951195219531954195519561957195819591960196119621963196419651966196719681969197019711972197319741975197619771978197919801981198219831984198519861987198819891990199119921993199419951996199719981999200020012002200320042005200620072008200920102011201220132014201520162017201820192020202120222023202420252026202720282029203020312032203320342035203620372038203920402041204220432044204520462047204820492050205120522053205420552056205720582059206020612062206320642065206620672068206920702071207220732074207520762077207820792080208120822083208420852086208720882089209020912092209320942095209620972098209921002101210221032104210521062107210821092110211121122113211421152116211721182119212021212122212321242125212621272128212921302131213221332134213521362137213821392140214121422143214421452146214721482149215021512152215321542155215621572158215921602161216221632164216521662167216821692170217121722173217421752176217721782179218021812182218321842185218621872188218921902191219221932194219521962197219821992200220122022203220422052206220722082209221022112212221322142215221622172218221922202221222222232224222522262227222822292230223122322233223422352236223722382239224022412242224322442245224622472248224922502251225222532254225522562257225822592260226122622263226422652266226722682269227022712272227322742275227622772278227922802281228222832284228522862287228822892290229122922293229422952296229722982299230023012302230323042305230623072308230923102311231223132314231523162317231823192320232123222323232423252326232723282329233023312332233323342335233623372338233923402341234223432344234523462347234823492350235123522353235423552356235723582359236023612362236323642365236623672368236923702371237223732374237523762377237823792380238123822383238423852386238723882389239023912392239323942395239623972398239924002401240224032404240524062407240824092410241124122413241424152416241724182419242024212422242324242425242624272428242924302431243224332434243524362437243824392440244124422443244424452446244724482449245024512452245324542455245624572458245924602461246224632464246524662467246824692470247124722473247424752476247724782479248024812482248324842485248624872488248924902491249224932494249524962497249824992500250125022503250425052506250725082509251025112512251325142515251625172518251925202521252225232524252525262527252825292530253125322533253425352536253725382539254025412542254325442545254625472548254925502551255225532554255525562557255825592560256125622563256425652566256725682569257025712572257325742575257625772578257925802581258225832584258525862587258825892590259125922593259425952596259725982599260026012602260326042605260626072608260926102611261226132614261526162617261826192620262126222623262426252626262726282629263026312632263326342635263626372638263926402641264226432644264526462647264826492650265126522653265426552656265726582659266026612662266326642665266626672668266926702671267226732674267526762677267826792680268126822683268426852686268726882689269026912692269326942695269626972698269927002701270227032704270527062707270827092710271127122713271427152716271727182719272027212722272327242725272627272728272927302731273227332734273527362737273827392740274127422743274427452746274727482749275027512752275327542755275627572758275927602761276227632764276527662767276827692770277127722773277427752776277727782779278027812782278327842785278627872788278927902791279227932794279527962797279827992800280128022803280428052806280728082809281028112812281328142815281628172818281928202821282228232824282528262827282828292830283128322833283428352836283728382839284028412842284328442845284628472848284928502851285228532854285528562857285828592860286128622863286428652866286728682869287028712872287328742875287628772878287928802881288228832884288528862887288828892890289128922893289428952896289728982899290029012902290329042905290629072908290929102911291229132914291529162917291829192920292129222923292429252926292729282929293029312932293329342935293629372938293929402941294229432944294529462947294829492950295129522953295429552956295729582959 |
- #!/usr/bin/env python
- #
- # ESP8266 & ESP32 ROM Bootloader Utility
- # Copyright (C) 2014-2016 Fredrik Ahlberg, Angus Gratton, Espressif Systems (Shanghai) PTE LTD, other contributors as noted.
- # https://github.com/espressif/esptool
- #
- # 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.
- from __future__ import division, print_function
- import argparse
- import base64
- import binascii
- import copy
- import hashlib
- import inspect
- import io
- import os
- import shlex
- import struct
- import sys
- import time
- import zlib
- import string
- 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:
- if "serialization" in serial.__doc__ and "deserialization" in serial.__doc__:
- raise ImportError("""
- esptool.py depends on pyserial, but there is a conflict with a currently installed package named 'serial'.
- You may be able to work around this by 'pip uninstall serial; pip install pyserial' \
- but this may break other installed Python software that depends on 'serial'.
- There is no good fix for this right now, apart from configuring virtualenvs. \
- See https://github.com/espressif/esptool/issues/269#issuecomment-385298196 for discussion of the underlying issue(s).""")
- 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.6"
- MAX_UINT32 = 0xffffffff
- MAX_UINT24 = 0xffffff
- DEFAULT_TIMEOUT = 3 # timeout for most flash operations
- START_FLASH_TIMEOUT = 20 # timeout for starting flash (may perform erase)
- CHIP_ERASE_TIMEOUT = 120 # timeout for full chip erase
- MAX_TIMEOUT = CHIP_ERASE_TIMEOUT * 2 # longest any command can run
- SYNC_TIMEOUT = 0.1 # timeout for syncing with bootloader
- MD5_TIMEOUT_PER_MB = 8 # timeout (per megabyte) for calculating md5sum
- ERASE_REGION_TIMEOUT_PER_MB = 30 # timeout (per megabyte) for erasing a region
- MEM_END_ROM_TIMEOUT = 0.05 # special short timeout for ESP_MEM_END, as it may never respond
- DEFAULT_SERIAL_WRITE_TIMEOUT = 10 # timeout for serial port write
- def timeout_per_mb(seconds_per_mb, size_bytes):
- """ Scales timeouts which are size-specific """
- result = seconds_per_mb * (size_bytes / 1e6)
- if result < DEFAULT_TIMEOUT:
- return DEFAULT_TIMEOUT
- return result
- DETECTED_FLASH_SIZES = {0x12: '256KB', 0x13: '512KB', 0x14: '1MB',
- 0x15: '2MB', 0x16: '4MB', 0x17: '8MB', 0x18: '16MB'}
- def check_supported_function(func, check_func):
- """
- Decorator implementation that wraps a check around an ESPLoader
- bootloader function to check if it's supported.
- This is used to capture the multidimensional differences in
- functionality between the ESP8266 & ESP32 ROM loaders, and the
- software stub that runs on both. Not possible to do this cleanly
- via inheritance alone.
- """
- def inner(*args, **kwargs):
- obj = args[0]
- if check_func(obj):
- return func(*args, **kwargs)
- else:
- raise NotImplementedInROMError(obj, func)
- return inner
- def stub_function_only(func):
- """ Attribute for a function only supported in the software stub loader """
- return check_supported_function(func, lambda o: o.IS_STUB)
- def stub_and_esp32_function_only(func):
- """ Attribute for a function only supported by software stubs or ESP32 ROM """
- return check_supported_function(func, lambda o: o.IS_STUB or o.CHIP_NAME == "ESP32")
- PYTHON2 = sys.version_info[0] < 3 # True if on pre-Python 3
- # Function to return nth byte of a bitstring
- # Different behaviour on Python 2 vs 3
- if PYTHON2:
- def byte(bitstr, index):
- return ord(bitstr[index])
- else:
- def byte(bitstr, index):
- return bitstr[index]
- # Provide a 'basestring' class on Python 3
- try:
- basestring
- except NameError:
- basestring = str
- def esp8266_function_only(func):
- """ Attribute for a function only supported on ESP8266 """
- return check_supported_function(func, lambda o: o.CHIP_NAME == "ESP8266")
- class ESPLoader(object):
- """ Base class providing access to ESP ROM & software stub bootloaders.
- Subclasses provide ESP8266 & ESP32 specific functionality.
- Don't instantiate this base class directly, either instantiate a subclass or
- call ESPLoader.detect_chip() which will interrogate the chip and return the
- appropriate subclass instance.
- """
- CHIP_NAME = "Espressif device"
- IS_STUB = False
- DEFAULT_PORT = "/dev/ttyUSB0"
- # Commands supported by ESP8266 ROM bootloader
- 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
- # Some comands supported by ESP32 ROM bootloader (or -8266 w/ stub)
- ESP_SPI_SET_PARAMS = 0x0B
- ESP_SPI_ATTACH = 0x0D
- ESP_CHANGE_BAUDRATE = 0x0F
- ESP_FLASH_DEFL_BEGIN = 0x10
- ESP_FLASH_DEFL_DATA = 0x11
- ESP_FLASH_DEFL_END = 0x12
- ESP_SPI_FLASH_MD5 = 0x13
- # Some commands supported by stub only
- ESP_ERASE_FLASH = 0xD0
- ESP_ERASE_REGION = 0xD1
- ESP_READ_FLASH = 0xD2
- ESP_RUN_USER_CODE = 0xD3
- # Maximum block sized for RAM and Flash writes, respectively.
- ESP_RAM_BLOCK = 0x1800
- FLASH_WRITE_SIZE = 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
- # Flash sector size, minimum unit of erase.
- FLASH_SECTOR_SIZE = 0x1000
- UART_DATA_REG_ADDR = 0x60000078
- # Memory addresses
- IROM_MAP_START = 0x40200000
- IROM_MAP_END = 0x40300000
- # The number of bytes in the UART response that signify command status
- STATUS_BYTES_LENGTH = 2
- def __init__(self, port=DEFAULT_PORT, baud=ESP_ROM_BAUD, trace_enabled=False):
- """Base constructor for ESPLoader bootloader interaction
- Don't call this constructor, either instantiate ESP8266ROM
- or ESP32ROM, or use ESPLoader.detect_chip().
- This base class has all of the instance methods for bootloader
- functionality supported across various chips & stub
- loaders. Subclasses replace the functions they don't support
- with ones which throw NotImplementedInROMError().
- """
- if isinstance(port, basestring):
- self._port = serial.serial_for_url(port)
- else:
- self._port = port
- self._slip_reader = slip_reader(self._port, self.trace)
- # 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/espressif/esptool/issues/44#issuecomment-107094446
- self._set_port_baudrate(baud)
- self._trace_enabled = trace_enabled
- # set write timeout, to prevent esptool blocked at write forever.
- 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:
- self._port.baudrate = baud
- except IOError:
- raise FatalError("Failed to set baud rate %d. The driver may not support this rate." % baud)
- @staticmethod
- def detect_chip(port=DEFAULT_PORT, baud=ESP_ROM_BAUD, connect_mode='default_reset', trace_enabled=False):
- """ Use serial access to detect the chip type.
- We use the UART's datecode register for this, it's mapped at
- the same address on ESP8266 & ESP32 so we can use one
- memory read and compare to the datecode register for each chip
- type.
- This routine automatically performs ESPLoader.connect() (passing
- connect_mode parameter) as part of querying the chip.
- """
- detect_port = ESPLoader(port, baud, trace_enabled=trace_enabled)
- detect_port.connect(connect_mode)
- try:
- print('Detecting chip type...', end='')
- sys.stdout.flush()
- date_reg = detect_port.read_reg(ESPLoader.UART_DATA_REG_ADDR)
- for cls in [ESP8266ROM, ESP32ROM]:
- if date_reg == cls.DATE_REG_VALUE:
- # don't connect a second time
- inst = cls(detect_port._port, baud, trace_enabled=trace_enabled)
- print(' %s' % inst.CHIP_NAME, end='')
- return inst
- finally:
- print('') # end line
- raise FatalError("Unexpected UART datecode value 0x%08x. Failed to autodetect chip type." % date_reg)
- """ Read a SLIP packet from the serial port """
- def read(self):
- return next(self._slip_reader)
- """ Write bytes to the serial port while performing SLIP escaping """
- def write(self, packet):
- buf = b'\xc0' \
- + (packet.replace(b'\xdb',b'\xdb\xdd').replace(b'\xc0',b'\xdb\xdc')) \
- + b'\xc0'
- self.trace("Write %d bytes: %s", len(buf), HexFormatter(buf))
- self._port.write(buf)
- def trace(self, message, *format_args):
- if self._trace_enabled:
- now = time.time()
- try:
- delta = now - self._last_trace
- except AttributeError:
- delta = 0.0
- self._last_trace = now
- prefix = "TRACE +%.3f " % delta
- print(prefix + (message % format_args))
- """ Calculate checksum of a blob, as it is defined by the ROM """
- @staticmethod
- def checksum(data, state=ESP_CHECKSUM_MAGIC):
- for b in data:
- if type(b) is int: # python 2/3 compat
- state ^= b
- else:
- state ^= ord(b)
- return state
- """ Send a request and read the response """
- def command(self, op=None, data=b"", chk=0, wait_response=True, timeout=DEFAULT_TIMEOUT):
- saved_timeout = self._port.timeout
- new_timeout = min(timeout, MAX_TIMEOUT)
- if new_timeout != saved_timeout:
- self._port.timeout = new_timeout
- try:
- if op is not None:
- self.trace("command op=0x%02x data len=%s wait_response=%d timeout=%.3f data=%s",
- op, len(data), 1 if wait_response else 0, timeout, HexFormatter(data))
- pkt = struct.pack(b'<BBHI', 0x00, op, len(data), chk) + data
- self.write(pkt)
- if not wait_response:
- return
- # 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 range(100):
- p = self.read()
- if len(p) < 8:
- continue
- (resp, op_ret, len_ret, val) = struct.unpack('<BBHI', p[:8])
- if resp != 1:
- continue
- data = p[8:]
- if op is None or op_ret == op:
- return val, data
- finally:
- if new_timeout != saved_timeout:
- self._port.timeout = saved_timeout
- raise FatalError("Response doesn't match request")
- def check_command(self, op_description, op=None, data=b'', chk=0, timeout=DEFAULT_TIMEOUT):
- """
- Execute a command with 'command', check the result code and throw an appropriate
- FatalError if it fails.
- Returns the "result" of a successful command.
- """
- val, data = self.command(op, data, chk, timeout=timeout)
- # things are a bit weird here, bear with us
- # the status bytes are the last 2/4 bytes in the data (depending on chip)
- if len(data) < self.STATUS_BYTES_LENGTH:
- raise FatalError("Failed to %s. Only got %d byte status response." % (op_description, len(data)))
- status_bytes = data[-self.STATUS_BYTES_LENGTH:]
- # we only care if the first one is non-zero. If it is, the second byte is a reason.
- if byte(status_bytes, 0) != 0:
- raise FatalError.WithResult('Failed to %s' % op_description, status_bytes)
- # if we had more data than just the status bytes, return it as the result
- # (this is used by the md5sum command, maybe other commands?)
- if len(data) > self.STATUS_BYTES_LENGTH:
- return data[:-self.STATUS_BYTES_LENGTH]
- else: # otherwise, just return the 'val' field which comes from the reply header (this is used by read_reg)
- return val
- def flush_input(self):
- self._port.flushInput()
- self._slip_reader = slip_reader(self._port, self.trace)
- def sync(self):
- self.command(self.ESP_SYNC, b'\x07\x07\x12\x20' + 32 * b'\x55',
- timeout=SYNC_TIMEOUT)
- for i in range(7):
- self.command()
- def _setDTR(self, state):
- self._port.setDTR(state)
- def _setRTS(self, state):
- self._port.setRTS(state)
- # Work-around for adapters on Windows using the usbser.sys driver:
- # generate a dummy change to DTR so that the set-control-line-state
- # request is sent with the updated RTS state and the same DTR state
- self._port.setDTR(self._port.dtr)
- def _connect_attempt(self, mode='default_reset', esp32r0_delay=False):
- """ A single connection attempt, with esp32r0 workaround options """
- # esp32r0_delay is a workaround for bugs with the most common auto reset
- # circuit and Windows, if the EN pin on the dev board does not have
- # enough capacitance.
- #
- # Newer dev boards shouldn't have this problem (higher value capacitor
- # on the EN pin), and ESP32 revision 1 can't use this workaround as it
- # relies on a silicon bug.
- #
- # Details: https://github.com/espressif/esptool/issues/136
- last_error = None
- # If we're doing no_sync, we're likely communicating as a pass through
- # with an intermediate device to the ESP32
- if mode == "no_reset_no_sync":
- return last_error
- # issue reset-to-bootloader:
- # RTS = either CH_PD/EN or nRESET (both active low = chip in reset
- # DTR = GPIO0 (active low = boot to flasher)
- #
- # DTR & RTS are active low signals,
- # ie True = pin @ 0V, False = pin @ VCC.
- if mode != 'no_reset':
- self._setDTR(False) # IO0=HIGH
- self._setRTS(True) # EN=LOW, chip in reset
- time.sleep(0.1)
- if esp32r0_delay:
- # Some chips are more likely to trigger the esp32r0
- # watchdog reset silicon bug if they're held with EN=LOW
- # for a longer period
- time.sleep(1.2)
- self._setDTR(True) # IO0=LOW
- self._setRTS(False) # EN=HIGH, chip out of reset
- if esp32r0_delay:
- # Sleep longer after reset.
- # This workaround only works on revision 0 ESP32 chips,
- # it exploits a silicon bug spurious watchdog reset.
- time.sleep(0.4) # allow watchdog reset to occur
- time.sleep(0.05)
- self._setDTR(False) # IO0=HIGH, done
- for _ in range(5):
- try:
- self.flush_input()
- self._port.flushOutput()
- self.sync()
- return None
- except FatalError as e:
- if esp32r0_delay:
- print('_', end='')
- else:
- print('.', end='')
- sys.stdout.flush()
- time.sleep(0.05)
- last_error = e
- return last_error
- def connect(self, mode='default_reset'):
- """ Try connecting repeatedly until successful, or giving up """
- print('Connecting...', end='')
- sys.stdout.flush()
- last_error = None
- try:
- for _ in range(7):
- last_error = self._connect_attempt(mode=mode, esp32r0_delay=False)
- if last_error is None:
- return
- last_error = self._connect_attempt(mode=mode, esp32r0_delay=True)
- if last_error is None:
- return
- finally:
- print('') # end 'Connecting...' line
- raise FatalError('Failed to connect to %s: %s' % (self.CHIP_NAME, last_error))
- """ Read memory address in target """
- def read_reg(self, addr):
- # we don't call check_command here because read_reg() function is called
- # when detecting chip type, and the way we check for success (STATUS_BYTES_LENGTH) is different
- # for different chip types (!)
- val, data = self.command(self.ESP_READ_REG, struct.pack('<I', addr))
- if byte(data, 0) != 0:
- raise FatalError.WithResult("Failed to read register address %08x" % addr, data)
- return val
- """ Write to memory address in target """
- def write_reg(self, addr, value, mask=0xFFFFFFFF, delay_us=0):
- return self.check_command("write target memory", self.ESP_WRITE_REG,
- struct.pack('<IIII', addr, value, mask, delay_us))
- """ Start downloading an application image to RAM """
- def mem_begin(self, size, blocks, blocksize, offset):
- if self.IS_STUB: # check we're not going to overwrite a running stub with this data
- stub = self.STUB_CODE
- load_start = offset
- load_end = offset + size
- for (start, end) in [(stub["data_start"], stub["data_start"] + len(stub["data"])),
- (stub["text_start"], stub["text_start"] + len(stub["text"]))]:
- if load_start < end and load_end > start:
- raise FatalError(("Software loader is resident at 0x%08x-0x%08x. " +
- "Can't load binary at overlapping address range 0x%08x-0x%08x. " +
- "Either change binary loading address, or use the --no-stub " +
- "option to disable the software loader.") % (start, end, load_start, load_end))
- return self.check_command("enter RAM download mode", self.ESP_MEM_BEGIN,
- struct.pack('<IIII', size, blocks, blocksize, offset))
- """ Send a block of an image to RAM """
- def mem_block(self, data, seq):
- return self.check_command("write to target RAM", self.ESP_MEM_DATA,
- struct.pack('<IIII', len(data), seq, 0, 0) + data,
- self.checksum(data))
- """ Leave download mode and run the application """
- def mem_finish(self, entrypoint=0):
- # Sending ESP_MEM_END usually sends a correct response back, however sometimes
- # (with ROM loader) the executed code may reset the UART or change the baud rate
- # before the transmit FIFO is empty. So in these cases we set a short timeout and
- # ignore errors.
- timeout = DEFAULT_TIMEOUT if self.IS_STUB else MEM_END_ROM_TIMEOUT
- data = struct.pack('<II', int(entrypoint == 0), entrypoint)
- try:
- return self.check_command("leave RAM download mode", self.ESP_MEM_END,
- data=data, timeout=timeout)
- except FatalError:
- if self.IS_STUB:
- raise
- pass
- """ Start downloading to Flash (performs an erase)
- Returns number of blocks (of size self.FLASH_WRITE_SIZE) to write.
- """
- def flash_begin(self, size, offset):
- num_blocks = (size + self.FLASH_WRITE_SIZE - 1) // self.FLASH_WRITE_SIZE
- erase_size = self.get_erase_size(offset, size)
- t = time.time()
- if self.IS_STUB:
- timeout = DEFAULT_TIMEOUT
- else:
- timeout = timeout_per_mb(ERASE_REGION_TIMEOUT_PER_MB, size) # ROM performs the erase up front
- self.check_command("enter Flash download mode", self.ESP_FLASH_BEGIN,
- struct.pack('<IIII', erase_size, num_blocks, self.FLASH_WRITE_SIZE, offset),
- timeout=timeout)
- if size != 0 and not self.IS_STUB:
- print("Took %.2fs to erase flash block" % (time.time() - t))
- return num_blocks
- """ Write block to flash """
- def flash_block(self, data, seq, timeout=DEFAULT_TIMEOUT):
- self.check_command("write to target Flash after seq %d" % seq,
- self.ESP_FLASH_DATA,
- struct.pack('<IIII', len(data), seq, 0, 0) + data,
- self.checksum(data),
- timeout=timeout)
- """ Leave flash mode and run/reboot """
- def flash_finish(self, reboot=False):
- pkt = struct.pack('<I', int(not reboot))
- # stub sends a reply to this command
- self.check_command("leave Flash mode", self.ESP_FLASH_END, pkt)
- """ Run application code in flash """
- def run(self, reboot=False):
- # Fake flash begin immediately followed by flash end
- self.flash_begin(0, 0)
- self.flash_finish(reboot)
- """ Read SPI flash manufacturer and device id """
- def flash_id(self):
- SPIFLASH_RDID = 0x9F
- return self.run_spiflash_command(SPIFLASH_RDID, b"", 24)
- def parse_flash_size_arg(self, arg):
- try:
- return self.FLASH_SIZES[arg]
- except KeyError:
- raise FatalError("Flash size '%s' is not supported by this chip type. Supported sizes: %s"
- % (arg, ", ".join(self.FLASH_SIZES.keys())))
- def run_stub(self, stub=None):
- if stub is None:
- if self.IS_STUB:
- raise FatalError("Not possible for a stub to load another stub (memory likely to overlap.)")
- stub = self.STUB_CODE
- # Upload
- print("Uploading stub...")
- for field in ['text', 'data']:
- if field in stub:
- offs = stub[field + "_start"]
- length = len(stub[field])
- blocks = (length + self.ESP_RAM_BLOCK - 1) // self.ESP_RAM_BLOCK
- self.mem_begin(length, blocks, self.ESP_RAM_BLOCK, offs)
- for seq in range(blocks):
- from_offs = seq * self.ESP_RAM_BLOCK
- to_offs = from_offs + self.ESP_RAM_BLOCK
- self.mem_block(stub[field][from_offs:to_offs], seq)
- print("Running stub...")
- self.mem_finish(stub['entry'])
- p = self.read()
- if p != b'OHAI':
- raise FatalError("Failed to start stub. Unexpected response: %s" % p)
- print("Stub running...")
- return self.STUB_CLASS(self)
- @stub_and_esp32_function_only
- def flash_defl_begin(self, size, compsize, offset):
- """ Start downloading compressed data to Flash (performs an erase)
- Returns number of blocks (size self.FLASH_WRITE_SIZE) to write.
- """
- num_blocks = (compsize + self.FLASH_WRITE_SIZE - 1) // self.FLASH_WRITE_SIZE
- erase_blocks = (size + self.FLASH_WRITE_SIZE - 1) // self.FLASH_WRITE_SIZE
- t = time.time()
- if self.IS_STUB:
- write_size = size # stub expects number of bytes here, manages erasing internally
- timeout = DEFAULT_TIMEOUT
- else:
- write_size = erase_blocks * self.FLASH_WRITE_SIZE # ROM expects rounded up to erase block size
- timeout = timeout_per_mb(ERASE_REGION_TIMEOUT_PER_MB, write_size) # ROM performs the erase up front
- print("Compressed %d bytes to %d..." % (size, compsize))
- self.check_command("enter compressed flash mode", self.ESP_FLASH_DEFL_BEGIN,
- struct.pack('<IIII', write_size, num_blocks, self.FLASH_WRITE_SIZE, offset),
- timeout=timeout)
- if size != 0 and not self.IS_STUB:
- # (stub erases as it writes, but ROM loaders erase on begin)
- print("Took %.2fs to erase flash block" % (time.time() - t))
- return num_blocks
- """ Write block to flash, send compressed """
- @stub_and_esp32_function_only
- def flash_defl_block(self, data, seq, timeout=DEFAULT_TIMEOUT):
- self.check_command("write compressed data to flash after seq %d" % seq,
- self.ESP_FLASH_DEFL_DATA, struct.pack('<IIII', len(data), seq, 0, 0) + data, self.checksum(data), timeout=timeout)
- """ Leave compressed flash mode and run/reboot """
- @stub_and_esp32_function_only
- def flash_defl_finish(self, reboot=False):
- if not reboot and not self.IS_STUB:
- # skip sending flash_finish to ROM loader, as this
- # exits the bootloader. Stub doesn't do this.
- return
- pkt = struct.pack('<I', int(not reboot))
- self.check_command("leave compressed flash mode", self.ESP_FLASH_DEFL_END, pkt)
- self.in_bootloader = False
- @stub_and_esp32_function_only
- def flash_md5sum(self, addr, size):
- # the MD5 command returns additional bytes in the standard
- # command reply slot
- timeout = timeout_per_mb(MD5_TIMEOUT_PER_MB, size)
- res = self.check_command('calculate md5sum', self.ESP_SPI_FLASH_MD5, struct.pack('<IIII', addr, size, 0, 0),
- timeout=timeout)
- if len(res) == 32:
- return res.decode("utf-8") # already hex formatted
- elif len(res) == 16:
- return hexify(res).lower()
- else:
- raise FatalError("MD5Sum command returned unexpected result: %r" % res)
- @stub_and_esp32_function_only
- def change_baud(self, baud):
- print("Changing baud rate to %d" % baud)
- # stub takes the new baud rate and the old one
- second_arg = self._port.baudrate if self.IS_STUB else 0
- self.command(self.ESP_CHANGE_BAUDRATE, struct.pack('<II', baud, second_arg))
- print("Changed.")
- self._set_port_baudrate(baud)
- time.sleep(0.05) # get rid of crap sent during baud rate change
- self.flush_input()
- @stub_function_only
- def erase_flash(self):
- # depending on flash chip model the erase may take this long (maybe longer!)
- self.check_command("erase flash", self.ESP_ERASE_FLASH,
- timeout=CHIP_ERASE_TIMEOUT)
- @stub_function_only
- def erase_region(self, offset, size):
- if offset % self.FLASH_SECTOR_SIZE != 0:
- raise FatalError("Offset to erase from must be a multiple of 4096")
- if size % self.FLASH_SECTOR_SIZE != 0:
- raise FatalError("Size of data to erase must be a multiple of 4096")
- timeout = timeout_per_mb(ERASE_REGION_TIMEOUT_PER_MB, size)
- self.check_command("erase region", self.ESP_ERASE_REGION, struct.pack('<II', offset, size), timeout=timeout)
- @stub_function_only
- def read_flash(self, offset, length, progress_fn=None):
- # issue a standard bootloader command to trigger the read
- self.check_command("read flash", self.ESP_READ_FLASH,
- struct.pack('<IIII',
- offset,
- length,
- self.FLASH_SECTOR_SIZE,
- 64))
- # now we expect (length // block_size) SLIP frames with the data
- data = b''
- while len(data) < length:
- p = self.read()
- data += p
- if len(data) < length and len(p) < self.FLASH_SECTOR_SIZE:
- raise FatalError('Corrupt data, expected 0x%x bytes but received 0x%x bytes' % (self.FLASH_SECTOR_SIZE, len(p)))
- self.write(struct.pack('<I', len(data)))
- if progress_fn and (len(data) % 1024 == 0 or len(data) == length):
- progress_fn(len(data), length)
- if progress_fn:
- progress_fn(len(data), length)
- if len(data) > length:
- raise FatalError('Read more than expected')
- digest_frame = self.read()
- if len(digest_frame) != 16:
- raise FatalError('Expected digest, got: %s' % hexify(digest_frame))
- expected_digest = hexify(digest_frame).upper()
- digest = hashlib.md5(data).hexdigest().upper()
- if digest != expected_digest:
- raise FatalError('Digest mismatch: expected %s, got %s' % (expected_digest, digest))
- return data
- def flash_spi_attach(self, hspi_arg):
- """Send SPI attach command to enable the SPI flash pins
- ESP8266 ROM does this when you send flash_begin, ESP32 ROM
- has it as a SPI command.
- """
- # last 3 bytes in ESP_SPI_ATTACH argument are reserved values
- arg = struct.pack('<I', hspi_arg)
- if not self.IS_STUB:
- # ESP32 ROM loader takes additional 'is legacy' arg, which is not
- # currently supported in the stub loader or esptool.py (as it's not usually needed.)
- is_legacy = 0
- arg += struct.pack('BBBB', is_legacy, 0, 0, 0)
- self.check_command("configure SPI flash pins", ESP32ROM.ESP_SPI_ATTACH, arg)
- def flash_set_parameters(self, size):
- """Tell the ESP bootloader the parameters of the chip
- Corresponds to the "flashchip" data structure that the ROM
- has in RAM.
- 'size' is in bytes.
- All other flash parameters are currently hardcoded (on ESP8266
- these are mostly ignored by ROM code, on ESP32 I'm not sure.)
- """
- fl_id = 0
- total_size = size
- block_size = 64 * 1024
- sector_size = 4 * 1024
- page_size = 256
- status_mask = 0xffff
- self.check_command("set SPI params", ESP32ROM.ESP_SPI_SET_PARAMS,
- struct.pack('<IIIIII', fl_id, total_size, block_size, sector_size, page_size, status_mask))
- def run_spiflash_command(self, spiflash_command, data=b"", read_bits=0):
- """Run an arbitrary SPI flash command.
- This function uses the "USR_COMMAND" functionality in the ESP
- SPI hardware, rather than the precanned commands supported by
- hardware. So the value of spiflash_command is an actual command
- byte, sent over the wire.
- After writing command byte, writes 'data' to MOSI and then
- reads back 'read_bits' of reply on MISO. Result is a number.
- """
- # SPI_USR register flags
- SPI_USR_COMMAND = (1 << 31)
- SPI_USR_MISO = (1 << 28)
- SPI_USR_MOSI = (1 << 27)
- # SPI registers, base address differs ESP32 vs 8266
- base = self.SPI_REG_BASE
- SPI_CMD_REG = base + 0x00
- SPI_USR_REG = base + 0x1C
- SPI_USR1_REG = base + 0x20
- SPI_USR2_REG = base + 0x24
- SPI_W0_REG = base + self.SPI_W0_OFFS
- # following two registers are ESP32 only
- if self.SPI_HAS_MOSI_DLEN_REG:
- # ESP32 has a more sophisticated wayto set up "user" commands
- def set_data_lengths(mosi_bits, miso_bits):
- SPI_MOSI_DLEN_REG = base + 0x28
- SPI_MISO_DLEN_REG = base + 0x2C
- if mosi_bits > 0:
- self.write_reg(SPI_MOSI_DLEN_REG, mosi_bits - 1)
- if miso_bits > 0:
- self.write_reg(SPI_MISO_DLEN_REG, miso_bits - 1)
- else:
- def set_data_lengths(mosi_bits, miso_bits):
- SPI_DATA_LEN_REG = SPI_USR1_REG
- SPI_MOSI_BITLEN_S = 17
- SPI_MISO_BITLEN_S = 8
- mosi_mask = 0 if (mosi_bits == 0) else (mosi_bits - 1)
- miso_mask = 0 if (miso_bits == 0) else (miso_bits - 1)
- self.write_reg(SPI_DATA_LEN_REG,
- (miso_mask << SPI_MISO_BITLEN_S) | (
- mosi_mask << SPI_MOSI_BITLEN_S))
- # SPI peripheral "command" bitmasks for SPI_CMD_REG
- SPI_CMD_USR = (1 << 18)
- # shift values
- SPI_USR2_DLEN_SHIFT = 28
- if read_bits > 32:
- raise FatalError("Reading more than 32 bits back from a SPI flash operation is unsupported")
- if len(data) > 64:
- raise FatalError("Writing more than 64 bytes of data with one SPI command is unsupported")
- data_bits = len(data) * 8
- old_spi_usr = self.read_reg(SPI_USR_REG)
- old_spi_usr2 = self.read_reg(SPI_USR2_REG)
- flags = SPI_USR_COMMAND
- if read_bits > 0:
- flags |= SPI_USR_MISO
- if data_bits > 0:
- flags |= SPI_USR_MOSI
- set_data_lengths(data_bits, read_bits)
- self.write_reg(SPI_USR_REG, flags)
- self.write_reg(SPI_USR2_REG,
- (7 << SPI_USR2_DLEN_SHIFT) | spiflash_command)
- if data_bits == 0:
- self.write_reg(SPI_W0_REG, 0) # clear data register before we read it
- else:
- data = pad_to(data, 4, b'\00') # pad to 32-bit multiple
- words = struct.unpack("I" * (len(data) // 4), data)
- next_reg = SPI_W0_REG
- for word in words:
- self.write_reg(next_reg, word)
- next_reg += 4
- self.write_reg(SPI_CMD_REG, SPI_CMD_USR)
- def wait_done():
- for _ in range(10):
- if (self.read_reg(SPI_CMD_REG) & SPI_CMD_USR) == 0:
- return
- raise FatalError("SPI command did not complete in time")
- wait_done()
- status = self.read_reg(SPI_W0_REG)
- # restore some SPI controller registers
- self.write_reg(SPI_USR_REG, old_spi_usr)
- self.write_reg(SPI_USR2_REG, old_spi_usr2)
- return status
- def read_status(self, num_bytes=2):
- """Read up to 24 bits (num_bytes) of SPI flash status register contents
- via RDSR, RDSR2, RDSR3 commands
- Not all SPI flash supports all three commands. The upper 1 or 2
- bytes may be 0xFF.
- """
- SPIFLASH_RDSR = 0x05
- SPIFLASH_RDSR2 = 0x35
- SPIFLASH_RDSR3 = 0x15
- status = 0
- shift = 0
- for cmd in [SPIFLASH_RDSR, SPIFLASH_RDSR2, SPIFLASH_RDSR3][0:num_bytes]:
- status += self.run_spiflash_command(cmd, read_bits=8) << shift
- shift += 8
- return status
- def write_status(self, new_status, num_bytes=2, set_non_volatile=False):
- """Write up to 24 bits (num_bytes) of new status register
- num_bytes can be 1, 2 or 3.
- Not all flash supports the additional commands to write the
- second and third byte of the status register. When writing 2
- bytes, esptool also sends a 16-byte WRSR command (as some
- flash types use this instead of WRSR2.)
- If the set_non_volatile flag is set, non-volatile bits will
- be set as well as volatile ones (WREN used instead of WEVSR).
- """
- SPIFLASH_WRSR = 0x01
- SPIFLASH_WRSR2 = 0x31
- SPIFLASH_WRSR3 = 0x11
- SPIFLASH_WEVSR = 0x50
- SPIFLASH_WREN = 0x06
- SPIFLASH_WRDI = 0x04
- enable_cmd = SPIFLASH_WREN if set_non_volatile else SPIFLASH_WEVSR
- # try using a 16-bit WRSR (not supported by all chips)
- # this may be redundant, but shouldn't hurt
- if num_bytes == 2:
- self.run_spiflash_command(enable_cmd)
- self.run_spiflash_command(SPIFLASH_WRSR, struct.pack("<H", new_status))
- # also try using individual commands (also not supported by all chips for num_bytes 2 & 3)
- for cmd in [SPIFLASH_WRSR, SPIFLASH_WRSR2, SPIFLASH_WRSR3][0:num_bytes]:
- self.run_spiflash_command(enable_cmd)
- self.run_spiflash_command(cmd, struct.pack("B", new_status & 0xFF))
- new_status >>= 8
- self.run_spiflash_command(SPIFLASH_WRDI)
- def hard_reset(self):
- self._setRTS(True) # EN->LOW
- time.sleep(0.1)
- self._setRTS(False)
- def soft_reset(self, stay_in_bootloader):
- if not self.IS_STUB:
- if stay_in_bootloader:
- return # ROM bootloader is already in bootloader!
- else:
- # 'run user code' is as close to a soft reset as we can do
- self.flash_begin(0, 0)
- self.flash_finish(False)
- else:
- if stay_in_bootloader:
- # soft resetting from the stub loader
- # will re-load the ROM bootloader
- self.flash_begin(0, 0)
- self.flash_finish(True)
- elif self.CHIP_NAME != "ESP8266":
- raise FatalError("Soft resetting is currently only supported on ESP8266")
- else:
- # running user code from stub loader requires some hacks
- # in the stub loader
- self.command(self.ESP_RUN_USER_CODE, wait_response=False)
- class ESP8266ROM(ESPLoader):
- """ Access class for ESP8266 ROM bootloader
- """
- CHIP_NAME = "ESP8266"
- IS_STUB = False
- DATE_REG_VALUE = 0x00062000
- # OTP ROM addresses
- ESP_OTP_MAC0 = 0x3ff00050
- ESP_OTP_MAC1 = 0x3ff00054
- ESP_OTP_MAC3 = 0x3ff0005c
- SPI_REG_BASE = 0x60000200
- SPI_W0_OFFS = 0x40
- SPI_HAS_MOSI_DLEN_REG = False
- FLASH_SIZES = {
- '512KB':0x00,
- '256KB':0x10,
- '1MB':0x20,
- '2MB':0x30,
- '4MB':0x40,
- '2MB-c1': 0x50,
- '4MB-c1':0x60,
- '8MB':0x80,
- '16MB':0x90,
- }
- BOOTLOADER_FLASH_OFFSET = 0
- def get_efuses(self):
- # Return the 128 bits of ESP8266 efuse as a single Python integer
- return (self.read_reg(0x3ff0005c) << 96 |
- self.read_reg(0x3ff00058) << 64 |
- self.read_reg(0x3ff00054) << 32 |
- self.read_reg(0x3ff00050))
- def get_chip_description(self):
- efuses = self.get_efuses()
- is_8285 = (efuses & ((1 << 4) | 1 << 80)) != 0 # One or the other efuse bit is set for ESP8285
- return "ESP8285" if is_8285 else "ESP8266EX"
- def get_chip_features(self):
- features = ["WiFi"]
- if self.get_chip_description() == "ESP8285":
- features += ["Embedded Flash"]
- return features
- def flash_spi_attach(self, hspi_arg):
- if self.IS_STUB:
- super(ESP8266ROM, self).flash_spi_attach(hspi_arg)
- else:
- # ESP8266 ROM has no flash_spi_attach command in serial protocol,
- # but flash_begin will do it
- self.flash_begin(0, 0)
- def flash_set_parameters(self, size):
- # not implemented in ROM, but OK to silently skip for ROM
- if self.IS_STUB:
- super(ESP8266ROM, self).flash_set_parameters(size)
- def chip_id(self):
- """ Read Chip ID from efuse - the equivalent of the SDK system_get_chip_id() function """
- id0 = self.read_reg(self.ESP_OTP_MAC0)
- id1 = self.read_reg(self.ESP_OTP_MAC1)
- return (id0 >> 24) | ((id1 & MAX_UINT24) << 8)
- def read_mac(self):
- """ Read MAC from OTP ROM """
- 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:
- oui = (0x18, 0xfe, 0x34)
- elif ((mac1 >> 16) & 0xff) == 1:
- oui = (0xac, 0xd0, 0x74)
- else:
- raise FatalError("Unknown OUI")
- return oui + ((mac1 >> 8) & 0xff, mac1 & 0xff, (mac0 >> 24) & 0xff)
- def get_erase_size(self, offset, size):
- """ Calculate an erase size given a specific size in bytes.
- Provides a workaround for the bootloader erase bug."""
- sectors_per_block = 16
- sector_size = self.FLASH_SECTOR_SIZE
- 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:
- return (num_sectors + 1) // 2 * sector_size
- else:
- return (num_sectors - head_sectors) * sector_size
- def override_vddsdio(self, new_voltage):
- raise NotImplementedInROMError("Overriding VDDSDIO setting only applies to ESP32")
- class ESP8266StubLoader(ESP8266ROM):
- """ Access class for ESP8266 stub loader, runs on top of ROM.
- """
- FLASH_WRITE_SIZE = 0x4000 # matches MAX_WRITE_BLOCK in stub_loader.c
- IS_STUB = True
- def __init__(self, rom_loader):
- self._port = rom_loader._port
- self._trace_enabled = rom_loader._trace_enabled
- self.flush_input() # resets _slip_reader
- def get_erase_size(self, offset, size):
- return size # stub doesn't have same size bug as ROM loader
- ESP8266ROM.STUB_CLASS = ESP8266StubLoader
- class ESP32ROM(ESPLoader):
- """Access class for ESP32 ROM bootloader
- """
- CHIP_NAME = "ESP32"
- IS_STUB = False
- DATE_REG_VALUE = 0x15122500
- IROM_MAP_START = 0x400d0000
- IROM_MAP_END = 0x40400000
- DROM_MAP_START = 0x3F400000
- DROM_MAP_END = 0x3F800000
- # ESP32 uses a 4 byte status reply
- STATUS_BYTES_LENGTH = 4
- SPI_REG_BASE = 0x60002000
- EFUSE_REG_BASE = 0x6001a000
- SPI_W0_OFFS = 0x80
- SPI_HAS_MOSI_DLEN_REG = True
- FLASH_SIZES = {
- '1MB':0x00,
- '2MB':0x10,
- '4MB':0x20,
- '8MB':0x30,
- '16MB':0x40
- }
- BOOTLOADER_FLASH_OFFSET = 0x1000
- OVERRIDE_VDDSDIO_CHOICES = ["1.8V", "1.9V", "OFF"]
- def get_chip_description(self):
- word3 = self.read_efuse(3)
- chip_ver_rev1 = (word3 >> 15) & 0x1
- pkg_version = (word3 >> 9) & 0x07
- chip_name = {
- 0: "ESP32D0WDQ6",
- 1: "ESP32D0WDQ5",
- 2: "ESP32D2WDQ5",
- 5: "ESP32-PICO-D4",
- }.get(pkg_version, "unknown ESP32")
- return "%s (revision %d)" % (chip_name, chip_ver_rev1)
- def get_chip_features(self):
- features = ["WiFi"]
- word3 = self.read_efuse(3)
- # names of variables in this section are lowercase
- # versions of EFUSE names as documented in TRM and
- # ESP-IDF efuse_reg.h
- chip_ver_dis_bt = word3 & (1 << 1)
- if chip_ver_dis_bt == 0:
- features += ["BT"]
- chip_ver_dis_app_cpu = word3 & (1 << 0)
- if chip_ver_dis_app_cpu:
- features += ["Single Core"]
- else:
- features += ["Dual Core"]
- chip_cpu_freq_rated = word3 & (1 << 13)
- if chip_cpu_freq_rated:
- chip_cpu_freq_low = word3 & (1 << 12)
- if chip_cpu_freq_low:
- features += ["160MHz"]
- else:
- features += ["240MHz"]
- pkg_version = (word3 >> 9) & 0x07
- if pkg_version in [2, 4, 5]:
- features += ["Embedded Flash"]
- word4 = self.read_efuse(4)
- adc_vref = (word4 >> 8) & 0x1F
- 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):
- """ Read the nth word of the ESP3x EFUSE region. """
- return self.read_reg(self.EFUSE_REG_BASE + (4 * n))
- def chip_id(self):
- raise NotSupportedError(self, "chip_id")
- def read_mac(self):
- """ Read MAC from EFUSE region """
- words = [self.read_efuse(2), self.read_efuse(1)]
- bitstring = struct.pack(">II", *words)
- bitstring = bitstring[2:8] # trim the 2 byte CRC
- try:
- return tuple(ord(b) for b in bitstring)
- except TypeError: # Python 3, bitstring elements are already bytes
- return tuple(bitstring)
- def get_erase_size(self, offset, size):
- return size
- def override_vddsdio(self, new_voltage):
- new_voltage = new_voltage.upper()
- if new_voltage not in self.OVERRIDE_VDDSDIO_CHOICES:
- raise FatalError("The only accepted VDDSDIO overrides are '1.8V', '1.9V' and 'OFF'")
- RTC_CNTL_SDIO_CONF_REG = 0x3ff48074
- RTC_CNTL_XPD_SDIO_REG = (1 << 31)
- RTC_CNTL_DREFH_SDIO_M = (3 << 29)
- RTC_CNTL_DREFM_SDIO_M = (3 << 27)
- RTC_CNTL_DREFL_SDIO_M = (3 << 25)
- # RTC_CNTL_SDIO_TIEH = (1 << 23) # not used here, setting TIEH=1 would set 3.3V output, not safe for esptool.py to do
- RTC_CNTL_SDIO_FORCE = (1 << 22)
- RTC_CNTL_SDIO_PD_EN = (1 << 21)
- reg_val = RTC_CNTL_SDIO_FORCE # override efuse setting
- reg_val |= RTC_CNTL_SDIO_PD_EN
- if new_voltage != "OFF":
- reg_val |= RTC_CNTL_XPD_SDIO_REG # enable internal LDO
- if new_voltage == "1.9V":
- reg_val |= (RTC_CNTL_DREFH_SDIO_M | RTC_CNTL_DREFM_SDIO_M | RTC_CNTL_DREFL_SDIO_M) # boost voltage
- self.write_reg(RTC_CNTL_SDIO_CONF_REG, reg_val)
- print("VDDSDIO regulator set to %s" % new_voltage)
- class ESP32StubLoader(ESP32ROM):
- """ Access class for ESP32 stub loader, runs on top of ROM.
- """
- FLASH_WRITE_SIZE = 0x4000 # matches MAX_WRITE_BLOCK in stub_loader.c
- STATUS_BYTES_LENGTH = 2 # same as ESP8266, different to ESP32 ROM
- IS_STUB = True
- def __init__(self, rom_loader):
- self._port = rom_loader._port
- self._trace_enabled = rom_loader._trace_enabled
- self.flush_input() # resets _slip_reader
- ESP32ROM.STUB_CLASS = ESP32StubLoader
- 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(chip, filename):
- """ Load a firmware image. Can be for ESP8266 or ESP32. ESP8266 images will be examined to determine if they are
- original ROM firmware images (ESP8266ROMFirmwareImage) or "v2" OTA bootloader images.
- Returns a BaseFirmwareImage subclass, either ESP8266ROMFirmwareImage (v1) or ESP8266V2FirmwareImage (v2).
- """
- with open(filename, 'rb') as f:
- if chip.lower() == 'esp32':
- return ESP32FirmwareImage(f)
- else: # Otherwise, ESP8266 so look at magic to determine the image type
- magic = ord(f.read(1))
- f.seek(0)
- if magic == ESPLoader.ESP_IMAGE_MAGIC:
- return ESP8266ROMFirmwareImage(f)
- elif magic == ESPBOOTLOADER.IMAGE_V2_MAGIC:
- return ESP8266V2FirmwareImage(f)
- else:
- raise FatalError("Invalid image magic number: %d" % magic)
- class ImageSegment(object):
- """ Wrapper class for a segment in an ESP image
- (very similar to a section in an ELFImage also) """
- def __init__(self, addr, data, file_offs=None):
- self.addr = addr
- self.data = data
- self.file_offs = file_offs
- self.include_in_checksum = True
- if self.addr != 0:
- self.pad_to_alignment(4) # pad all "real" ImageSegments 4 byte aligned length
- def copy_with_new_addr(self, new_addr):
- """ Return a new ImageSegment with same data, but mapped at
- a new address. """
- return ImageSegment(new_addr, self.data, 0)
- def split_image(self, split_len):
- """ Return a new ImageSegment which splits "split_len" bytes
- from the beginning of the data. Remaining bytes are kept in
- this segment object (and the start address is adjusted to match.) """
- result = copy.copy(self)
- result.data = self.data[:split_len]
- self.data = self.data[split_len:]
- self.addr += split_len
- self.file_offs = None
- result.file_offs = None
- return result
- def __repr__(self):
- r = "len 0x%05x load 0x%08x" % (len(self.data), self.addr)
- if self.file_offs is not None:
- r += " file_offs 0x%08x" % (self.file_offs)
- return r
- def pad_to_alignment(self, alignment):
- self.data = pad_to(self.data, alignment, b'\x00')
- class ELFSection(ImageSegment):
- """ Wrapper class for a section in an ELF image, has a section
- name as well as the common properties of an ImageSegment. """
- def __init__(self, name, addr, data):
- super(ELFSection, self).__init__(addr, data)
- self.name = name.decode("utf-8")
- def __repr__(self):
- return "%s %s" % (self.name, super(ELFSection, self).__repr__())
- 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('<BBBBI', load_file.read(8))
- 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()
- (offset, size) = struct.unpack('<II', f.read(8))
- self.warn_if_unusual_segment(offset, size, is_irom_segment)
- 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 = ImageSegment(offset, segment_data, file_offs)
- self.segments.append(segment)
- return segment
- def warn_if_unusual_segment(self, offset, size, is_irom_segment):
- if not is_irom_segment:
- 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 """
- segment_data = self.maybe_patch_segment_data(f, segment.data)
- f.write(struct.pack('<II', segment.addr, len(segment_data)))
- f.write(segment_data)
- if checksum is not None:
- return ESPLoader.checksum(segment_data, checksum)
- def read_checksum(self, f):
- """ Return ESPLoader 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 calculate_checksum(self):
- """ Calculate checksum of loaded image, based on segments in
- segment array.
- """
- checksum = ESPLoader.ESP_CHECKSUM_MAGIC
- for seg in self.segments:
- if seg.include_in_checksum:
- checksum = ESPLoader.checksum(seg.data, checksum)
- return checksum
- def append_checksum(self, f, checksum):
- """ Append ESPLoader checksum to the just-written image """
- align_file_position(f, 16)
- f.write(struct.pack(b'B', checksum))
- def write_common_header(self, f, segments):
- f.write(struct.pack('<BBBBI', ESPLoader.ESP_IMAGE_MAGIC, len(segments),
- self.flash_mode, self.flash_size_freq, self.entrypoint))
- def is_irom_addr(self, addr):
- """ Returns True if an address starts in the irom region.
- Valid for ESP8266 only.
- """
- return ESP8266ROM.IROM_MAP_START <= addr < ESP8266ROM.IROM_MAP_END
- def get_irom_segment(self):
- irom_segments = [s for s in self.segments if self.is_irom_addr(s.addr)]
- if len(irom_segments) > 0:
- if len(irom_segments) != 1:
- raise FatalError('Found %d segments that could be irom0. Bad ELF file?' % len(irom_segments))
- return irom_segments[0]
- return None
- def get_non_irom_segments(self):
- irom_segment = self.get_irom_segment()
- return [s for s in self.segments if s != irom_segment]
- class ESP8266ROMFirmwareImage(BaseFirmwareImage):
- """ 'Version 1' firmware image, segments loaded directly by the ROM bootloader. """
- ROM_LOADER = ESP8266ROM
- def __init__(self, load_file=None):
- super(ESP8266ROMFirmwareImage, self).__init__()
- self.flash_mode = 0
- self.flash_size_freq = 0
- self.version = 1
- if load_file is not None:
- segments = self.load_common_header(load_file, ESPLoader.ESP_IMAGE_MAGIC)
- for _ in range(segments):
- self.load_segment(load_file)
- self.checksum = self.read_checksum(load_file)
- self.verify()
- def default_output_name(self, input_file):
- """ Derive a default output name from the ELF name. """
- return input_file + '-'
- def save(self, basename):
- """ Save a set of V1 images for flashing. Parameter is a base filename. """
- # IROM data goes in its own plain binary file
- irom_segment = self.get_irom_segment()
- if irom_segment is not None:
- with open("%s0x%05x.bin" % (basename, irom_segment.addr - ESP8266ROM.IROM_MAP_START), "wb") as f:
- f.write(irom_segment.data)
- # everything but IROM goes at 0x00000 in an image file
- normal_segments = self.get_non_irom_segments()
- with open("%s0x00000.bin" % basename, 'wb') as f:
- self.write_common_header(f, normal_segments)
- checksum = ESPLoader.ESP_CHECKSUM_MAGIC
- for segment in normal_segments:
- checksum = self.save_segment(f, segment, checksum)
- self.append_checksum(f, checksum)
- class ESP8266V2FirmwareImage(BaseFirmwareImage):
- """ 'Version 2' firmware image, segments loaded by software bootloader stub
- (ie Espressif bootloader or rboot)
- """
- ROM_LOADER = ESP8266ROM
- def __init__(self, load_file=None):
- super(ESP8266V2FirmwareImage, self).__init__()
- self.version = 2
- if load_file is not None:
- segments = self.load_common_header(load_file, ESPBOOTLOADER.IMAGE_V2_MAGIC)
- if segments != ESPBOOTLOADER.IMAGE_V2_SEGMENT:
- # 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
- #
- # the file is saved in the image with a zero load address
- # in the header, so we need to calculate a load address
- irom_segment = self.load_segment(load_file, True)
- irom_segment.addr = 0 # for actual mapped addr, add ESP8266ROM.IROM_MAP_START + flashing_addr + 8
- irom_segment.include_in_checksum = False
- first_flash_mode = self.flash_mode
- first_flash_size_freq = self.flash_size_freq
- first_entrypoint = self.entrypoint
- # load the second header
- segments = self.load_common_header(load_file, ESPLoader.ESP_IMAGE_MAGIC)
- 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: Entrypoint address in first header (0x%08x) disagrees with second header (0x%08x). Using second value.'
- % (first_entrypoint, self.entrypoint))
- # load all the usual segments
- for _ in range(segments):
- self.load_segment(load_file)
- self.checksum = self.read_checksum(load_file)
- self.verify()
- def default_output_name(self, input_file):
- """ Derive a default output name from the ELF name. """
- irom_segment = self.get_irom_segment()
- if irom_segment is not None:
- irom_offs = irom_segment.addr - ESP8266ROM.IROM_MAP_START
- else:
- irom_offs = 0
- return "%s-0x%05x.bin" % (os.path.splitext(input_file)[0],
- irom_offs & ~(ESPLoader.FLASH_SECTOR_SIZE - 1))
- def save(self, filename):
- with open(filename, 'wb') as f:
- # Save first header for irom0 segment
- f.write(struct.pack(b'<BBBBI', ESPBOOTLOADER.IMAGE_V2_MAGIC, ESPBOOTLOADER.IMAGE_V2_SEGMENT,
- self.flash_mode, self.flash_size_freq, self.entrypoint))
- irom_segment = self.get_irom_segment()
- if irom_segment is not None:
- # save irom0 segment, make sure it has load addr 0 in the file
- irom_segment = irom_segment.copy_with_new_addr(0)
- irom_segment.pad_to_alignment(16) # irom_segment must end on a 16 byte boundary
- self.save_segment(f, irom_segment)
- # second header, matches V1 header and contains loadable segments
- normal_segments = self.get_non_irom_segments()
- self.write_common_header(f, normal_segments)
- checksum = ESPLoader.ESP_CHECKSUM_MAGIC
- for segment in normal_segments:
- checksum = self.save_segment(f, segment, checksum)
- self.append_checksum(f, checksum)
- # calculate a crc32 of entire file and append
- # (algorithm used by recent 8266 SDK bootloaders)
- with open(filename, 'rb') as f:
- crc = esp8266_crc32(f.read())
- with open(filename, 'ab') as f:
- f.write(struct.pack(b'<I', crc))
- # Backwards compatibility for previous API, remove in esptool.py V3
- ESPFirmwareImage = ESP8266ROMFirmwareImage
- OTAFirmwareImage = ESP8266V2FirmwareImage
- def esp8266_crc32(data):
- """
- CRC32 algorithm used by 8266 SDK bootloader (and gen_appbin.py).
- """
- crc = binascii.crc32(data, 0) & 0xFFFFFFFF
- if crc & 0x80000000:
- return crc ^ 0xFFFFFFFF
- else:
- return crc + 1
- class ESP32FirmwareImage(BaseFirmwareImage):
- """ ESP32 firmware image is very similar to V1 ESP8266 image,
- except with an additional 16 byte reserved header at top of image,
- and because of new flash mapping capabilities the flash-mapped regions
- can be placed in the normal image (just @ 64kB padded offsets).
- """
- ROM_LOADER = ESP32ROM
- # ROM bootloader will read the wp_pin field if SPI flash
- # pins are remapped via flash. IDF actually enables QIO only
- # from software bootloader, so this can be ignored. But needs
- # to be set to this value so ROM bootloader will skip it.
- WP_PIN_DISABLED = 0xEE
- EXTENDED_HEADER_STRUCT_FMT = "B" * 16
- IROM_ALIGN = 65536
- def __init__(self, load_file=None):
- super(ESP32FirmwareImage, self).__init__()
- self.secure_pad = False
- self.flash_mode = 0
- self.flash_size_freq = 0
- self.version = 1
- self.wp_pin = self.WP_PIN_DISABLED
- # SPI pin drive levels
- self.clk_drv = 0
- self.q_drv = 0
- self.d_drv = 0
- self.cs_drv = 0
- self.hd_drv = 0
- self.wp_drv = 0
- self.append_digest = True
- if load_file is not None:
- start = load_file.tell()
- segments = self.load_common_header(load_file, ESPLoader.ESP_IMAGE_MAGIC)
- self.load_extended_header(load_file)
- for _ in range(segments):
- self.load_segment(load_file)
- self.checksum = self.read_checksum(load_file)
- if self.append_digest:
- end = load_file.tell()
- self.stored_digest = load_file.read(32)
- load_file.seek(start)
- calc_digest = hashlib.sha256()
- calc_digest.update(load_file.read(end - start))
- self.calc_digest = calc_digest.digest() # TODO: decide what to do here?
- self.verify()
- def is_flash_addr(self, addr):
- return (ESP32ROM.IROM_MAP_START <= addr < ESP32ROM.IROM_MAP_END) \
- or (ESP32ROM.DROM_MAP_START <= addr < ESP32ROM.DROM_MAP_END)
- def default_output_name(self, input_file):
- """ Derive a default output name from the ELF name. """
- return "%s.bin" % (os.path.splitext(input_file)[0])
- def warn_if_unusual_segment(self, offset, size, is_irom_segment):
- pass # TODO: add warnings for ESP32 segment offset/size combinations that are wrong
- def save(self, filename):
- total_segments = 0
- with io.BytesIO() as f: # write file to memory first
- self.write_common_header(f, self.segments)
- # first 4 bytes of header are read by ROM bootloader for SPI
- # config, but currently unused
- self.save_extended_header(f)
- checksum = ESPLoader.ESP_CHECKSUM_MAGIC
- # split segments into flash-mapped vs ram-loaded, and take copies so we can mutate them
- flash_segments = [copy.deepcopy(s) for s in sorted(self.segments, key=lambda s:s.addr) if self.is_flash_addr(s.addr)]
- ram_segments = [copy.deepcopy(s) for s in sorted(self.segments, key=lambda s:s.addr) if not self.is_flash_addr(s.addr)]
- # check for multiple ELF sections that are mapped in the same flash mapping region.
- # this is usually a sign of a broken linker script, but if you have a legitimate
- # use case then let us know (we can merge segments here, but as a rule you probably
- # want to merge them in your linker script.)
- if len(flash_segments) > 0:
- last_addr = flash_segments[0].addr
- for segment in flash_segments[1:]:
- 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))
- last_addr = segment.addr
- def get_alignment_data_needed(segment):
- # Actual alignment (in data bytes) required for a segment header: positioned so that
- # after we write the next 8 byte header, file_offs % IROM_ALIGN == segment.addr % IROM_ALIGN
- #
- # (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 % 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 += self.IROM_ALIGN
- return pad_len
- # try to fit each flash segment on a 64kB aligned boundary
- # by padding with parts of the non-flash segments...
- while len(flash_segments) > 0:
- segment = flash_segments[0]
- pad_len = get_alignment_data_needed(segment)
- if pad_len > 0: # need to pad
- if len(ram_segments) > 0 and pad_len > self.SEG_HEADER_LEN:
- pad_segment = ram_segments[0].split_image(pad_len)
- if len(ram_segments[0].data) == 0:
- ram_segments.pop(0)
- else:
- pad_segment = ImageSegment(0, b'\x00' * pad_len, f.tell())
- checksum = self.save_segment(f, pad_segment, checksum)
- total_segments += 1
- else:
- # write the flash segment
- 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
- # flash segments all written, so write any remaining RAM segments
- for segment in ram_segments:
- checksum = self.save_segment(f, segment, checksum)
- total_segments += 1
- if self.secure_pad:
- # pad the image so that after signing it will end on a a 64KB boundary.
- # 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) % 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 = (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)
- total_segments += 1
- # done writing segments
- self.append_checksum(f, checksum)
- image_length = f.tell()
- if self.secure_pad:
- 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
- f.seek(1)
- try:
- f.write(chr(total_segments))
- except TypeError: # Python 3
- f.write(bytes([total_segments]))
- if self.append_digest:
- # calculate the SHA256 of the whole file and append it
- f.seek(0)
- digest = hashlib.sha256()
- digest.update(f.read(image_length))
- f.write(digest.digest())
- 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)
- fields = list(struct.unpack(self.EXTENDED_HEADER_STRUCT_FMT, load_file.read(16)))
- self.wp_pin = fields[0]
- # SPI pin drive stengths are two per byte
- self.clk_drv, self.q_drv = split_byte(fields[1])
- self.d_drv, self.cs_drv = split_byte(fields[2])
- self.hd_drv, self.wp_drv = split_byte(fields[3])
- if fields[15] in [0, 1]:
- self.append_digest = (fields[15] == 1)
- else:
- raise RuntimeError("Invalid value for append_digest field (0x%02x). Should be 0 or 1.", fields[15])
- # remaining fields in the middle should all be zero
- if any(f for f in fields[4:15] if f != 0):
- print("Warning: some reserved header fields have non-zero values. This image may be from a newer esptool.py?")
- def save_extended_header(self, save_file):
- def join_byte(ln,hn):
- return (ln & 0x0F) + ((hn & 0x0F) << 4)
- append_digest = 1 if self.append_digest else 0
- fields = [self.wp_pin,
- join_byte(self.clk_drv, self.q_drv),
- join_byte(self.d_drv, self.cs_drv),
- join_byte(self.hd_drv, self.wp_drv)]
- fields += [0] * 11
- fields += [append_digest]
- packed = struct.pack(self.EXTENDED_HEADER_STRUCT_FMT, *fields)
- save_file.write(packed)
- class ELFFile(object):
- SEC_TYPE_PROGBITS = 0x01
- SEC_TYPE_STRTAB = 0x03
- LEN_SEC_HEADER = 0x28
- def __init__(self, name):
- # Load sections from the ELF file
- self.name = name
- with open(self.name, 'rb') as f:
- self._read_elf_file(f)
- def get_section(self, section_name):
- for s in self.sections:
- if s.name == section_name:
- return s
- raise ValueError("No section %s in ELF file" % section_name)
- def _read_elf_file(self, f):
- # read the ELF file header
- LEN_FILE_HEADER = 0x34
- try:
- (ident,_type,machine,_version,
- self.entrypoint,_phoff,shoff,_flags,
- _ehsize, _phentsize,_phnum, shentsize,
- shnum,shstrndx) = struct.unpack("<16sHHLLLLLHHHHHH", f.read(LEN_FILE_HEADER))
- except struct.error as e:
- raise FatalError("Failed to read a valid ELF header from %s: %s" % (self.name, e))
- if byte(ident, 0) != 0x7f or ident[1:4] != b'ELF':
- raise FatalError("%s has invalid ELF magic header" % self.name)
- if machine != 0x5e:
- raise FatalError("%s does not appear to be an Xtensa ELF file. e_machine=%04x" % (self.name, machine))
- if shentsize != self.LEN_SEC_HEADER:
- raise FatalError("%s has unexpected section header entry size 0x%x (not 0x28)" % (self.name, shentsize, self.LEN_SEC_HEADER))
- if shnum == 0:
- raise FatalError("%s has 0 section headers" % (self.name))
- self._read_sections(f, shoff, shnum, shstrndx)
- def _read_sections(self, f, section_header_offs, section_header_count, shstrndx):
- f.seek(section_header_offs)
- len_bytes = section_header_count * self.LEN_SEC_HEADER
- section_header = f.read(len_bytes)
- if len(section_header) == 0:
- raise FatalError("No section header found at offset %04x in ELF file." % section_header_offs)
- if len(section_header) != (len_bytes):
- raise FatalError("Only read 0x%x bytes from section header (expected 0x%x.) Truncated ELF file?" % (len(section_header), len_bytes))
- # walk through the section header and extract all sections
- section_header_offsets = range(0, len(section_header), self.LEN_SEC_HEADER)
- def read_section_header(offs):
- name_offs,sec_type,_flags,lma,sec_offs,size = struct.unpack_from("<LLLLLL", section_header[offs:])
- return (name_offs, sec_type, lma, size, sec_offs)
- all_sections = [read_section_header(offs) for offs in section_header_offsets]
- prog_sections = [s for s in all_sections if s[1] == ELFFile.SEC_TYPE_PROGBITS]
- # search for the string table section
- if not (shstrndx * self.LEN_SEC_HEADER) in section_header_offsets:
- raise FatalError("ELF file has no STRTAB section at shstrndx %d" % shstrndx)
- _,sec_type,_,sec_size,sec_offs = read_section_header(shstrndx * self.LEN_SEC_HEADER)
- if sec_type != ELFFile.SEC_TYPE_STRTAB:
- print('WARNING: ELF file has incorrect STRTAB section type 0x%02x' % sec_type)
- f.seek(sec_offs)
- string_table = f.read(sec_size)
- # build the real list of ELFSections by reading the actual section names from the
- # string table section, and actual data for each section from the ELF file itself
- def lookup_string(offs):
- raw = string_table[offs:]
- return raw[:raw.index(b'\x00')]
- def read_data(offs,size):
- f.seek(offs)
- 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 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.
- 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 == b'':
- waiting_for = "header" if partial_packet is None else "content"
- trace_function("Timed out waiting for packet %s", waiting_for)
- raise FatalError("Timed out waiting for packet %s" % waiting_for)
- trace_function("Read %d bytes: %s", len(read_bytes), HexFormatter(read_bytes))
- for b in read_bytes:
- if type(b) is int:
- b = bytes([b]) # python 2/3 compat
- if partial_packet is None: # waiting for packet header
- if b == b'\xc0':
- partial_packet = b""
- else:
- trace_function("Read invalid data: %s", HexFormatter(read_bytes))
- trace_function("Remaining data in serial buffer: %s", HexFormatter(port.read(port.inWaiting())))
- raise FatalError('Invalid head of packet (0x%s)' % hexify(b))
- elif in_escape: # part-way through escape sequence
- in_escape = False
- if b == b'\xdc':
- partial_packet += b'\xc0'
- elif b == b'\xdd':
- partial_packet += b'\xdb'
- else:
- trace_function("Read invalid data: %s", HexFormatter(read_bytes))
- trace_function("Remaining data in serial buffer: %s", HexFormatter(port.read(port.inWaiting())))
- raise FatalError('Invalid SLIP escape (0xdb, 0x%s)' % (hexify(b)))
- elif b == b'\xdb': # start of escape sequence
- in_escape = True
- elif b == b'\xc0': # end of packet
- trace_function("Received full packet: %s", HexFormatter(partial_packet))
- yield partial_packet
- partial_packet = None
- else: # normal byte in packet
- partial_packet += b
- def arg_auto_int(x):
- return int(x, 0)
- 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 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 flash_size_bytes(size):
- """ Given a flash size of the type passed in args.flash_size
- (ie 512KB or 1MB) then return the size in bytes.
- """
- if "MB" in size:
- return int(size[:size.index("MB")]) * 1024 * 1024
- elif "KB" in size:
- return int(size[:size.index("KB")]) * 1024
- else:
- raise FatalError("Unknown size %s" % size)
- def hexify(s, uppercase=True):
- format_str = '%02X' if uppercase else '%02x'
- if not PYTHON2:
- return ''.join(format_str % c for c in s)
- else:
- return ''.join(format_str % ord(c) for c in s)
- class HexFormatter(object):
- """
- Wrapper class which takes binary data in its constructor
- and returns a hex string as it's __str__ method.
- This is intended for "lazy formatting" of trace() output
- in hex format. Avoids overhead (significant on slow computers)
- of generating long hex strings even if tracing is disabled.
- Note that this doesn't save any overhead if passed as an
- argument to "%", only when passed to trace()
- If auto_split is set (default), any long line (> 16 bytes) will be
- printed as separately indented lines, with ASCII decoding at the end
- of each line.
- """
- def __init__(self, binary_string, auto_split=True):
- self._s = binary_string
- self._auto_split = auto_split
- def __str__(self):
- if self._auto_split and len(self._s) > 16:
- result = ""
- s = self._s
- while len(s) > 0:
- line = s[:16]
- ascii_line = "".join(c if (c == ' ' or (c in string.printable and c not in string.whitespace))
- else '.' for c in line.decode('ascii', 'replace'))
- s = s[16:]
- result += "\n %-16s %-16s | %s" % (hexify(line[:8], False), hexify(line[8:], False), ascii_line)
- return result
- else:
- return hexify(self._s, False)
- def pad_to(data, alignment, pad_character=b'\xFF'):
- """ Pad to the next alignment boundary """
- pad_mod = len(data) % alignment
- if pad_mod != 0:
- data += pad_character * (alignment - pad_mod)
- return data
- 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 appends the hex values of
- 'result' as a string formatted argument.
- """
- message += " (result was %s)" % hexify(result)
- return FatalError(message)
- class NotImplementedInROMError(FatalError):
- """
- Wrapper class for the error thrown when a particular ESP bootloader function
- is not implemented in the ROM bootloader.
- """
- def __init__(self, bootloader, func):
- FatalError.__init__(self, "%s ROM does not support function %s." % (bootloader.CHIP_NAME, func.__name__))
- class NotSupportedError(FatalError):
- def __init__(self, esp, function_name):
- FatalError.__init__(self, "Function %s is not supported for %s." % (function_name, esp.CHIP_NAME))
- # "Operation" commands, executable at command line. One function each
- #
- # Each function takes either two args (<ESPLoader instance>, <args>) or a single <args>
- # argument.
- def load_ram(esp, args):
- image = LoadFirmwareImage(esp.CHIP_NAME, args.filename)
- print('RAM boot...')
- for seg in image.segments:
- size = len(seg.data)
- print('Downloading %d bytes at %08x...' % (size, seg.addr), end=' ')
- sys.stdout.flush()
- esp.mem_begin(size, div_roundup(size, esp.ESP_RAM_BLOCK), esp.ESP_RAM_BLOCK, seg.addr)
- seq = 0
- while len(seg.data) > 0:
- esp.mem_block(seg.data[0:esp.ESP_RAM_BLOCK], seq)
- seg.data = seg.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):
- with open(args.filename, 'wb') as f:
- for i in range(args.size // 4):
- d = esp.read_reg(args.address + (i * 4))
- f.write(struct.pack(b'<I', d))
- if f.tell() % 1024 == 0:
- print('\r%d bytes read... (%d %%)' % (f.tell(),
- f.tell() * 100 // args.size),
- end=' ')
- sys.stdout.flush()
- print('Done!')
- def detect_flash_size(esp, args):
- if args.flash_size == 'detect':
- flash_id = esp.flash_id()
- size_id = flash_id >> 16
- args.flash_size = DETECTED_FLASH_SIZES.get(size_id)
- if args.flash_size is None:
- print('Warning: Could not auto-detect Flash size (FlashID=0x%x, SizeID=0x%x), defaulting to 4MB' % (flash_id, size_id))
- args.flash_size = '4MB'
- else:
- print('Auto-detected Flash size:', args.flash_size)
- def _update_image_flash_params(esp, address, args, image):
- """ Modify the flash mode & size bytes if this looks like an executable bootloader image """
- if len(image) < 8:
- return image # not long enough to be a bootloader image
- # unpack the (potential) image header
- magic, _, flash_mode, flash_size_freq = struct.unpack("BBBB", image[:4])
- if address != esp.BOOTLOADER_FLASH_OFFSET or magic != esp.ESP_IMAGE_MAGIC:
- return image # not flashing a bootloader, so don't modify this
- if args.flash_mode != 'keep':
- flash_mode = {'qio':0, 'qout':1, 'dio':2, 'dout': 3}[args.flash_mode]
- flash_freq = flash_size_freq & 0x0F
- if args.flash_freq != 'keep':
- flash_freq = {'40m':0, '26m':1, '20m':2, '80m': 0xf}[args.flash_freq]
- flash_size = flash_size_freq & 0xF0
- if args.flash_size != 'keep':
- flash_size = esp.parse_flash_size_arg(args.flash_size)
- flash_params = struct.pack(b'BB', flash_mode, flash_size + flash_freq)
- if flash_params != image[2:4]:
- print('Flash params set to 0x%04x' % struct.unpack(">H", flash_params))
- image = image[0:2] + flash_params + image[4:]
- return image
- def write_flash(esp, args):
- # set args.compress based on default behaviour:
- # -> if either --compress or --no-compress is set, honour that
- # -> otherwise, set --compress unless --no-stub is set
- if args.compress is None and not args.no_compress:
- args.compress = not args.no_stub
- # verify file sizes fit in flash
- flash_end = flash_size_bytes(args.flash_size)
- for address, argfile in args.addr_filename:
- argfile.seek(0,2) # seek to end
- if address + argfile.tell() > flash_end:
- raise FatalError(("File %s (length %d) at offset %d will not fit in %d bytes of flash. " +
- "Use --flash-size argument, or change flashing address.")
- % (argfile.name, argfile.tell(), address, flash_end))
- argfile.seek(0)
- if args.erase_all:
- erase_flash(esp, args)
- for address, argfile in args.addr_filename:
- if args.no_stub:
- print('Erasing flash...')
- image = pad_to(argfile.read(), 4)
- if len(image) == 0:
- print('WARNING: File %s is empty' % argfile.name)
- continue
- image = _update_image_flash_params(esp, address, args, image)
- calcmd5 = hashlib.md5(image).hexdigest()
- uncsize = len(image)
- if args.compress:
- uncimage = image
- image = zlib.compress(uncimage, 9)
- ratio = uncsize / len(image)
- blocks = esp.flash_defl_begin(uncsize, len(image), address)
- else:
- ratio = 1.0
- blocks = esp.flash_begin(uncsize, address)
- argfile.seek(0) # in case we need it again
- seq = 0
- written = 0
- t = time.time()
- while len(image) > 0:
- print('\rWriting at 0x%08x... (%d %%)' % (address + seq * esp.FLASH_WRITE_SIZE, 100 * (seq + 1) // blocks), end='')
- sys.stdout.flush()
- block = image[0:esp.FLASH_WRITE_SIZE]
- if args.compress:
- esp.flash_defl_block(block, seq, timeout=DEFAULT_TIMEOUT * ratio * 2)
- else:
- # Pad the last block
- block = block + b'\xff' * (esp.FLASH_WRITE_SIZE - len(block))
- esp.flash_block(block, seq)
- image = image[esp.FLASH_WRITE_SIZE:]
- seq += 1
- written += len(block)
- t = time.time() - t
- speed_msg = ""
- if args.compress:
- if t > 0.0:
- speed_msg = " (effective %.1f kbit/s)" % (uncsize / t * 8 / 1000)
- print('\rWrote %d bytes (%d compressed) at 0x%08x in %.1f seconds%s...' % (uncsize, written, address, t, speed_msg))
- else:
- if t > 0.0:
- speed_msg = " (%.1f kbit/s)" % (written / t * 8 / 1000)
- print('\rWrote %d bytes at 0x%08x in %.1f seconds%s...' % (written, address, t, speed_msg))
- try:
- res = esp.flash_md5sum(address, uncsize)
- if res != calcmd5:
- print('File md5: %s' % calcmd5)
- print('Flash md5: %s' % res)
- print('MD5 of 0xFF is %s' % (hashlib.md5(b'\xFF' * uncsize).hexdigest()))
- raise FatalError("MD5 of file does not match data in flash!")
- else:
- print('Hash of data verified.')
- except NotImplementedInROMError:
- pass
- print('\nLeaving...')
- if esp.IS_STUB:
- # skip sending flash_finish to ROM loader here,
- # as it causes the loader to exit and run user code
- esp.flash_begin(0, 0)
- if args.compress:
- esp.flash_defl_finish(False)
- else:
- esp.flash_finish(False)
- if args.verify:
- print('Verifying just-written flash...')
- print('(This option is deprecated, flash contents are now always read back after flashing.)')
- verify_flash(esp, args)
- def image_info(args):
- image = LoadFirmwareImage(args.chip, 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
- idx = 0
- for seg in image.segments:
- idx += 1
- print('Segment %d: %r' % (idx, seg))
- calc_checksum = image.calculate_checksum()
- print('Checksum: %02x (%s)' % (image.checksum,
- 'valid' if image.checksum == calc_checksum else 'invalid - calculated %02x' % calc_checksum))
- try:
- digest_msg = 'Not appended'
- if image.append_digest:
- is_valid = image.stored_digest == image.calc_digest
- digest_msg = "%s (%s)" % (hexify(image.calc_digest).lower(),
- "valid" if is_valid else "invalid")
- print('Validation Hash: %s' % digest_msg)
- except AttributeError:
- pass # ESP8266 image has no append_digest field
- def make_image(args):
- image = ESP8266ROMFirmwareImage()
- 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):
- with open(seg, 'rb') as f:
- data = f.read()
- image.segments.append(ImageSegment(addr, data))
- image.entrypoint = args.entrypoint
- image.save(args.output)
- def elf2image(args):
- e = ELFFile(args.input)
- if args.chip == 'auto': # Default to ESP8266 for backwards compatibility
- print("Creating image for ESP8266...")
- args.chip = 'esp8266'
- if args.chip == 'esp32':
- image = ESP32FirmwareImage()
- image.secure_pad = args.secure_pad
- elif args.version == '1': # ESP8266
- image = ESP8266ROMFirmwareImage()
- else:
- image = ESP8266V2FirmwareImage()
- image.entrypoint = e.entrypoint
- image.segments = e.sections # ELFSection is a subclass of ImageSegment
- image.flash_mode = {'qio':0, 'qout':1, 'dio':2, 'dout': 3}[args.flash_mode]
- image.flash_size_freq = image.ROM_LOADER.FLASH_SIZES[args.flash_size]
- image.flash_size_freq += {'40m':0, '26m':1, '20m':2, '80m': 0xf}[args.flash_freq]
- if args.elf_sha256_offset:
- image.elf_sha256 = e.sha256()
- image.elf_sha256_offset = args.elf_sha256_offset
- image.verify()
- if args.output is None:
- args.output = image.default_output_name(args.input)
- image.save(args.output)
- def read_mac(esp, args):
- mac = esp.read_mac()
- def print_mac(label, mac):
- print('%s: %s' % (label, ':'.join(map(lambda x: '%02x' % x, mac))))
- print_mac("MAC", mac)
- def chip_id(esp, args):
- try:
- chipid = esp.chip_id()
- print('Chip ID: 0x%08x' % chipid)
- except NotSupportedError:
- print('Warning: %s has no Chip ID. Reading MAC instead.' % esp.CHIP_NAME)
- read_mac(esp, args)
- def erase_flash(esp, args):
- print('Erasing flash (this may take a while)...')
- t = time.time()
- esp.erase_flash()
- print('Chip erase completed successfully in %.1fs' % (time.time() - t))
- def erase_region(esp, args):
- print('Erasing region (may be slow depending on size)...')
- t = time.time()
- esp.erase_region(args.address, args.size)
- print('Erase completed successfully in %.1f seconds.' % (time.time() - t))
- def run(esp, args):
- esp.run()
- def flash_id(esp, args):
- flash_id = esp.flash_id()
- print('Manufacturer: %02x' % (flash_id & 0xff))
- flid_lowbyte = (flash_id >> 16) & 0xFF
- print('Device: %02x%02x' % ((flash_id >> 8) & 0xff, flid_lowbyte))
- print('Detected flash size: %s' % (DETECTED_FLASH_SIZES.get(flid_lowbyte, "Unknown")))
- def read_flash(esp, args):
- if args.no_progress:
- flash_progress = None
- else:
- def flash_progress(progress, length):
- msg = '%d (%d %%)' % (progress, progress * 100.0 / length)
- padding = '\b' * len(msg)
- if progress == length:
- padding = '\n'
- sys.stdout.write(msg + padding)
- sys.stdout.flush()
- t = time.time()
- data = esp.read_flash(args.address, args.size, flash_progress)
- t = time.time() - t
- print('\rRead %d bytes at 0x%x in %.1f seconds (%.1f kbit/s)...'
- % (len(data), args.address, t, len(data) / t * 8 / 1000))
- with open(args.filename, 'wb') as f:
- f.write(data)
- def verify_flash(esp, args):
- differences = False
- for address, argfile in args.addr_filename:
- image = pad_to(argfile.read(), 4)
- argfile.seek(0) # rewind in case we need it again
- image = _update_image_flash_params(esp, address, args, image)
- 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 = esp.flash_md5sum(address, image_size)
- expected_digest = hashlib.md5(image).hexdigest()
- 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 = esp.read_flash(address, image_size)
- assert flash != image
- diff = [i for i in range(image_size) if flash[i] != image[i]]
- print('-- verify FAILED: %d differences, first @ 0x%08x' % (len(diff), address + diff[0]))
- for d in diff:
- flash_byte = flash[d]
- image_byte = image[d]
- if PYTHON2:
- flash_byte = ord(flash_byte)
- image_byte = ord(image_byte)
- print(' %08x %02x %02x' % (address + d, flash_byte, image_byte))
- if differences:
- raise FatalError("Verify failed.")
- def read_flash_status(esp, args):
- print('Status value: 0x%04x' % esp.read_status(args.bytes))
- def write_flash_status(esp, args):
- fmt = "0x%%0%dx" % (args.bytes * 2)
- args.value = args.value & ((1 << (args.bytes * 8)) - 1)
- print(('Initial flash status: ' + fmt) % esp.read_status(args.bytes))
- print(('Setting flash status: ' + fmt) % args.value)
- esp.write_status(args.value, args.bytes, args.non_volatile)
- print(('After flash status: ' + fmt) % esp.read_status(args.bytes))
- def version(args):
- print(__version__)
- #
- # End of operations functions
- #
- def main(custom_commandline=None):
- """
- Main function for esptool
- custom_commandline - Optional override for default arguments parsing (that uses sys.argv), can be a list of custom arguments
- as strings.
- """
- parser = argparse.ArgumentParser(description='esptool.py v%s - ESP8266 ROM Bootloader Utility' % __version__, prog='esptool')
- parser.add_argument('--chip', '-c',
- help='Target chip type',
- choices=['auto', 'esp8266', 'esp32'],
- default=os.environ.get('ESPTOOL_CHIP', 'auto'))
- parser.add_argument(
- '--port', '-p',
- help='Serial port device',
- default=os.environ.get('ESPTOOL_PORT', None))
- parser.add_argument(
- '--baud', '-b',
- help='Serial port baud rate used when flashing/reading',
- type=arg_auto_int,
- default=os.environ.get('ESPTOOL_BAUD', ESPLoader.ESP_ROM_BAUD))
- parser.add_argument(
- '--before',
- help='What to do before connecting to the chip',
- choices=['default_reset', 'no_reset', 'no_reset_no_sync'],
- default=os.environ.get('ESPTOOL_BEFORE', 'default_reset'))
- parser.add_argument(
- '--after', '-a',
- help='What to do after esptool.py is finished',
- choices=['hard_reset', 'soft_reset', 'no_reset'],
- default=os.environ.get('ESPTOOL_AFTER', 'hard_reset'))
- parser.add_argument(
- '--no-stub',
- help="Disable launching the flasher stub, only talk to ROM bootloader. Some features will not be available.",
- action='store_true')
- parser.add_argument(
- '--trace', '-t',
- help="Enable trace-level output of esptool.py interactions.",
- action='store_true')
- parser.add_argument(
- '--override-vddsdio',
- help="Override ESP32 VDDSDIO internal voltage regulator (use with care)",
- choices=ESP32ROM.OVERRIDE_VDDSDIO_CHOICES,
- nargs='?')
- subparsers = parser.add_subparsers(
- dest='operation',
- help='Run esptool {command} -h for additional help')
- def add_spi_connection_arg(parent):
- parent.add_argument('--spi-connection', '-sc', help='ESP32-only argument. Override default SPI Flash connection. ' +
- 'Value can be SPI, HSPI or a comma-separated list of 5 I/O numbers to use for SPI flash (CLK,Q,D,HD,CS).',
- action=SpiConnectionAction)
- parser_load_ram = subparsers.add_parser(
- 'load_ram',
- help='Download an image to RAM and execute')
- parser_load_ram.add_argument('filename', help='Firmware image')
- parser_dump_mem = subparsers.add_parser(
- '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')
- parser_read_mem = subparsers.add_parser(
- 'read_mem',
- help='Read arbitrary memory location')
- parser_read_mem.add_argument('address', help='Address to read', type=arg_auto_int)
- parser_write_mem = subparsers.add_parser(
- '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, is_elf2image):
- """ Add common parser arguments for SPI flash properties """
- extra_keep_args = [] if is_elf2image else ['keep']
- auto_detect = not is_elf2image
- parent.add_argument('--flash_freq', '-ff', help='SPI Flash frequency',
- choices=extra_keep_args + ['40m', '26m', '20m', '80m'],
- default=os.environ.get('ESPTOOL_FF', '40m' if is_elf2image else 'keep'))
- parent.add_argument('--flash_mode', '-fm', help='SPI Flash mode',
- choices=extra_keep_args + ['qio', 'qout', 'dio', 'dout'],
- default=os.environ.get('ESPTOOL_FM', 'qio' if is_elf2image else 'keep'))
- parent.add_argument('--flash_size', '-fs', help='SPI Flash size in MegaBytes (1MB, 2MB, 4MB, 8MB, 16M)'
- ' plus ESP8266-only (256KB, 512KB, 2MB-c1, 4MB-c1)',
- action=FlashSizeAction, auto_detect=auto_detect,
- default=os.environ.get('ESPTOOL_FS', 'detect' if auto_detect else '1MB'))
- add_spi_connection_arg(parent)
- parser_write_flash = subparsers.add_parser('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)
- parser_write_flash.add_argument('--erase-all', '-e',
- help='Erase all regions of flash (not just write areas) before programming',
- action="store_true")
- add_spi_flash_subparsers(parser_write_flash, is_elf2image=False)
- 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 on flash ' +
- '(mostly superfluous, data is read back during flashing)', action='store_true')
- compress_args = parser_write_flash.add_mutually_exclusive_group(required=False)
- compress_args.add_argument('--compress', '-z', help='Compress data in transfer (default unless --no-stub is specified)',action="store_true", default=None)
- compress_args.add_argument('--no-compress', '-u', help='Disable data compression during transfer (default if --no-stub is specified)',action="store_true")
- subparsers.add_parser(
- 'run',
- help='Run application code in flash')
- parser_image_info = subparsers.add_parser(
- 'image_info',
- help='Dump headers from an application image')
- parser_image_info.add_argument('filename', help='Image file to parse')
- parser_make_image = subparsers.add_parser(
- '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)
- parser_elf2image = subparsers.add_parser(
- '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')
- parser_elf2image.add_argument('--secure-pad', action='store_true', help='Pad image so once signed it will end on a 64KB boundary. For ESP32 images only.')
- parser_elf2image.add_argument('--elf-sha256-offset', help='If set, insert SHA256 hash (32 bytes) of the input ELF file at specified offset in the binary.',
- type=arg_auto_int, default=None)
- add_spi_flash_subparsers(parser_elf2image, is_elf2image=True)
- subparsers.add_parser(
- 'read_mac',
- help='Read MAC address from OTP ROM')
- subparsers.add_parser(
- 'chip_id',
- help='Read Chip ID from OTP ROM')
- parser_flash_id = subparsers.add_parser(
- 'flash_id',
- help='Read SPI flash manufacturer and device ID')
- add_spi_connection_arg(parser_flash_id)
- parser_read_status = subparsers.add_parser(
- 'read_flash_status',
- help='Read SPI flash status register')
- add_spi_connection_arg(parser_read_status)
- parser_read_status.add_argument('--bytes', help='Number of bytes to read (1-3)', type=int, choices=[1,2,3], default=2)
- parser_write_status = subparsers.add_parser(
- 'write_flash_status',
- help='Write SPI flash status register')
- add_spi_connection_arg(parser_write_status)
- parser_write_status.add_argument('--non-volatile', help='Write non-volatile bits (use with caution)', action='store_true')
- parser_write_status.add_argument('--bytes', help='Number of status bytes to write (1-3)', type=int, choices=[1,2,3], default=2)
- parser_write_status.add_argument('value', help='New value', type=arg_auto_int)
- parser_read_flash = subparsers.add_parser(
- 'read_flash',
- help='Read SPI flash content')
- add_spi_connection_arg(parser_read_flash)
- 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')
- add_spi_flash_subparsers(parser_verify_flash, is_elf2image=False)
- parser_erase_flash = subparsers.add_parser(
- 'erase_flash',
- help='Perform Chip Erase on SPI flash')
- add_spi_connection_arg(parser_erase_flash)
- parser_erase_region = subparsers.add_parser(
- 'erase_region',
- help='Erase a region of the flash')
- add_spi_connection_arg(parser_erase_region)
- parser_erase_region.add_argument('address', help='Start address (must be multiple of 4096)', type=arg_auto_int)
- parser_erase_region.add_argument('size', help='Size of region to erase (must be multiple of 4096)', type=arg_auto_int)
- 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
- expand_file_arguments()
- args = parser.parse_args(custom_commandline)
- print('esptool.py v%s' % __version__)
- # operation function can take 1 arg (args), 2 args (esp, arg)
- # or be a member function of the ESPLoader class.
- if args.operation is None:
- parser.print_help()
- sys.exit(1)
- operation_func = globals()[args.operation]
- if PYTHON2:
- # This function is depreciated in Python3
- operation_args = inspect.getargspec(operation_func).args
- else:
- operation_args = inspect.getfullargspec(operation_func).args
- if operation_args[0] == 'esp': # operation function takes an ESPLoader connection object
- if args.before != "no_reset_no_sync":
- initial_baud = min(ESPLoader.ESP_ROM_BAUD, args.baud) # don't sync faster than the default baud rate
- else:
- initial_baud = args.baud
- if args.port is None:
- ser_list = sorted(ports.device for ports in list_ports.comports())
- print("Found %d serial ports" % len(ser_list))
- else:
- ser_list = [args.port]
- esp = None
- for each_port in reversed(ser_list):
- print("Serial port %s" % each_port)
- try:
- if args.chip == 'auto':
- esp = ESPLoader.detect_chip(each_port, initial_baud, args.before, args.trace)
- else:
- chip_class = {
- 'esp8266': ESP8266ROM,
- 'esp32': ESP32ROM,
- }[args.chip]
- esp = chip_class(each_port, initial_baud, args.trace)
- esp.connect(args.before)
- break
- except (FatalError, OSError) as err:
- if args.port is not None:
- raise
- print("%s failed to connect: %s" % (each_port, err))
- esp = None
- if esp is None:
- raise FatalError("All of the %d available serial ports could not connect to a Espressif device." % len(ser_list))
- print("Chip is %s" % (esp.get_chip_description()))
- print("Features: %s" % ", ".join(esp.get_chip_features()))
- read_mac(esp, args)
- if not args.no_stub:
- esp = esp.run_stub()
- if args.override_vddsdio:
- esp.override_vddsdio(args.override_vddsdio)
- if args.baud > initial_baud:
- try:
- esp.change_baud(args.baud)
- except NotImplementedInROMError:
- print("WARNING: ROM doesn't support changing baud rate. Keeping initial baud rate %d" % initial_baud)
- # override common SPI flash parameter stuff if configured to do so
- if hasattr(args, "spi_connection") and args.spi_connection is not None:
- if esp.CHIP_NAME != "ESP32":
- raise FatalError("Chip %s does not support --spi-connection option." % esp.CHIP_NAME)
- print("Configuring SPI flash mode...")
- esp.flash_spi_attach(args.spi_connection)
- elif args.no_stub:
- print("Enabling default SPI flash mode...")
- # ROM loader doesn't enable flash unless we explicitly do it
- esp.flash_spi_attach(0)
- if hasattr(args, "flash_size"):
- print("Configuring flash size...")
- detect_flash_size(esp, args)
- esp.flash_set_parameters(flash_size_bytes(args.flash_size))
- try:
- operation_func(esp, args)
- finally:
- try: # Clean up AddrFilenamePairAction files
- for address, argfile in args.addr_filename:
- argfile.close()
- except AttributeError:
- pass
- # Handle post-operation behaviour (reset or other)
- if operation_func == load_ram:
- # the ESP is now running the loaded image, so let it run
- print('Exiting immediately.')
- elif args.after == 'hard_reset':
- print('Hard resetting via RTS pin...')
- esp.hard_reset()
- elif args.after == 'soft_reset':
- print('Soft resetting...')
- # flash_finish will trigger a soft reset
- esp.soft_reset(False)
- else:
- print('Staying in bootloader.')
- if esp.IS_STUB:
- esp.soft_reset(True) # exit stub back to ROM loader
- esp._port.close()
- else:
- operation_func(args)
- def expand_file_arguments():
- """ Any argument starting with "@" gets replaced with all values read from a text file.
- Text file arguments can be split by newline or by space.
- Values are added "as-is", as if they were specified in this order on the command line.
- """
- new_args = []
- expanded = False
- for arg in sys.argv:
- if arg.startswith("@"):
- expanded = True
- with open(arg[1:],"r") as f:
- for line in f.readlines():
- new_args += shlex.split(line)
- else:
- new_args.append(arg)
- if expanded:
- print("esptool.py %s" % (" ".join(new_args[1:])))
- sys.argv = new_args
- class FlashSizeAction(argparse.Action):
- """ Custom flash size parser class to support backwards compatibility with megabit size arguments.
- (At next major relase, remove deprecated sizes and this can become a 'normal' choices= argument again.)
- """
- def __init__(self, option_strings, dest, nargs=1, auto_detect=False, **kwargs):
- super(FlashSizeAction, self).__init__(option_strings, dest, nargs, **kwargs)
- self._auto_detect = auto_detect
- def __call__(self, parser, namespace, values, option_string=None):
- try:
- value = {
- '2m': '256KB',
- '4m': '512KB',
- '8m': '1MB',
- '16m': '2MB',
- '32m': '4MB',
- '16m-c1': '2MB-c1',
- '32m-c1': '4MB-c1',
- }[values[0]]
- print("WARNING: Flash size arguments in megabits like '%s' are deprecated." % (values[0]))
- print("Please use the equivalent size '%s'." % (value))
- print("Megabit arguments may be removed in a future release.")
- except KeyError:
- value = values[0]
- known_sizes = dict(ESP8266ROM.FLASH_SIZES)
- known_sizes.update(ESP32ROM.FLASH_SIZES)
- if self._auto_detect:
- known_sizes['detect'] = 'detect'
- if value not in known_sizes:
- raise argparse.ArgumentError(self, '%s is not a known flash size. Known sizes: %s' % (value, ", ".join(known_sizes.keys())))
- setattr(namespace, self.dest, value)
- class SpiConnectionAction(argparse.Action):
- """ Custom action to parse 'spi connection' override. Values are SPI, HSPI, or a sequence of 5 pin numbers separated by commas.
- """
- def __call__(self, parser, namespace, value, option_string=None):
- if value.upper() == "SPI":
- value = 0
- elif value.upper() == "HSPI":
- value = 1
- elif "," in value:
- values = value.split(",")
- if len(values) != 5:
- raise argparse.ArgumentError(self, '%s is not a valid list of comma-separate pin numbers. Must be 5 numbers - CLK,Q,D,HD,CS.' % value)
- try:
- values = tuple(int(v,0) for v in values)
- except ValueError:
- raise argparse.ArgumentError(self, '%s is not a valid argument. All pins must be numeric values' % values)
- if any([v for v in values if v > 33 or v < 0]):
- raise argparse.ArgumentError(self, 'Pin numbers must be in the range 0-33.')
- # encode the pin numbers as a 32-bit integer with packed 6-bit values, the same way ESP32 ROM takes them
- # TODO: make this less ESP32 ROM specific somehow...
- clk,q,d,hd,cs = values
- value = (hd << 24) | (cs << 18) | (d << 12) | (q << 6) | clk
- else:
- raise argparse.ArgumentError(self, '%s is not a valid spi-connection value. ' +
- 'Values are SPI, HSPI, or a sequence of 5 pin numbers CLK,Q,D,HD,CS).' % value)
- setattr(namespace, self.dest, value)
- 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)
- 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:
- 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))
- # Sort the addresses and check for overlapping
- end = 0
- for address, argfile in sorted(pairs):
- argfile.seek(0,2) # seek to end
- size = argfile.tell()
- argfile.seek(0)
- sector_start = address & ~(ESPLoader.FLASH_SECTOR_SIZE - 1)
- sector_end = ((address + size + ESPLoader.FLASH_SECTOR_SIZE - 1) & ~(ESPLoader.FLASH_SECTOR_SIZE - 1)) - 1
- if sector_start < end:
- message = 'Detected overlap at address: 0x%x for file: %s' % (address, argfile.name)
- raise argparse.ArgumentError(self, message)
- end = sector_end
- setattr(namespace, self.dest, pairs)
- # Binary stub code (see flasher_stub dir for source & details)
- ESP8266ROM.STUB_CODE = eval(zlib.decompress(base64.b64decode(b"""
- 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\
- """)))
- ESP32ROM.STUB_CODE = eval(zlib.decompress(base64.b64decode(b"""
- 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\
- """)))
- def _main():
- try:
- main()
- except FatalError as e:
- print('\nA fatal error occurred: %s' % e)
- sys.exit(2)
- if __name__ == '__main__':
- _main()
|