Unverified Commit eaac369d authored by Johny Mattsson's avatar Johny Mattsson Committed by GitHub
Browse files

LFS support for ESP32 NodeMCU (#2801)

* Port LFS from ESP8266 to ESP32
parent 7cb61a27
# Espressif ESP32 Partition Table for 2MB flash
# Name, Type, SubType, Offset, Size
nvs, data, nvs, 0x9000, 0x6000
phy_init, data, phy, 0xf000, 0x1000
factory, app, factory, 0x10000, 0x180000
# 0xC2 => NodeMCU, 0x0 => Spiffs
nodemcuspiffs, 0xC2, 0x0, , 0x70000
nvs, data, nvs, 0x9000, 0x6000
phy_init, data, phy, 0xf000, 0x1000
factory, app, factory, 0x10000, 0x180000
# Type 0xC2 => NodeMCU. SubTypes: 0x00 = SPIFFS, 0x01 = LFS
lfs, 0xC2, 0x01, , 0x10000
nodemcuspiffs, 0xC2, 0x00, , 0x60000
# Espressif ESP32 Partition Table for 2MB flash
# Name, Type, SubType, Offset, Size
nvs, data, nvs, 0x9000, 0x6000
phy_init, data, phy, 0xf000, 0x1000
factory, app, factory, 0x10000, 0x180000
nvs, data, nvs, 0x9000, 0x6000
phy_init, data, phy, 0xf000, 0x1000
factory, app, factory, 0x10000, 0x180000
# Type 0xC2 => NodeMCU. SubTypes: 0x00 = SPIFFS, 0x01 = LFS
lfs, 0xC2, 0x01, , 0x10000
nodemcuspiffs, 0xC2, 0x00, , 0x70000
-include $(PROJECT_PATH)/build/include/config/auto.conf
uzlib - Deflate/Zlib-compatible LZ77 compression library
======================================================
This is a heavily modified and cut down version of Paul Sokolovsky's
uzlib library. This library has exported routines which
- Can compress data to a Deflate-compatible bitstream, albeit with lower
compression ratio than the Zlib Deflate algorithm as a static Deflate Huffman
tree encoding is used for bitstream). Note that since this compression is
in RAM and requires ~4 bytes per byte of the input record, should only be
called for compressing small records on the ESP8266.
- Can decompress any valid Deflate, Zlib, and Gzip (further called just
"Deflate") bitstream less than 16Kb, and any arbitrary length stream
compressed by the uzlib compressor.
uzlib aims for minimal code size and runtime memory requirements, and thus
is suitable for embedded systems and IoT devices such as the ESP8266.
uzlib is based on:
- tinf library by Joergen Ibsen (Deflate decompression)
- Deflate Static Huffman tree routines by Simon Tatham
- LZ77 compressor by Paul Sokolovsky provided my initial inspiration, but
I ended up rewriting this following RFC 1951 to get improved compression
performance.
The above 16Kb limitation arises from the RFC 1951 use of a 32Kb dictionary,
which is impractical on a chipset with only ~40 Kb RAM avialable to
applications.
The relevant copyright statements are provided in the source files which
use this code.
uzlib library is licensed under Zlib license.
COMPONENT_ADD_INCLUDEDIRS:=.
/*
* CRC32 checksum
*
* Copyright (c) 1998-2003 by Joergen Ibsen / Jibz
* All Rights Reserved
*
* http://www.ibsensoftware.com/
*
* This software is provided 'as-is', without any express
* or implied warranty. In no event will the authors be
* held liable for any damages arising from the use of
* this software.
*
* Permission is granted to anyone to use this software
* for any purpose, including commercial applications,
* and to alter it and redistribute it freely, subject to
* the following restrictions:
*
* 1. The origin of this software must not be
* misrepresented; you must not claim that you
* wrote the original software. If you use this
* software in a product, an acknowledgment in
* the product documentation would be appreciated
* but is not required.
*
* 2. Altered source versions must be plainly marked
* as such, and must not be misrepresented as
* being the original software.
*
* 3. This notice may not be removed or altered from
* any source distribution.
*/
/*
* CRC32 algorithm taken from the zlib source, which is
* Copyright (C) 1995-1998 Jean-loup Gailly and Mark Adler
*/
#include <stdint.h>
static const unsigned int tinf_crc32tab[16] = {
0x00000000, 0x1db71064, 0x3b6e20c8, 0x26d930ac, 0x76dc4190,
0x6b6b51f4, 0x4db26158, 0x5005713c, 0xedb88320, 0xf00f9344,
0xd6d6a3e8, 0xcb61b38c, 0x9b64c2b0, 0x86d3d2d4, 0xa00ae278,
0xbdbdf21c
};
/* crc is previous value for incremental computation, 0xffffffff initially */
uint32_t uzlib_crc32(const void *data, unsigned int length, uint32_t crc)
{
const unsigned char *buf = (const unsigned char *)data;
unsigned int i;
for (i = 0; i < length; ++i)
{
crc ^= buf[i];
crc = tinf_crc32tab[crc & 0x0f] ^ (crc >> 4);
crc = tinf_crc32tab[crc & 0x0f] ^ (crc >> 4);
}
// return value suitable for passing in next time, for final value invert it
return crc/* ^ 0xffffffff*/;
}
/*
* uzlib - tiny deflate/inflate library (deflate, gzip, zlib)
*
* Copyright (c) 2003 by Joergen Ibsen / Jibz
* All Rights Reserved
* http://www.ibsensoftware.com/
*
* Copyright (c) 2014-2016 by Paul Sokolovsky
*/
#ifndef UZLIB_INFLATE_H
#define UZLIB_INFLATE_H
#include <setjmp.h>
#include <stdint.h>
#include <stdlib.h>
#define uz_malloc malloc
#define uz_free free
#if defined(__XTENSA__)
#define UZLIB_THROW(v) longjmp(unwindAddr, (v))
#define UZLIB_SETJMP setjmp
#else /* Host */
extern int dbg_break(void);
#if defined(_MSC_VER) || defined(__MINGW32__) //msvc requires old name for longjmp
#define UZLIB_THROW(v) {dbg_break();longjmp(unwindAddr, (v));}
#define UZLIB_SETJMP(n) setjmp(n)
#else
#define UZLIB_THROW(v) {dbg_break();_longjmp(unwindAddr, (v));}
#define UZLIB_SETJMP(n) _setjmp(n)
#endif
#endif /* defined(__XTENSA__) */
extern jmp_buf unwindAddr;
/* ok status, more data produced */
#define UZLIB_OK 0
/* end of compressed stream reached */
#define UZLIB_DONE 1
#define UZLIB_DATA_ERROR (-3)
#define UZLIB_CHKSUM_ERROR (-4)
#define UZLIB_DICT_ERROR (-5)
#define UZLIB_MEMORY_ERROR (-6)
/* checksum types */
#define UZLIB_CHKSUM_NONE 0
#define UZLIB_CHKSUM_ADLER 1
#define UZLIB_CHKSUM_CRC 2
/* Gzip header codes */
#define UZLIB_FTEXT 1
#define UZLIB_FHCRC 2
#define UZLIB_FEXTRA 4
#define UZLIB_FNAME 8
#define UZLIB_FCOMMENT 16
/* Compression API */
typedef struct uzlib_data UZLIB_DATA;
int uzlib_inflate (uint8_t (*)(void), void (*)(uint8_t),
uint8_t (*)(uint32_t), uint32_t len, uint32_t *crc, void **state);
int uzlib_compress (uint8_t **dest, uint32_t *destLen,
const uint8_t *src, uint32_t srcLen);
/* Checksum API */
/* crc is previous value for incremental computation, 0xffffffff initially */
uint32_t uzlib_crc32(const void *data, uint32_t length, uint32_t crc);
#endif /* UZLIB_INFLATE_H */
/*
* This implementation draws heavily on the work down by Paul Sokolovsky
* (https://github.com/pfalcon) and his uzlib library which in turn uses
* work done by Joergen Ibsen, Simon Tatham and others. All of this work
* is under an unrestricted right to use subject to copyright attribution.
* Two copyright wordings (variants A and B) are following.
*
* (c) statement A initTables, copy, literal
*
* The remainder of this code has been written by me, Terry Ellison 2018,
* under the standard NodeMCU MIT licence, but is available to the other
* contributors to this source under any permissive licence.
*
* My primary algorthmic reference is RFC 1951: "DEFLATE Compressed Data
* Format Specification version 1.3", dated May 1996.
*
* Also because the code in this module is drawn from different sources,
* the different coding practices can be confusing, I have standardised
* the source by:
*
* - Adopting the 2 indent rule as in the rest of the firmware
*
* - I have replaced the various mix of char, unsigned char and uchar
* by the single uchar type; ditto for ushort and uint.
*
* - All internal (non-exported) functions and data are static
*
* - Only exported functions and data have the module prefix. All
* internal (static) variables and fields are lowerCamalCase.
*
***********************************************************************
* Copyright statement A for Zlib (RFC1950 / RFC1951) compression for PuTTY.
PuTTY is copyright 1997-2014 Simon Tatham.
Portions copyright Robert de Bath, Joris van Rantwijk, Delian
Delchev, Andreas Schultz, Jeroen Massar, Wez Furlong, Nicolas Barry,
Justin Bradford, Ben Harris, Malcolm Smith, Ahmad Khalifa, Markus
Kuhn, Colin Watson, and CORE SDI S.A.
Permission is hereby granted, free of charge, to any person
obtaining a copy of this software and associated documentation files
(the "Software"), to deal in the Software without restriction,
including without limitation the rights to use, copy, modify, merge,
publish, distribute, sublicense, and/or sell copies of the Software,
and to permit persons to whom the Software is furnished to do so,
subject to the following conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE COP--YRIGHT HOLDERS BE LIABLE
FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
************************************************************************
Copyright statement B for genlz77 functions:
*
* genlz77 - Generic LZ77 compressor
*
* Copyright (c) 2014 by Paul Sokolovsky
*
* This software is provided 'as-is', without any express
* or implied warranty. In no event will the authors be
* held liable for any damages arising from the use of
* this software.
*
* Permission is granted to anyone to use this software
* for any purpose, including commercial applications,
* and to alter it and redistribute it freely, subject to
* the following restrictions:
*
* 1. The origin of this software must not be
* misrepresented; you must not claim that you
* wrote the original software. If you use this
* software in a product, an acknowledgment in
* the product documentation would be appreciated
* but is not required.
*
* 2. Altered source versions must be plainly marked
* as such, and must not be misrepresented as
* being the original software.
*
* 3. This notice may not be removed or altered from
* any source distribution.
*/
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <assert.h>
#include "uzlib.h"
jmp_buf unwindAddr;
/* Minimum and maximum length of matches to look for, inclusive */
#define MIN_MATCH 3
#define MAX_MATCH 258
/* Max offset of the match to look for, inclusive */
#define MAX_OFFSET 16384 // 32768 //
#define OFFSET16_MASK 0x7FFF
#define NULL_OFFSET 0xFFFF
#if MIN_MATCH < 3
#error "Encoding requires a minium match of 3 bytes"
#endif
#define SIZE(a) (sizeof(a)/sizeof(*a)) /* no of elements in array */
#ifdef __XTENSA__
#define RAM_COPY_BYTE_ARRAY(c,s,sl) uchar *c = alloca(sl); memcpy(c,s,(sl))
#else
#define RAM_COPY_BYTE_ARRAY(c,s,sl) uchar *c = s;
#endif
#define FREE(v) if (v) uz_free(v)
typedef uint8_t uchar;
typedef uint16_t ushort;
typedef uint32_t uint;
#ifdef DEBUG_COUNTS
#define DBG_PRINT(...) printf(__VA_ARGS__)
#define DBG_COUNT(n) (debugCounts[n]++)
#define DBG_ADD_COUNT(n,m) (debugCounts[n]+=m)
int debugCounts[20];
#else
#define DBG_PRINT(...)
#define DBG_COUNT(n)
#define DBG_ADD_COUNT(n,m)
#endif
int dbg_break(void) {return 1;}
typedef struct {
ushort code, extraBits, min, max;
} codeRecord;
struct dynTables {
ushort *hashChain;
ushort *hashTable;
ushort hashMask;
ushort hashSlots;
ushort hashBits;
ushort dictLen;
const uchar bitrevNibble[16];
const codeRecord lenCodes[285-257+1];
const codeRecord distCodes[29-0+1];
} *dynamicTables;
struct outputBuf {
uchar *buffer;
uint len, size;
uint inLen, inNdx;
uint bits, nBits;
uint compDisabled;
} *oBuf;
/*
* Set up the constant tables used to drive the compression
*
* Constants are stored in flash memory on the ESP8266 NodeMCU firmware
* builds, but only word aligned data access are supported in hardare so
* short and byte accesses are handled by a S/W exception handler and are
* SLOW. RAM is also at premium, so these short routines are driven by
* byte vectors copied into RAM and then used to generate temporary RAM
* tables, which are the same as the above statically declared versions.
*
* This might seem a bit convolved but this runs faster and takes up less
* memory than the original version. This code also works fine on the
* x86-64s so we just use one code variant.
*
* Note that fixed Huffman trees as defined in RFC 1951 Sec 3.2.5 are
* always used. Whilst dynamic trees can give better compression for
* larger blocks, this comes at a performance hit of having to compute
* these trees. Fixed trees give better compression performance on short
* blocks and significantly reduce compression times.
*
* The following defines are used to initialise these tables.
*/
#define lenCodes_GEN \
"\x03\x01\x01\x01\x01\x01\x01\x01\xff\x02\x02\x02\x02\xff\x04\x04\x04\x04" \
"\xff\x08\x08\x08\x08\xff\x10\x10\x10\x10\xff\x20\x20\x20\x1f\xff\x01\x00"
#define lenCodes_LEN 29
#define distCodes_GEN \
"\x01\x01\x01\x01\xff\x02\x02\xff\x04\x04\xff\x08\x08\xff\x10\x10\xff" \
"\x20\x20\xff\x40\x40\xff\x86\x86\xff\x87\x87\xff\x88\x88\xff" \
"\x89\x89\xff\x8a\x8a\xff\x8b\x8b\xff\x8c\x8c"
#define distCodes_LEN 30
#define BITREV16 "\x0\x8\x4\xc\x2\xa\x6\xe\x1\x9\x5\xd\x3\xb\x7\xf"
static void genCodeRecs (const codeRecord *rec, ushort len,
char *init, int initLen,
ushort start, ushort m0) {
DBG_COUNT(0);
int i, b=0, m=0, last=m0;
RAM_COPY_BYTE_ARRAY(c, (uchar *)init,initLen);
codeRecord *p = (codeRecord *) rec;
for (i = start; i < start+len; i++, c++) {
if (*c == 0xFF)
b++, c++;
m += (*c & 0x80) ? 2 << (*c & 0x1F) : *c;
*p++ = (codeRecord) {i, b, last + 1, (last = m)};
}
}
static void initTables (uint chainLen, uint hashSlots) {
DBG_COUNT(1);
uint dynamicSize = sizeof(struct dynTables) +
sizeof(struct outputBuf) +
chainLen * sizeof(ushort) +
hashSlots * sizeof(ushort);
struct dynTables *dt = uz_malloc(dynamicSize);
memset(dt, 0, dynamicSize);
dynamicTables = dt;
/* Do a single malloc for dymanic tables and assign addresses */
if(!dt )
UZLIB_THROW(UZLIB_MEMORY_ERROR);
memcpy((uchar*)dt->bitrevNibble, BITREV16, 16);
oBuf = (struct outputBuf *)(dt+1);
dt->hashTable = (ushort *)(oBuf+1);
dt->hashChain = dt->hashTable + hashSlots;
dt->hashSlots = hashSlots;
dt->hashMask = hashSlots - 1;
/* As these are offset rather than pointer, 0 is a valid offset */
/* (unlike NULL), so 0xFFFF is used to denote an unset value */
memset(dt->hashTable, -1, sizeof(ushort)*hashSlots);
memset(dt->hashChain, -1, sizeof(ushort)*chainLen);
/* Generate the code recors for the lenth and distance code tables */
genCodeRecs(dt->lenCodes, SIZE(dt->lenCodes),
lenCodes_GEN, sizeof(lenCodes_GEN),
257,2);
((codeRecord *)(dynamicTables->lenCodes+285-257))->extraBits=0; /* odd ball entry */
genCodeRecs(dt->distCodes, SIZE(dt->distCodes),
distCodes_GEN, sizeof(distCodes_GEN),
0,0);
}
/*
* Routines to output bit streams and byte streams to the output buffer
*/
void resizeBuffer(void) {
uchar *nb;
DBG_COUNT(2);
/* The outbuf is given an initial size estimate but if we are running */
/* out of space then extropolate size using current compression */
double newEstimate = (((double) oBuf->len)*oBuf->inLen) / oBuf->inNdx;
oBuf->size = 128 + (uint) newEstimate;
if (!(nb = realloc(oBuf->buffer, oBuf->size)))
UZLIB_THROW(UZLIB_MEMORY_ERROR);
oBuf->buffer = nb;
}
void outBits(ushort bits, int nBits) {
DBG_COUNT(3);
oBuf->bits |= bits << oBuf->nBits;
oBuf->nBits += nBits;
if (oBuf->len >= oBuf->size - sizeof(bits))
resizeBuffer();
while (oBuf->nBits >= 8) {
DBG_PRINT("%02x-", oBuf->bits & 0xFF);
oBuf->buffer[oBuf->len++] = oBuf->bits & 0xFF;
oBuf->bits >>= 8;
oBuf->nBits -= 8;
}
}
void outBitsRev(uchar bits, int nBits) {
DBG_COUNT(4);
/* Note that bit reversal only operates on an 8-bit bits field */
uchar bitsRev = (dynamicTables->bitrevNibble[bits & 0x0f]<<4) |
dynamicTables->bitrevNibble[bits>>4];
outBits(bitsRev, nBits);
}
void outBytes(void *bytes, int nBytes) {
DBG_COUNT(5);
int i;
if (oBuf->len >= oBuf->size - nBytes)
resizeBuffer();
/* Note that byte output dumps any bits data so the caller must */
/* flush this first, if necessary */
oBuf->nBits = oBuf->bits = 0;
for (i = 0; i < nBytes; i++) {
DBG_PRINT("%02x-", *((uchar*)bytes+i));
oBuf->buffer[oBuf->len++] = *((uchar*)bytes+i);
}
}
/*
* Output an literal byte as an 8 or 9 bit code
*/
void literal (uchar c) {
DBG_COUNT(6);
DBG_PRINT("sym: %02x %c\n", c, c);
if (oBuf->compDisabled) {
/* We're in an uncompressed block, so just output the byte. */
outBits(c, 8);
} else if (c <= 143) {
/* 0 through 143 are 8 bits long starting at 00110000. */
outBitsRev(0x30 + c, 8);
} else {
/* 144 through 255 are 9 bits long starting at 110010000. */
outBits(1, 1);
outBitsRev(0x90 - 144 + c, 8);
}
}
/*
* Output a dictionary (distance, length) pars as bitstream codes
*/
void copy (int distance, int len) {
DBG_COUNT(7);
const codeRecord *lenCodes = dynamicTables->lenCodes, *l;
const codeRecord *distCodes = dynamicTables->distCodes, *d;
int i, j, k;
assert(!oBuf->compDisabled);
while (len > 0) {
/*
* We can transmit matches of lengths 3 through 258
* inclusive. So if len exceeds 258, we must transmit in
* several steps, with 258 or less in each step.
*
* Specifically: if len >= 261, we can transmit 258 and be
* sure of having at least 3 left for the next step. And if
* len <= 258, we can just transmit len. But if len == 259
* or 260, we must transmit len-3.
*/
int thislen = (len > 260 ? 258 : len <= 258 ? len : len - 3);
len -= thislen;
/*
* Binary-search to find which length code we're
* transmitting.
*/
i = -1;
j = lenCodes_LEN;
while (1) {
assert(j - i >= 2);
k = (j + i) / 2;
if (thislen < lenCodes[k].min)
j = k;
else if (thislen > lenCodes[k].max)
i = k;
else {
l = &lenCodes[k];
break; /* found it! */
}
}
/*
* Transmit the length code. 256-279 are seven bits
* starting at 0000000; 280-287 are eight bits starting at
* 11000000.
*/
if (l->code <= 279) {
outBitsRev((l->code - 256) * 2, 7);
} else {
outBitsRev(0xc0 - 280 + l->code, 8);
}
/*
* Transmit the extra bits.
*/
if (l->extraBits)
outBits(thislen - l->min, l->extraBits);
/*
* Binary-search to find which distance code we're
* transmitting.
*/
i = -1;
j = distCodes_LEN;
while (1) {
assert(j - i >= 2);
k = (j + i) / 2;
if (distance < distCodes[k].min)
j = k;
else if (distance > distCodes[k].max)
i = k;
else {
d = &distCodes[k];
break; /* found it! */
}
}
/*
* Transmit the distance code. Five bits starting at 00000.
*/
outBitsRev(d->code * 8, 5);
/*
* Transmit the extra bits.
*/
if (d->extraBits)
outBits(distance - d->min, d->extraBits);
}
}
/*
* Block compression uses a hashTable to index into a set of search
* chainList, where each chain links together the triples of chars within
* the dictionary (the last MAX_OFFSET bytes of the input buffer) with
* the same hash index. So for compressing a file of 200Kb, say, with a
* 16K dictionary (the largest that we can inflate within the memory
* constraints of the ESP8266), the chainList is 16K slots long, and the
* hashTable is 4K slots long, so a typical chain will have 4 links.
*
* These two tables use 16-bit ushort offsets rather than pointers to
* save memory (essential on the ESP8266).
*
* As per RFC 1951 sec 4, we also implement a "lazy match" procedure
*/
void uzlibCompressBlock(const uchar *src, uint srcLen) {
int i, j, k, l;
uint hashMask = dynamicTables->hashMask;
ushort *hashChain = dynamicTables->hashChain;
ushort *hashTable = dynamicTables->hashTable;
uint hashShift = 24 - dynamicTables->hashBits;
uint lastOffset = 0, lastLen = 0;
oBuf->inLen = srcLen; /* used for output buffer resizing */
DBG_COUNT(9);
for (i = 0; i <= ((int)srcLen) - MIN_MATCH; i++) {
/*
* Calculate a hash on the next three chars using the liblzf hash
* function, then use this via the hashTable to index into the chain
* of triples within the dictionary window which have the same hash.
*
* Note that using 16-bit offsets requires a little manipulation to
* handle wrap-around and recover the correct offset, but all other
* working uses uint offsets simply because the compiler generates
* faster (and smaller in the case of the ESP8266) code.
*
* Also note that this code also works for any tail 2 literals; the
* hash will access beyond the array and will be incorrect, but
* these can't match and will flush the last cache.
*/
const uchar *this = src + i, *comp;
uint base = i & ~OFFSET16_MASK;
uint iOffset = i - base;
uint maxLen = srcLen - i;
uint matchLen = MIN_MATCH - 1;
uint matchOffset = 0;
uint v = (this[0] << 16) | (this[1] << 8) | this[2];
uint hash = ((v >> hashShift) - v) & hashMask;
uint nextOffset = hashTable[hash];
oBuf->inNdx = i; /* used for output buffer resizing */
DBG_COUNT(10);
if (maxLen>MAX_MATCH)
maxLen = MAX_MATCH;
hashTable[hash] = iOffset;
hashChain[iOffset & (MAX_OFFSET-1)] = nextOffset;
for (l = 0; nextOffset != NULL_OFFSET && l<60; l++) {
DBG_COUNT(11);
/* handle the case where base has bumped */
j = base + nextOffset - ((nextOffset < iOffset) ? 0 : (OFFSET16_MASK + 1));
if (i - j > MAX_OFFSET)
break;
for (k = 0, comp = src + j; this[k] == comp[k] && k < maxLen; k++)
{}
DBG_ADD_COUNT(12, k);
if (k > matchLen) {
matchOffset = i - j;
matchLen = k;
}
nextOffset = hashChain[nextOffset & (MAX_OFFSET-1)];
}
if (lastOffset) {
if (matchOffset == 0 || lastLen >= matchLen ) {
/* ignore this match (or not) and process last */
DBG_COUNT(14);
copy(lastOffset, lastLen);
DBG_PRINT("dic: %6x %6x %6x\n", i-1, lastLen, lastOffset);
i += lastLen - 1 - 1;
lastOffset = lastLen = 0;
} else {
/* ignore last match and emit a symbol instead; cache this one */
DBG_COUNT(15);
literal(this[-1]);
lastOffset = matchOffset;
lastLen = matchLen;
}
} else { /* no last match */
if (matchOffset) {
DBG_COUNT(16);
/* cache this one */
lastOffset = matchOffset;
lastLen = matchLen;
} else {
DBG_COUNT(17);
/* emit a symbol; last already clear */
literal(this[0]);
}
}
}
if (lastOffset) { /* flush cached match if any */
copy(lastOffset, lastLen);
DBG_PRINT("dic: %6x %6x %6x\n", i, lastLen, lastOffset);
i += lastLen - 1;
}
while (i < srcLen)
literal(src[i++]); /* flush the last few bytes if needed */
}
/*
* This compress wrapper treats the input stream as a single block for
* compression using the default Static huffman block encoding
*/
int uzlib_compress (uchar **dest, uint *destLen, const uchar *src, uint srcLen) {
uint crc = ~uzlib_crc32(src, srcLen, ~0);
uint chainLen = srcLen < MAX_OFFSET ? srcLen : MAX_OFFSET;
uint hashSlots, i, j;
int status;
uint FLG_MTIME[] = {0x00088b1f, 0};
ushort XFL_OS = 0x0304;
/* The hash table has 4K slots for a 16K chain and scaling down */
/* accordingly, for an average chain length of 4 links or thereabouts */
for (i = 256, j = 8 - 2; i < chainLen; i <<= 1)
j++;
hashSlots = i >> 2;
if ((status = UZLIB_SETJMP(unwindAddr)) == 0) {
initTables(chainLen, hashSlots);
oBuf->size = srcLen/5; /* initial guess of a 5x compression ratio */
oBuf->buffer = uz_malloc(oBuf->size);
dynamicTables->hashSlots = hashSlots;
dynamicTables->hashBits = j;
if(!oBuf->buffer ) {
status = UZLIB_MEMORY_ERROR;
} else {
/* Output gzip and block headers */
outBytes(FLG_MTIME, sizeof(FLG_MTIME));
outBytes(&XFL_OS, sizeof(XFL_OS));
outBits(1, 1); /* Final block */
outBits(1, 2); /* Static huffman block */
uzlibCompressBlock(src, srcLen); /* Do the compress */
/* Output block finish */
outBits(0, 7); /* close block */
outBits(0, 7); /* Make sure all bits are flushed */
outBytes(&crc, sizeof(crc));
outBytes(&srcLen, sizeof(srcLen));
status = UZLIB_OK;
}
} else {
status = UZLIB_OK;
}
for (i=0; i<20;i++) DBG_PRINT("count %u = %u\n",i,debugCounts[i]);
if (status == UZLIB_OK) {
uchar *trimBuf = realloc(oBuf->buffer, oBuf->len);
*dest = trimBuf ? trimBuf : oBuf->buffer;
*destLen = oBuf->len;
} else {
*dest = NULL;
*destLen = 0;
FREE(oBuf->buffer);
}
FREE(dynamicTables);
return status;
}
/*
* tinfgzip.c - tiny gzip decompressor
* tinflate.c - tiny inflate
*
* The original source headers as below for licence compliance and in
* full acknowledgement of the originitor contributions. Modified by
* Terry Ellison 2018 to provide lightweight stream inflate for NodeMCU
* Lua. Modifications are under the standard NodeMCU MIT licence.
*
* Copyright (c) 2003 by Joergen Ibsen / Jibz
* All Rights Reserved
* http://www.ibsensoftware.com/
*
* Copyright (c) 2014-2016 by Paul Sokolovsky
*
* This software is provided 'as-is', without any express
* or implied warranty. In no event will the authors be
* held liable for any damages arising from the use of
* this software.
*
* Permission is granted to anyone to use this software
* for any purpose, including commercial applications,
* and to alter it and redistribute it freely, subject to
* the following restrictions:
*
* 1. The origin of this software must not be
* misrepresented; you must not claim that you
* wrote the original software. If you use this
* software in a product, an acknowledgment in
* the product documentation would be appreciated
* but is not required.
*
* 2. Altered source versions must be plainly marked
* as such, and must not be misrepresented as
* being the original software.
*
* 3. This notice may not be removed or altered from
* any source distribution.
*/
#include <string.h>
#include <stdio.h>
#include "uzlib.h"
#ifdef DEBUG_COUNTS
#define DBG_PRINT(...) printf(__VA_ARGS__)
#define DBG_COUNT(n) (debugCounts[n]++)
#define DBG_ADD_COUNT(n,m) (debugCounts[n]+=m)
int debugCounts[20];
#else
#define NDEBUG
#define DBG_PRINT(...)
#define DBG_COUNT(n)
#define DBG_ADD_COUNT(n,m)
#endif
#define SIZE(arr) (sizeof(arr) / sizeof(*(arr)))
jmp_buf unwindAddr;
int dbg_break(void) {return 1;}
typedef uint8_t uchar;
typedef uint16_t ushort;
typedef uint32_t uint;
/* data structures */
typedef struct {
ushort table[16]; /* table of code length counts */
ushort trans[288]; /* code -> symbol translation table */
} UZLIB_TREE;
struct uzlib_data {
/*
* extra bits and base tables for length and distance codes
*/
uchar lengthBits[30];
ushort lengthBase[30];
uchar distBits[30];
ushort distBase[30];
/*
* special ordering of code length codes
*/
uchar clcidx[19];
/*
* dynamic length/symbol and distance trees
*/
UZLIB_TREE ltree;
UZLIB_TREE dtree;
/*
* methods encapsulate handling of the input and output streams
*/
uchar (*get_byte)(void);
void (*put_byte)(uchar b);
uchar (*recall_byte)(uint offset);
/*
* Other state values
*/
uint destSize;
uint tag;
uint bitcount;
uint lzOffs;
int bType;
int bFinal;
uint curLen;
uint checksum;
};
/*
* Note on changes to layout, naming, etc. This module combines extracts
* from 3 code files from two sources (Sokolovsky, Ibsen et al) with perhaps
* 30% from me Terry Ellison. These sources had inconsistent layout and
* naming conventions, plus extra condtional handling of platforms that
* cannot support NodeMCU. (This is intended to be run compiled and executed
* on GCC POSIX and XENTA newlib environments.) So I have (1) reformatted
* this file in line with NodeMCU rules; (2) demoted all private data and
* functions to static and removed the redundant name prefixes; (3) reordered
* functions into a more logic order; (4) added some ESP architecture
* optimisations, for example these IoT devices are very RAM limited, so
* statically allocating large RAM blocks is against programming guidelines.
*/
static void skip_bytes(UZLIB_DATA *d, int num) {
if (num) /* Skip a fixed number of bytes */
while (num--) (void) d->get_byte();
else /* Skip to next nullchar */
while (d->get_byte()) {}
}
static uint16_t get_uint16(UZLIB_DATA *d) {
uint16_t v = d->get_byte();
return v | (d->get_byte() << 8);
}
static uint get_le_uint32 (UZLIB_DATA *d) {
uint v = get_uint16(d);
return v | ((uint) get_uint16(d) << 16);
}
/* get one bit from source stream */
static int getbit (UZLIB_DATA *d) {
uint bit;
/* check if tag is empty */
if (!d->bitcount--) {
/* load next tag */
d->tag = d->get_byte();
d->bitcount = 7;
}
/* shift bit out of tag */
bit = d->tag & 0x01;
d->tag >>= 1;
return bit;
}
/* read a num bit value from a stream and add base */
static uint read_bits (UZLIB_DATA *d, int num, int base) {
/* This is an optimised version which doesn't call getbit num times */
if (!num)
return base;
uint i, n = (((uint)-1)<<num);
for (i = d->bitcount; i < num; i +=8)
d->tag |= ((uint)d->get_byte()) << i;
n = d->tag & ~n;
d->tag >>= num;
d->bitcount = i - num;
return base + n;
}
/* --------------------------------------------------- *
* -- uninitialized global data (static structures) -- *
* --------------------------------------------------- */
/*
* Constants are stored in flash memory on the ESP8266 NodeMCU firmware
* builds, but only word aligned data access are supported in hardare so
* short and byte accesses are handled by a S/W exception handler and
* are SLOW. RAM is also at premium, especially static initialised vars,
* so we malloc a single block on first call to hold all tables and call
* the dynamic generator to generate malloced RAM tables that have the
* same content as the above statically declared versions.
*
* This might seem a bit convolved but this runs faster and takes up
* less memory than the static version on the ESP8266.
*/
#define CLCIDX_INIT \
"\x10\x11\x12\x00\x08\x07\x09\x06\x0a\x05\x0b\x04\x0c\x03\x0d\x02\x0e\x01\x0f"
/* ----------------------- *
* -- utility functions -- *
* ----------------------- */
/* build extra bits and base tables */
static void build_bits_base (uchar *bits, ushort *base,
int delta, int first) {
int i, sum;
/* build bits table */
for (i = 0; i < delta; ++i) bits[i] = 0;
for (i = 0; i < 30 - delta; ++i) bits[i + delta] = i / delta;
/* build base table */
for (sum = first, i = 0; i < 30; ++i) {
base[i] = sum;
sum += 1 << bits[i];
}
}
/* build the fixed huffman trees */
static void build_fixed_trees (UZLIB_TREE *lt, UZLIB_TREE *dt) {
int i;
/* build fixed length tree */
for (i = 0; i < 7; ++i) lt->table[i] = 0;
lt->table[7] = 24;
lt->table[8] = 152;
lt->table[9] = 112;
for (i = 0; i < 24; ++i) lt->trans[i] = 256 + i;
for (i = 0; i < 144; ++i) lt->trans[24 + i] = i;
for (i = 0; i < 8; ++i) lt->trans[24 + 144 + i] = 280 + i;
for (i = 0; i < 112; ++i) lt->trans[24 + 144 + 8 + i] = 144 + i;
/* build fixed distance tree */
for (i = 0; i < 5; ++i) dt->table[i] = 0;
dt->table[5] = 32;
for (i = 0; i < 32; ++i) dt->trans[i] = i;
}
/* given an array of code lengths, build a tree */
static void build_tree (UZLIB_TREE *t, const uchar *lengths, uint num) {
ushort offs[16];
uint i, sum;
/* clear code length count table */
for (i = 0; i < 16; ++i)
t->table[i] = 0;
/* scan symbol lengths, and sum code length counts */
for (i = 0; i < num; ++i)
t->table[lengths[i]]++;
t->table[0] = 0;
/* compute offset table for distribution sort */
for (sum = 0, i = 0; i < 16; ++i) {
offs[i] = sum;
sum += t->table[i];
}
/* create code->symbol translation table (symbols sorted by code) */
for (i = 0; i < num; ++i) {
if (lengths[i])
t->trans[offs[lengths[i]]++] = i;
}
}
/* ---------------------- *
* -- decode functions -- *
* ---------------------- */
/* given a data stream and a tree, decode a symbol */
static int decode_symbol (UZLIB_DATA *d, UZLIB_TREE *t) {
int sum = 0, cur = 0, len = 0;
/* get more bits while code value is above sum */
do {
cur = 2*cur + getbit(d);
if (++len == SIZE(t->table))
return UZLIB_DATA_ERROR;
sum += t->table[len];
cur -= t->table[len];
} while (cur >= 0);
sum += cur;
if (sum < 0 || sum >= SIZE(t->trans))
return UZLIB_DATA_ERROR;
return t->trans[sum];
}
/* given a data stream, decode dynamic trees from it */
static int decode_trees (UZLIB_DATA *d, UZLIB_TREE *lt, UZLIB_TREE *dt) {
uchar lengths[288+32];
uint hlit, hdist, hclen, hlimit;
uint i, num, length;
/* get 5 bits HLIT (257-286) */
hlit = read_bits(d, 5, 257);
/* get 5 bits HDIST (1-32) */
hdist = read_bits(d, 5, 1);
/* get 4 bits HCLEN (4-19) */
hclen = read_bits(d, 4, 4);
for (i = 0; i < 19; ++i) lengths[i] = 0;
/* read code lengths for code length alphabet */
for (i = 0; i < hclen; ++i) {
/* get 3 bits code length (0-7) */
uint clen = read_bits(d, 3, 0);
lengths[d->clcidx[i]] = clen;
}
/* build code length tree, temporarily use length tree */
build_tree(lt, lengths, 19);
/* decode code lengths for the dynamic trees */
hlimit = hlit + hdist;
for (num = 0; num < hlimit; ) {
int sym = decode_symbol(d, lt);
uchar fill_value = 0;
int lbits, lbase = 3;
/* error decoding */
if (sym < 0)
return sym;
switch (sym) {
case 16:
/* copy previous code length 3-6 times (read 2 bits) */
fill_value = lengths[num - 1];
lbits = 2;
break;
case 17:
/* repeat code length 0 for 3-10 times (read 3 bits) */
lbits = 3;
break;
case 18:
/* repeat code length 0 for 11-138 times (read 7 bits) */
lbits = 7;
lbase = 11;
break;
default:
/* values 0-15 represent the actual code lengths */
lengths[num++] = sym;
/* continue the for loop */
continue;
}
/* special code length 16-18 are handled here */
length = read_bits(d, lbits, lbase);
if (num + length > hlimit)
return UZLIB_DATA_ERROR;
for (; length; --length)
lengths[num++] = fill_value;
}
/* build dynamic trees */
build_tree(lt, lengths, hlit);
build_tree(dt, lengths + hlit, hdist);
return UZLIB_OK;
}
/* ----------------------------- *
* -- block inflate functions -- *
* ----------------------------- */
/* given a stream and two trees, inflate a block of data */
static int inflate_block_data (UZLIB_DATA *d, UZLIB_TREE *lt, UZLIB_TREE *dt) {
if (d->curLen == 0) {
int dist;
int sym = decode_symbol(d, lt);
/* literal byte */
if (sym < 256) {
DBG_PRINT("huff sym: %02x %c\n", sym, sym);
d->put_byte(sym);
return UZLIB_OK;
}
/* end of block */
if (sym == 256)
return UZLIB_DONE;
/* substring from sliding dictionary */
sym -= 257;
/* possibly get more bits from length code */
d->curLen = read_bits(d, d->lengthBits[sym], d->lengthBase[sym]);
dist = decode_symbol(d, dt);
/* possibly get more bits from distance code */
d->lzOffs = read_bits(d, d->distBits[dist], d->distBase[dist]);
DBG_PRINT("huff dict: -%u for %u\n", d->lzOffs, d->curLen);
}
/* copy next byte from dict substring */
uchar b = d->recall_byte(d->lzOffs);
DBG_PRINT("huff dict byte(%u): -%u - %02x %c\n\n",
d->curLen, d->lzOffs, b, b);
d->put_byte(b);
d->curLen--;
return UZLIB_OK;
}
/* inflate an uncompressed block of data */
static int inflate_uncompressed_block (UZLIB_DATA *d) {
if (d->curLen == 0) {
uint length = get_uint16(d);
uint invlength = get_uint16(d);
/* check length */
if (length != (~invlength & 0x0000ffff))
return UZLIB_DATA_ERROR;
/* increment length to properly return UZLIB_DONE below, without
producing data at the same time */
d->curLen = length + 1;
/* make sure we start next block on a byte boundary */
d->bitcount = 0;
}
if (--d->curLen == 0) {
return UZLIB_DONE;
}
d->put_byte(d->get_byte());
return UZLIB_OK;
}
/* -------------------------- *
* -- main parse functions -- *
* -------------------------- */
static int parse_gzip_header(UZLIB_DATA *d) {
/* check id bytes */
if (d->get_byte() != 0x1f || d->get_byte() != 0x8b)
return UZLIB_DATA_ERROR;
if (d->get_byte() != 8) /* check method is deflate */
return UZLIB_DATA_ERROR;
uchar flg = d->get_byte();/* get flag byte */
if (flg & 0xe0)/* check that reserved bits are zero */
return UZLIB_DATA_ERROR;
skip_bytes(d, 6); /* skip rest of base header of 10 bytes */
if (flg & UZLIB_FEXTRA) /* skip extra data if present */
skip_bytes(d, get_uint16(d));
if (flg & UZLIB_FNAME) /* skip file name if present */
skip_bytes(d,0);
if (flg & UZLIB_FCOMMENT) /* skip file comment if present */
skip_bytes(d,0);
if (flg & UZLIB_FHCRC) /* ignore header crc if present */
skip_bytes(d,2);
return UZLIB_OK;
}
/* inflate next byte of compressed stream */
static int uncompress_stream (UZLIB_DATA *d) {
do {
int res;
/* start a new block */
if (d->bType == -1) {
next_blk:
/* read final block flag */
d->bFinal = getbit(d);
/* read block type (2 bits) */
d->bType = read_bits(d, 2, 0);
DBG_PRINT("Started new block: type=%d final=%d\n", d->bType, d->bFinal);
if (d->bType == 1) {
/* build fixed huffman trees */
build_fixed_trees(&d->ltree, &d->dtree);
} else if (d->bType == 2) {
/* decode trees from stream */
res = decode_trees(d, &d->ltree, &d->dtree);
if (res != UZLIB_OK)
return res;
}
}
/* process current block */
switch (d->bType) {
case 0:
/* decompress uncompressed block */
res = inflate_uncompressed_block(d);
break;
case 1:
case 2:
/* decompress block with fixed or dynamic huffman trees. These */
/* trees were decoded previously, so it's the same routine for both */
res = inflate_block_data(d, &d->ltree, &d->dtree);
break;
default:
return UZLIB_DATA_ERROR;
}
if (res == UZLIB_DONE && !d->bFinal) {
/* the block has ended (without producing more data), but we
can't return without data, so start procesing next block */
goto next_blk;
}
if (res != UZLIB_OK)
return res;
} while (--d->destSize);
return UZLIB_OK;
}
/*
* This implementation has a different usecase to Paul Sokolovsky's
* uzlib implementation, in that it is designed to target IoT devices
* such as the ESP8266. Here clarity and compact code size is an
* advantage, but the ESP8266 only has 40-45Kb free heap, and has to
* process files with an unpacked size of up 256Kb, so a streaming
* implementation is essential.
*
* I have taken the architectural decision to hide the implementation
* detials from the uncompress routines and the caller must provide
* three support routines to handle the streaming:
*
* void get_byte(void)
* void put_byte(uchar b)
* uchar recall_byte(uint offset)
*
* This last must be able to recall an output byte with an offet up to
* the maximum dictionary size.
*/
int uzlib_inflate (
uchar (*get_byte)(void),
void (*put_byte)(uchar v),
uchar (*recall_byte)(uint offset),
uint len, uint *crc, void **state) {
int res;
/* initialize decompression structure */
UZLIB_DATA *d = (UZLIB_DATA *) uz_malloc(sizeof(*d));
if (!d)
return UZLIB_MEMORY_ERROR;
*state = d;
d->bitcount = 0;
d->bFinal = 0;
d->bType = -1;
d->curLen = 0;
d->destSize = len;
d->get_byte = get_byte;
d->put_byte = put_byte;
d->recall_byte = recall_byte;
if ((res = UZLIB_SETJMP(unwindAddr)) != 0) {
if (crc)
*crc = d->checksum;
/* handle long jump */
if (d) {
uz_free(d);
*state = NULL;
}
return res;
}
/* create RAM copy of clcidx byte array */
memcpy(d->clcidx, CLCIDX_INIT, sizeof(d->clcidx));
/* build extra bits and base tables */
build_bits_base(d->lengthBits, d->lengthBase, 4, 3);
build_bits_base(d->distBits, d->distBase, 2, 1);
d->lengthBits[28] = 0; /* fix a special case */
d->lengthBase[28] = 258;
if ((res = parse_gzip_header(d))== UZLIB_OK)
while ((res = uncompress_stream(d)) == UZLIB_OK)
{}
if (res == UZLIB_DONE) {
d->checksum = get_le_uint32(d);
(void) get_le_uint32(d); /* already got length so ignore */
}
UZLIB_THROW(res);
}
# WiFi Module
# Eth Module
| Since | Origin / Contributor | Maintainer | Source |
| :----- | :-------------------- | :---------- | :------ |
| 2019-06-25 | [Arnim Laeuger](https://github.com/devsaurus) |[Arnim Laeuger](https://github.com/devsaurus) | [eth.c](../../components/modules/eth.c)|
......
--
-- File: LFS_dummy_strings.lua
--[[
luac.cross -f generates a ROM string table which is part of the compiled LFS
image. This table includes all strings referenced in the loaded modules.
If you want to preload other string constants, then one way to achieve this is
to include a dummy module in the LFS that references the strings that you want
to load. You never need to call this module; it's inclusion in the LFS image is
enough to add the strings to the ROM table. Your application can use any strings
in the ROM table without incuring any RAM or Lua Garbage Collector (LGC)
overhead.
The local preload example is a useful starting point. However, if you call the
following code in your application during testing, then this will provide a
listing of the current RAM string table.
do
local a=debug.getstrings'RAM'
for i =1, #a do a[i] = ('%q'):format(a[i]) end
print ('local preload='..table.concat(a,','))
end
This will exclude any strings already in the ROM table, so the output is the list
of putative strings that you should consider adding to LFS ROM table.
---------------------------------------------------------------------------------]]
local preload = "?.lc;?.lua", "/\n;\n?\n!\n-", "@init.lua", "_G", "_LOADED",
"_LOADLIB", "__add", "__call", "__concat", "__div", "__eq", "__gc", "__index",
"__le", "__len", "__lt", "__mod", "__mode", "__mul", "__newindex", "__pow",
"__sub", "__tostring", "__unm", "collectgarbage", "cpath", "debug", "file",
"file.obj", "file.vol", "flash", "getstrings", "index", "ipairs", "list", "loaded",
"loader", "loaders", "loadlib", "module", "net.tcpserver", "net.tcpsocket",
"net.udpsocket", "newproxy", "package", "pairs", "path", "preload", "reload",
"require", "seeall", "wdclr", "not enough memory", "sjson.decoder","sjson.encoder",
"tmr.timer"
--
-- File: _init.lua
--[[
This is a template for the LFS equivalent of the SPIFFS init.lua.
It is a good idea to such an _init.lua module to your LFS and do most of the LFS
module related initialisaion in this. This example uses standard Lua features to
simplify the LFS API.
The first section adds a 'LFS' table to _G and uses the __index metamethod to
resolve functions in the LFS, so you can execute the main function of module
'fred' by executing LFS.fred(params), etc. It also implements some standard
readonly properties:
LFS._time The Unix Timestamp when the luac.cross was executed. This can be
used as a version identifier.
LFS._config This returns a table of useful configuration parameters, hence
print (("0x%6x"):format(LFS._config.lfs_base))
gives you the parameter to use in the luac.cross -a option.
LFS._list This returns a table of the LFS modules, hence
print(table.concat(LFS._list,'\n'))
gives you a single column listing of all modules in the LFS.
---------------------------------------------------------------------------------]]
local index = node.flashindex
local lfs_t = {
__index = function(_, name)
fn_ut, ba, ma, size, modules = index(name)
if not ba then
return fn_ut
elseif name == '_time' then
return fn_ut
elseif name == '_config' then
fs_ma, fs_size = file.fscfg()
return {lfs_base = ba, lfs_mapped = ma, lfs_size = size,
fs_mapped = fs_ma, fs_size = fs_size}
elseif name == '_list' then
return modules
else
return nil
end
end,
__newindex = function(_, name, value)
error("LFS is readonly. Invalid write to LFS." .. name, 2)
end,
}
_G.LFS = setmetatable(lfs_t,lfs_t)
--[[-------------------------------------------------------------------------------
The second section adds the LFS to the require searchlist, so that you can
require a Lua module 'jean' in the LFS by simply doing require "jean". However
note that this is at the search entry following the FS searcher, so if you also
have jean.lc or jean.lua in SPIFFS, then this SPIFFS version will get loaded into
RAM instead of using. (Useful, for development).
See docs/en/lfs.md and the 'loaders' array in app/lua/loadlib.c for more details.
---------------------------------------------------------------------------------]]
package.loaders[3] = function(module) -- loader_flash
fn, ba = index(module)
return ba and "Module not in LFS" or fn
end
--[[-------------------------------------------------------------------------------
You can add any other initialisation here, for example a couple of the globals
are never used, so setting them to nil saves a couple of global entries
---------------------------------------------------------------------------------]]
_G.module = nil -- disable Lua 5.0 style modules to save RAM
package.seeall = nil
--[[-------------------------------------------------------------------------------
These replaces the builtins loadfile & dofile with ones which preferentially
loads the corresponding module from LFS if present. Flipping the search order
is an exercise left to the reader.-
---------------------------------------------------------------------------------]]
lf, df = loadfile, dofile
_G.loadfile = function(n)
mod, ext = n:match("(.*)%.(l[uc]a?)");
fn, ba = index(mod)
if ba or (ext ~= 'lc' and ext ~= 'lua') then return lf(n) else return fn end
end
_G.dofile = function(n)
mod, ext = n:match("(.*)%.(l[uc]a?)");
fn, ba = index(mod)
if ba or (ext ~= 'lc' and ext ~= 'lua') then return df(n) else return fn() end
end
......@@ -5,6 +5,9 @@ CONFIG_PARTITION_TABLE_SINGLE_APP=n
CONFIG_PARTITION_TABLE_TWO_OTA=n
CONFIG_PARTITION_TABLE_CUSTOM=y
# Default to 4MB for builds
CONFIG_ESPTOOLPY_FLASHSIZE_4MB=y
# Don't warn about undefined variables
CONFIG_MAKE_WARN_UNDEFINED_VARIABLES=n
......
local args = { ... }
local b = require "tools.build"
local builder = b.new_builder( ".build/cross-lua" )
local utils = b.utils
local sf = string.format
if not (_VERSION == "Lua 5.1" and pcall(require,"lfs")) then
print [[
cross_lua.lua must be run within Lua 5.1 and it requires the Lua Filesystem to be installed.
On most *nix distrubitions youwill find a packages lua-5.1 and lua-filesystem, or
alternalively you can install lua-rocks and use the Rocks package manager to install lfs.
]]
os.exit(1)
end
builder:init( args )
builder:set_build_mode( builder.BUILD_DIR_LINEARIZED )
local output = 'luac.cross'
local cdefs = '-DLUA_CROSS_COMPILER'
-- Lua source files and include path
local lua_files = [[
lapi.c lauxlib.c lbaselib.c lcode.c ldblib.c ldebug.c ldo.c ldump.c
lfunc.c lgc.c llex.c lmathlib.c lmem.c loadlib.c lobject.c lopcodes.c
lparser.c lrotable.c lstate.c lstring.c lstrlib.c ltable.c ltablib.c
ltm.c lundump.c lvm.c lzio.c
luac_cross/luac.c luac_cross/loslib.c luac_cross/print.c
../modules/linit.c
../libc/c_stdlib.c
]]
lua_files = lua_files:gsub( "\n" , "" )
local lua_full_files = utils.prepend_path( lua_files, "app/lua" )
local local_include = "-Iapp/include -Iinclude -Iapp/lua"
-- Compiler/linker options
builder:set_compile_cmd( sf( "gcc -O2 %s -Wall %s -c $(FIRST) -o $(TARGET)", local_include, cdefs ) )
builder:set_link_cmd( "gcc -o $(TARGET) $(DEPENDS) -lm" )
-- Build everything
builder:make_exe_target( output, lua_full_files )
builder:build()
#!/bin/bash
LUA_APP_SRC="$@"
MAP_FILE=build/NodeMCU.map
LUAC_OUTPUT=build/luac.out
LUAC_CROSS=build/luac_cross/luac.cross
if [ ! -f "${MAP_FILE}" ]; then
echo "Error: ${MAP_FILE} not found. Please run make first."
exit 1
fi
if [ ! -f "${LUAC_CROSS}" ]; then
echo "Error: ${LUAC_CROSS} not found. Please run make first."
exit 1
fi
LFS_ADDR_SIZE=`grep -E "\.lfs\.reserved[ ]+0x[0-9a-f]+[ ]+0x[0-9a-z]+" ${MAP_FILE} | tr -s ' '`
if [ -z "${LFS_ADDR_SIZE}" ]; then
echo "Error: LFS segment not found. Use 'make clean; make' perhaps?"
exit 1
fi
LFS_ADDR=`echo "${LFS_ADDR_SIZE}" | cut -d ' ' -f 3`
if [ -z "${LFS_ADDR}" ]; then
echo "Error: LFS segment address not found"
exit 1
fi
LFS_SIZE=`echo "${LFS_ADDR_SIZE}" | cut -d ' ' -f 4`
if [ -z "${LFS_SIZE}" ]; then
echo "Error: LFS segment size not found"
exit 1
fi
echo "LFS segment address ${LFS_ADDR}, length ${LFS_SIZE}"
${LUAC_CROSS} -a ${LFS_ADDR} -m ${LFS_SIZE} -o ${LUAC_OUTPUT} ${LUA_APP_SRC}
if [ $? != 0 ]; then
echo "Error: luac.cross failed"
exit 1
fi
make
#!/usr/bin/env python
#
# ESP8266 ROM Bootloader Utility
# https://github.com/themadinventor/esptool
#
# Copyright (C) 2014 Fredrik Ahlberg
#
# This program is free software; you can redistribute it and/or modify it under
# the terms of the GNU General Public License as published by the Free Software
# Foundation; either version 2 of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License along with
# this program; if not, write to the Free Software Foundation, Inc., 51 Franklin
# Street, Fifth Floor, Boston, MA 02110-1301 USA.
import sys
import struct
import serial
import math
import time
import argparse
import os
import subprocess
class ESPROM:
# These are the currently known commands supported by the ROM
ESP_FLASH_BEGIN = 0x02
ESP_FLASH_DATA = 0x03
ESP_FLASH_END = 0x04
ESP_MEM_BEGIN = 0x05
ESP_MEM_END = 0x06
ESP_MEM_DATA = 0x07
ESP_SYNC = 0x08
ESP_WRITE_REG = 0x09
ESP_READ_REG = 0x0a
# Maximum block sized for RAM and Flash writes, respectively.
ESP_RAM_BLOCK = 0x1800
ESP_FLASH_BLOCK = 0x400
# Default baudrate. The ROM auto-bauds, so we can use more or less whatever we want.
ESP_ROM_BAUD = 115200
# First byte of the application image
ESP_IMAGE_MAGIC = 0xe9
# Initial state for the checksum routine
ESP_CHECKSUM_MAGIC = 0xef
# OTP ROM addresses
ESP_OTP_MAC0 = 0x3ff00050
ESP_OTP_MAC1 = 0x3ff00054
# Sflash stub: an assembly routine to read from spi flash and send to host
SFLASH_STUB = "\x80\x3c\x00\x40\x1c\x4b\x00\x40\x21\x11\x00\x40\x00\x80" \
"\xfe\x3f\xc1\xfb\xff\xd1\xf8\xff\x2d\x0d\x31\xfd\xff\x41\xf7\xff\x4a" \
"\xdd\x51\xf9\xff\xc0\x05\x00\x21\xf9\xff\x31\xf3\xff\x41\xf5\xff\xc0" \
"\x04\x00\x0b\xcc\x56\xec\xfd\x06\xff\xff\x00\x00"
def __init__(self, port = 0, baud = ESP_ROM_BAUD):
self._port = serial.Serial(port, baud)
""" Read bytes from the serial port while performing SLIP unescaping """
def read(self, length = 1):
b = ''
while len(b) < length:
c = self._port.read(1)
if c == '\xdb':
c = self._port.read(1)
if c == '\xdc':
b = b + '\xc0'
elif c == '\xdd':
b = b + '\xdb'
else:
raise Exception('Invalid SLIP escape')
else:
b = b + c
return b
""" Write bytes to the serial port while performing SLIP escaping """
def write(self, packet):
buf = '\xc0'+(packet.replace('\xdb','\xdb\xdd').replace('\xc0','\xdb\xdc'))+'\xc0'
self._port.write(buf)
""" Calculate checksum of a blob, as it is defined by the ROM """
@staticmethod
def checksum(data, state = ESP_CHECKSUM_MAGIC):
for b in data:
state ^= ord(b)
return state
""" Send a request and read the response """
def command(self, op = None, data = None, chk = 0):
if op:
# Construct and send request
pkt = struct.pack('<BBHI', 0x00, op, len(data), chk) + data
self.write(pkt)
# Read header of response and parse
if self._port.read(1) != '\xc0':
raise Exception('Invalid head of packet')
hdr = self.read(8)
(resp, op_ret, len_ret, val) = struct.unpack('<BBHI', hdr)
if resp != 0x01 or (op and op_ret != op):
raise Exception('Invalid response')
# The variable-length body
body = self.read(len_ret)
# Terminating byte
if self._port.read(1) != chr(0xc0):
raise Exception('Invalid end of packet')
return val, body
""" Perform a connection test """
def sync(self):
self.command(ESPROM.ESP_SYNC, '\x07\x07\x12\x20'+32*'\x55')
for i in xrange(7):
self.command()
""" Try connecting repeatedly until successful, or giving up """
def connect(self):
print 'Connecting...'
for _ in xrange(4):
# issue reset-to-bootloader:
# RTS = either CH_PD or nRESET (both active low = chip in reset)
# DTR = GPIO0 (active low = boot to flasher)
self._port.setDTR(False)
self._port.setRTS(True)
time.sleep(0.05)
self._port.setDTR(True)
self._port.setRTS(False)
time.sleep(0.05)
self._port.setDTR(False)
self._port.timeout = 0.3 # worst-case latency timer should be 255ms (probably <20ms)
for _ in xrange(4):
try:
self._port.flushInput()
self._port.flushOutput()
self.sync()
self._port.timeout = 5
return
except:
time.sleep(0.05)
# this is a workaround for the CH340 serial driver on current versions of Linux,
# which seems to sometimes set the serial port up with wrong parameters
self._port.close()
self._port.open()
raise Exception('Failed to connect')
""" Read memory address in target """
def read_reg(self, addr):
res = self.command(ESPROM.ESP_READ_REG, struct.pack('<I', addr))
if res[1] != "\0\0":
raise Exception('Failed to read target memory')
return res[0]
""" Write to memory address in target """
def write_reg(self, addr, value, mask, delay_us = 0):
if self.command(ESPROM.ESP_WRITE_REG,
struct.pack('<IIII', addr, value, mask, delay_us))[1] != "\0\0":
raise Exception('Failed to write target memory')
""" Start downloading an application image to RAM """
def mem_begin(self, size, blocks, blocksize, offset):
if self.command(ESPROM.ESP_MEM_BEGIN,
struct.pack('<IIII', size, blocks, blocksize, offset))[1] != "\0\0":
raise Exception('Failed to enter RAM download mode')
""" Send a block of an image to RAM """
def mem_block(self, data, seq):
if self.command(ESPROM.ESP_MEM_DATA,
struct.pack('<IIII', len(data), seq, 0, 0)+data, ESPROM.checksum(data))[1] != "\0\0":
raise Exception('Failed to write to target RAM')
""" Leave download mode and run the application """
def mem_finish(self, entrypoint = 0):
if self.command(ESPROM.ESP_MEM_END,
struct.pack('<II', int(entrypoint == 0), entrypoint))[1] != "\0\0":
raise Exception('Failed to leave RAM download mode')
""" Start downloading to Flash (performs an erase) """
def flash_begin(self, size, offset):
old_tmo = self._port.timeout
num_blocks = (size + ESPROM.ESP_FLASH_BLOCK - 1) / ESPROM.ESP_FLASH_BLOCK
self._port.timeout = 10
if self.command(ESPROM.ESP_FLASH_BEGIN,
struct.pack('<IIII', size, num_blocks, ESPROM.ESP_FLASH_BLOCK, offset))[1] != "\0\0":
raise Exception('Failed to enter Flash download mode')
self._port.timeout = old_tmo
""" Write block to flash """
def flash_block(self, data, seq):
if self.command(ESPROM.ESP_FLASH_DATA,
struct.pack('<IIII', len(data), seq, 0, 0)+data, ESPROM.checksum(data))[1] != "\0\0":
raise Exception('Failed to write to target Flash')
""" Leave flash mode and run/reboot """
def flash_finish(self, reboot = False):
pkt = struct.pack('<I', int(not reboot))
if self.command(ESPROM.ESP_FLASH_END, pkt)[1] != "\0\0":
raise Exception('Failed to leave Flash mode')
""" 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 MAC from OTP ROM """
def read_mac(self):
mac0 = esp.read_reg(esp.ESP_OTP_MAC0)
mac1 = esp.read_reg(esp.ESP_OTP_MAC1)
if ((mac1 >> 16) & 0xff) == 0:
oui = (0x18, 0xfe, 0x34)
elif ((mac1 >> 16) & 0xff) == 1:
oui = (0xac, 0xd0, 0x74)
else:
raise Exception("Unknown OUI")
return oui + ((mac1 >> 8) & 0xff, mac1 & 0xff, (mac0 >> 24) & 0xff)
""" Read SPI flash manufacturer and device id """
def flash_id(self):
self.flash_begin(0, 0)
self.write_reg(0x60000240, 0x0, 0xffffffff)
self.write_reg(0x60000200, 0x10000000, 0xffffffff)
flash_id = esp.read_reg(0x60000240)
self.flash_finish(False)
return flash_id
""" Read SPI flash """
def flash_read(self, offset, size, count = 1):
# Create a custom stub
stub = struct.pack('<III', offset, size, count) + self.SFLASH_STUB
# Trick ROM to initialize SFlash
self.flash_begin(0, 0)
# Download stub
self.mem_begin(len(stub), 1, len(stub), 0x40100000)
self.mem_block(stub, 0)
self.mem_finish(0x4010001c)
# Fetch the data
data = ''
for _ in xrange(count):
if self._port.read(1) != '\xc0':
raise Exception('Invalid head of packet (sflash read)')
data += self.read(size)
if self._port.read(1) != chr(0xc0):
raise Exception('Invalid end of packet (sflash read)')
return data
""" Abuse the loader protocol to force flash to be left in write mode """
def flash_unlock_dio(self):
# Enable flash write mode
self.flash_begin(0, 0)
# Reset the chip rather than call flash_finish(), which would have
# write protected the chip again (why oh why does it do that?!)
self.mem_begin(0,0,0,0x40100000)
self.mem_finish(0x40000080)
""" Perform a chip erase of SPI flash """
def flash_erase(self):
# Trick ROM to initialize SFlash
self.flash_begin(0, 0)
# This is hacky: we don't have a custom stub, instead we trick
# the bootloader to jump to the SPIEraseChip() routine and then halt/crash
# when it tries to boot an unconfigured system.
self.mem_begin(0,0,0,0x40100000)
self.mem_finish(0x40004984)
# Yup - there's no good way to detect if we succeeded.
# It it on the other hand unlikely to fail.
class ESPFirmwareImage:
def __init__(self, filename = None):
self.segments = []
self.entrypoint = 0
self.flash_mode = 0
self.flash_size_freq = 0
if filename is not None:
f = file(filename, 'rb')
(magic, segments, self.flash_mode, self.flash_size_freq, self.entrypoint) = struct.unpack('<BBBBI', f.read(8))
# some sanity check
if magic != ESPROM.ESP_IMAGE_MAGIC or segments > 16:
raise Exception('Invalid firmware image')
for i in xrange(segments):
(offset, size) = struct.unpack('<II', f.read(8))
if offset > 0x40200000 or offset < 0x3ffe0000 or size > 65536:
raise Exception('Suspicious segment %x,%d' % (offset, size))
self.segments.append((offset, size, f.read(size)))
# Skip the padding. The checksum is stored in the last byte so that the
# file is a multiple of 16 bytes.
align = 15-(f.tell() % 16)
f.seek(align, 1)
self.checksum = ord(f.read(1))
def add_segment(self, addr, data):
# Data should be aligned on word boundary
l = len(data)
if l % 4:
data += b"\x00" * (4 - l % 4)
self.segments.append((addr, len(data), data))
def save(self, filename):
f = file(filename, 'wb')
f.write(struct.pack('<BBBBI', ESPROM.ESP_IMAGE_MAGIC, len(self.segments),
self.flash_mode, self.flash_size_freq, self.entrypoint))
checksum = ESPROM.ESP_CHECKSUM_MAGIC
for (offset, size, data) in self.segments:
f.write(struct.pack('<II', offset, size))
f.write(data)
checksum = ESPROM.checksum(data, checksum)
align = 15-(f.tell() % 16)
f.seek(align, 1)
f.write(struct.pack('B', checksum))
class ELFFile:
def __init__(self, name):
self.name = name
self.symbols = None
def _fetch_symbols(self):
if self.symbols is not None:
return
self.symbols = {}
try:
tool_nm = "xtensa-lx106-elf-nm"
if os.getenv('XTENSA_CORE')=='lx106':
tool_nm = "xt-nm"
proc = subprocess.Popen([tool_nm, self.name], stdout=subprocess.PIPE)
except OSError:
print "Error calling "+tool_nm+", do you have Xtensa toolchain in PATH?"
sys.exit(1)
for l in proc.stdout:
fields = l.strip().split()
self.symbols[fields[2]] = int(fields[0], 16)
def get_symbol_addr(self, sym):
self._fetch_symbols()
return self.symbols[sym]
def get_entry_point(self):
tool_readelf = "xtensa-lx106-elf-readelf"
if os.getenv('XTENSA_CORE')=='lx106':
tool_objcopy = "xt-readelf"
try:
proc = subprocess.Popen([tool_readelf, "-h", self.name], stdout=subprocess.PIPE)
except OSError:
print "Error calling "+tool_nm+", do you have Xtensa toolchain in PATH?"
sys.exit(1)
for l in proc.stdout:
fields = l.strip().split()
if fields[0] == "Entry":
return int(fields[3], 0);
def load_section(self, section):
tool_objcopy = "xtensa-lx106-elf-objcopy"
if os.getenv('XTENSA_CORE')=='lx106':
tool_objcopy = "xt-objcopy"
subprocess.check_call([tool_objcopy, "--only-section", section, "-Obinary", self.name, ".tmp.section"])
f = open(".tmp.section", "rb")
data = f.read()
f.close()
os.remove(".tmp.section")
return data
def arg_auto_int(x):
return int(x, 0)
if __name__ == '__main__':
parser = argparse.ArgumentParser(description = 'ESP8266 ROM Bootloader Utility', prog = 'esptool')
parser.add_argument(
'--port', '-p',
help = 'Serial port device',
default = '/dev/ttyUSB0')
parser.add_argument(
'--baud', '-b',
help = 'Serial port baud rate',
type = arg_auto_int,
default = ESPROM.ESP_ROM_BAUD)
subparsers = parser.add_subparsers(
dest = 'operation',
help = 'Run esptool {command} -h for additional help')
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)
parser_write_flash = subparsers.add_parser(
'write_flash',
help = 'Write a binary blob to flash')
parser_write_flash.add_argument('addr_filename', nargs = '+', help = 'Address and binary file to write there, separated by space')
parser_write_flash.add_argument('--flash_freq', '-ff', help = 'SPI Flash frequency',
choices = ['40m', '26m', '20m', '80m'], default = '40m')
parser_write_flash.add_argument('--flash_mode', '-fm', help = 'SPI Flash mode',
choices = ['qio', 'qout', 'dio', 'dout'], default = 'qio')
parser_write_flash.add_argument('--flash_size', '-fs', help = 'SPI Flash size in Mbit',
choices = ['4m', '2m', '8m', '16m', '32m'], default = '4m')
parser_run = 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', type = str)
parser_elf2image.add_argument('--flash_freq', '-ff', help = 'SPI Flash frequency',
choices = ['40m', '26m', '20m', '80m'], default = '40m')
parser_elf2image.add_argument('--flash_mode', '-fm', help = 'SPI Flash mode',
choices = ['qio', 'qout', 'dio', 'dout'], default = 'qio')
parser_elf2image.add_argument('--flash_size', '-fs', help = 'SPI Flash size in Mbit',
choices = ['4m', '2m', '8m', '16m', '32m'], default = '4m')
parser_read_mac = subparsers.add_parser(
'read_mac',
help = 'Read MAC address from OTP ROM')
parser_flash_id = subparsers.add_parser(
'flash_id',
help = 'Read SPI flash manufacturer and device ID')
parser_read_flash = subparsers.add_parser(
'read_flash',
help = 'Read SPI flash content')
parser_read_flash.add_argument('address', help = 'Start address', type = arg_auto_int)
parser_read_flash.add_argument('size', help = 'Size of region to dump', type = arg_auto_int)
parser_read_flash.add_argument('filename', help = 'Name of binary dump')
parser_erase_flash = subparsers.add_parser(
'erase_flash',
help = 'Perform Chip Erase on SPI flash')
args = parser.parse_args()
# Create the ESPROM connection object, if needed
esp = None
if args.operation not in ('image_info','make_image','elf2image'):
esp = ESPROM(args.port, args.baud)
esp.connect()
# Do the actual work. Should probably be split into separate functions.
if args.operation == 'load_ram':
image = ESPFirmwareImage(args.filename)
print 'RAM boot...'
for (offset, size, data) in image.segments:
print 'Downloading %d bytes at %08x...' % (size, offset),
sys.stdout.flush()
esp.mem_begin(size, math.ceil(size / float(esp.ESP_RAM_BLOCK)), esp.ESP_RAM_BLOCK, offset)
seq = 0
while len(data) > 0:
esp.mem_block(data[0:esp.ESP_RAM_BLOCK], seq)
data = data[esp.ESP_RAM_BLOCK:]
seq += 1
print 'done!'
print 'All segments done, executing at %08x' % image.entrypoint
esp.mem_finish(image.entrypoint)
elif args.operation == 'read_mem':
print '0x%08x = 0x%08x' % (args.address, esp.read_reg(args.address))
elif args.operation == 'write_mem':
esp.write_reg(args.address, args.value, args.mask, 0)
print 'Wrote %08x, mask %08x to %08x' % (args.value, args.mask, args.address)
elif args.operation == 'dump_mem':
f = file(args.filename, 'wb')
for i in xrange(args.size/4):
d = esp.read_reg(args.address+(i*4))
f.write(struct.pack('<I', d))
if f.tell() % 1024 == 0:
print '\r%d bytes read... (%d %%)' % (f.tell(), f.tell()*100/args.size),
sys.stdout.flush()
print 'Done!'
elif args.operation == 'write_flash':
assert len(args.addr_filename) % 2 == 0
flash_mode = {'qio':0, 'qout':1, 'dio':2, 'dout': 3}[args.flash_mode]
flash_size_freq = {'4m':0x00, '2m':0x10, '8m':0x20, '16m':0x30, '32m':0x40}[args.flash_size]
flash_size_freq += {'40m':0, '26m':1, '20m':2, '80m': 0xf}[args.flash_freq]
flash_info = struct.pack('BB', flash_mode, flash_size_freq)
while args.addr_filename:
address = int(args.addr_filename[0], 0)
filename = args.addr_filename[1]
args.addr_filename = args.addr_filename[2:]
image = file(filename, 'rb').read()
print 'Erasing flash...'
blocks = math.ceil(len(image)/float(esp.ESP_FLASH_BLOCK))
esp.flash_begin(blocks*esp.ESP_FLASH_BLOCK, address)
seq = 0
while len(image) > 0:
print '\rWriting at 0x%08x... (%d %%)' % (address + seq*esp.ESP_FLASH_BLOCK, 100*(seq+1)/blocks),
sys.stdout.flush()
block = image[0:esp.ESP_FLASH_BLOCK]
# Fix sflash config data
if address == 0 and seq == 0 and block[0] == '\xe9':
block = block[0:2] + flash_info + block[4:]
# Pad the last block
block = block + '\xff' * (esp.ESP_FLASH_BLOCK-len(block))
esp.flash_block(block, seq)
image = image[esp.ESP_FLASH_BLOCK:]
seq += 1
print
print '\nLeaving...'
if args.flash_mode == 'dio':
esp.flash_unlock_dio()
else:
esp.flash_finish(False)
elif args.operation == 'run':
esp.run()
elif args.operation == 'image_info':
image = ESPFirmwareImage(args.filename)
print ('Entry point: %08x' % image.entrypoint) if image.entrypoint != 0 else 'Entry point not set'
print '%d segments' % len(image.segments)
print
checksum = ESPROM.ESP_CHECKSUM_MAGIC
for (idx, (offset, size, data)) in enumerate(image.segments):
print 'Segment %d: %5d bytes at %08x' % (idx+1, size, offset)
checksum = ESPROM.checksum(data, checksum)
print
print 'Checksum: %02x (%s)' % (image.checksum, 'valid' if image.checksum == checksum else 'invalid!')
elif args.operation == 'make_image':
image = ESPFirmwareImage()
if len(args.segfile) == 0:
raise Exception('No segments specified')
if len(args.segfile) != len(args.segaddr):
raise Exception('Number of specified files does not match number of specified addresses')
for (seg, addr) in zip(args.segfile, args.segaddr):
data = file(seg, 'rb').read()
image.add_segment(addr, data)
image.entrypoint = args.entrypoint
image.save(args.output)
elif args.operation == 'elf2image':
if args.output is None:
args.output = args.input + '-'
e = ELFFile(args.input)
image = ESPFirmwareImage()
image.entrypoint = e.get_entry_point()
for section, start in ((".text", "_text_start"), (".data", "_data_start"), (".rodata", "_rodata_start")):
data = e.load_section(section)
image.add_segment(e.get_symbol_addr(start), data)
image.flash_mode = {'qio':0, 'qout':1, 'dio':2, 'dout': 3}[args.flash_mode]
image.flash_size_freq = {'4m':0x00, '2m':0x10, '8m':0x20, '16m':0x30, '32m':0x40}[args.flash_size]
image.flash_size_freq += {'40m':0, '26m':1, '20m':2, '80m': 0xf}[args.flash_freq]
image.save(args.output + "0x00000.bin")
data = e.load_section(".irom0.text")
off = e.get_symbol_addr("_irom0_text_start") - 0x40200000
assert off >= 0
f = open(args.output + "0x%05x.bin" % off, "wb")
f.write(data)
f.close()
elif args.operation == 'read_mac':
mac = esp.read_mac()
print 'MAC: %s' % ':'.join(map(lambda x: '%02x'%x, mac))
elif args.operation == 'flash_id':
flash_id = esp.flash_id()
print 'Manufacturer: %02x' % (flash_id & 0xff)
print 'Device: %02x%02x' % ((flash_id >> 8) & 0xff, (flash_id >> 16) & 0xff)
elif args.operation == 'read_flash':
print 'Please wait...'
file(args.filename, 'wb').write(esp.flash_read(args.address, 1024, int(math.ceil(args.size / 1024.)))[:args.size])
elif args.operation == 'erase_flash':
esp.flash_erase()
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment