Browse Source

1st Tranch of SDK 3.0 follow up changes (#2732)

1st Tranche of SDK 3.0 follow up changes
Terry Ellison 5 years ago
parent
commit
bc61528db7

+ 133 - 92
Makefile

@@ -2,21 +2,32 @@
 #
 .NOTPARALLEL:
 
-TOOLCHAIN_VERSION:=20181106.0
+TOP_DIR:=$(abspath $(dir $(lastword $(MAKEFILE_LIST))))
 
-# SDK base version, as released by Espressif
-SDK_BASE_VER:=3.0
-SDK_VER:=$(SDK_BASE_VER)
-SDK_DIR_DEPENDS:=sdk_extracted
+# SDK base version, as released by Espressif depends on the RELEASE flag
+#
+# RELEASE = lastest pulls the latest V3.0.0 branch version as at the issue of this make
+# otherwise it pulls the labelled version in the SDK version's release directory
+#
+ifeq ("$(RELEASE)","latest")
+  export RELEASE:=$(RELEASE)
+  SDK_VER        := 3.0.0-dev-190412
+  SDK_COMMIT_SHA1:= 39ec2d4573eb77fda73f6afcf6dd1b3c41e74fcd
+  SDK_FILE_SHA1  := 44f7724490739536526fc4298d6fcc2fa2d29471
+  SDK_ZIP_ROOT   := ESP8266_NONOS_SDK-$(SDK_COMMIT_SHA1)
+  SDK_FILE_VER   := $(SDK_COMMIT_SHA1)
+else
+  SDK_VER        := 3.0
+  SDK_FILE_SHA1  := 029fc23fe87e03c9852de636490b2d7b9e07f01a
+  SDK_ZIP_ROOT   := ESP8266_NONOS_SDK-$(SDK_VER)
+  SDK_FILE_VER   := v$(SDK_VER)
+endif
+SDK_REL_DIR      := sdk/esp_iot_sdk_v$(SDK_VER)
+SDK_DIR          := $(TOP_DIR)/$(SDK_REL_DIR)
 
-SDK_FILE_VER:=$(SDK_BASE_VER)
-SDK_FILE_SHA1:=029fc23fe87e03c9852de636490b2d7b9e07f01a
-ESPTOOL_VER:=2.6
+ESPTOOL_VER := 2.6
 
-# Ensure we search "our" SDK before the tool-chain's SDK (if any)
-TOP_DIR:=$(abspath $(dir $(lastword $(MAKEFILE_LIST))))
-SDK_REL_DIR=sdk/esp_iot_sdk_v$(SDK_VER)
-SDK_DIR:=$(TOP_DIR)/$(SDK_REL_DIR)
+# Ensure that the Espresif SDK is searched before the tool-chain's SDK (if any)
 CCFLAGS:= -I$(TOP_DIR)/sdk-overrides/include -I$(TOP_DIR)/app/include/lwip/app -I$(SDK_DIR)/include
 LDFLAGS:= -L$(SDK_DIR)/lib -L$(SDK_DIR)/ld $(LDFLAGS)
 
@@ -27,19 +38,19 @@ else
   CCFLAGS += -O2
 endif
 
-#Handling of V=1/VERBOSE=1 flag
+
+# Handling of V=1/VERBOSE=1 flag
 #
 # if V=1, $(summary) does nothing
 # if V is unset or not 1, $(summary) echoes a summary
 VERBOSE ?=
 V ?= $(VERBOSE)
 ifeq ("$(V)","1")
-export summary := @true
+  export summary := @true
 else
-export summary := @echo
-
-# disable echoing of commands, directory names
-MAKEFLAGS += --silent -w
+  export summary := @echo
+  # disable echoing of commands, directory names
+  MAKEFLAGS += --silent -w
 endif  # $(V)==1
 
 ifndef BAUDRATE
@@ -49,9 +60,34 @@ endif
 #############################################################
 # Select compile
 #
-ifeq ($(OS),Windows_NT)
-# WIN32
-# We are under windows.
+#  ** HEALTH WARNING ** This section is largely legacy directives left over from 
+#  an Espressif template.  As far as I (TerrryE) know, we've only used the Linux
+#  Path. I have successfully build AMD and Intel (both x86, AMD64) and RPi ARM6
+#  all under Ubuntu.  Our docker container runs on Windows in an Ubuntu VM.
+#  Johny Mattson maintains a prebuild AMD64 xtensa cross-compile gcc v4.8.5
+#  toolchain which is compatible with the non-OS SDK and can be used on any recent 
+#  Ubuntu version including the Docker and Travis build environments.
+#
+#  You have the option to build your own toolchain and specify a TOOLCHAIN_ROOT
+#  environment variable (see https://github.com/pfalcon/esp-open-sdk).  If your
+#  architecture is compatable then you can omit this variable and the make will
+#  download and use this prebuilt toolchain.
+#
+#  If any developers wish to develop, test and support alternative environments 
+#  then please raise a GitHub issue on this work.
+#
+
+ifndef $(OS)
+  # Assume Windows if MAKE_HOST contains "indows" and Linux otherwise
+  ifneq (,$(findstring indows,$(MAKE_HOST)))
+    OS := windows
+  else
+    OS := linux
+  endif
+endif
+
+ifneq (,$(findstring indows,$(OS)))
+  #------------ BEGIN UNTESTED ------------ We are not under Linux, e.g.under windows.
 	ifeq ($(XTENSA_CORE),lx106)
 		# It is xcc
 		AR = xt-ar
@@ -85,58 +121,54 @@ ifeq ($(OS),Windows_NT)
     ifeq ($(PROCESSOR_ARCHITECTURE),x86)
 # ->IA32
     endif
+  #---------------- END UNTESTED ---------------- We are under windows.
 else
-# We are under other system, may be Linux. Assume using gcc.
-	# Can we use -fdata-sections?
-	PLATFORM:=linux-x86_64
+  # We are under other system, may be Linux. Assume using gcc.
+
+  UNAME_S := $(shell uname -s)
+  UNAME_P := $(shell uname -p)
+  ifeq ($(OS),linux)
+    ifndef TOOLCHAIN_ROOT
+      TOOLCHAIN_VERSION = 20181106.0
+      GCCTOOLCHAIN      = linux-x86_64-$(TOOLCHAIN_VERSION)
+      TOOLCHAIN_ROOT    = $(TOP_DIR)/tools/toolchains/esp8266-$(GCCTOOLCHAIN)
+      GITHUB_TOOLCHAIN  = https://github.com/jmattsson/esp-toolchains
+      export PATH:=$(PATH):$(TOOLCHAIN_ROOT)/bin
+  	endif
+	endif
+	
 	ifndef COMPORT
 		ESPPORT = /dev/ttyUSB0
 	else
 		ESPPORT = $(COMPORT)
 	endif
-	export PATH := $(PATH):$(TOP_DIR)/tools/toolchains/esp8266-$(PLATFORM)-$(TOOLCHAIN_VERSION)/bin/
+
 	CCFLAGS += -ffunction-sections -fno-jump-tables -fdata-sections
-	AR = xtensa-lx106-elf-ar
-	CC = $(WRAPCC) xtensa-lx106-elf-gcc
-	CXX = $(WRAPCC) xtensa-lx106-elf-g++
-	NM = xtensa-lx106-elf-nm
-	CPP = $(WRAPCC) xtensa-lx106-elf-gcc -E
+	AR      = xtensa-lx106-elf-ar
+	CC      = $(WRAPCC) xtensa-lx106-elf-gcc
+	CXX     = $(WRAPCC) xtensa-lx106-elf-g++
+	NM      = xtensa-lx106-elf-nm
+	CPP     = $(WRAPCC) xtensa-lx106-elf-gcc -E
 	OBJCOPY = xtensa-lx106-elf-objcopy
 	FIRMWAREDIR = ../bin/
   WGET = wget --tries=10 --timeout=15 --waitretry=30 --read-timeout=20 --retry-connrefused
-    UNAME_S := $(shell uname -s)
-    ifeq ($(UNAME_S),Linux)
-# LINUX
-    endif
-    ifeq ($(UNAME_S),Darwin)
-# OSX
-    endif
-    UNAME_P := $(shell uname -p)
-    ifeq ($(UNAME_P),x86_64)
-# ->AMD64
-    endif
-    ifneq ($(filter %86,$(UNAME_P)),)
-# ->IA32
-    endif
-    ifneq ($(filter arm%,$(UNAME_P)),)
-# ->ARM
-    endif
 endif
-#############################################################
 
-GITHUB_TOOLCHAIN = https://github.com/jmattsson/esp-toolchains
 GITHUB_SDK       = https://github.com/espressif/ESP8266_NONOS_SDK
 GITHUB_ESPTOOL   = https://github.com/espressif/esptool
 
 ESPTOOL ?= $(TOP_DIR)/tools/toolchains/esptool.py
 
-CSRCS ?= $(wildcard *.c)
-CXXSRCS ?= $(wildcard *.cpp)
-ASRCs ?= $(wildcard *.s)
-ASRCS ?= $(wildcard *.S)
 SUBDIRS ?= $(patsubst %/,%,$(dir $(filter-out tools/Makefile,$(wildcard */Makefile))))
 
-ODIR := .output
+ODIR    := .output
+
+ifdef TARGET
+CSRCS   ?= $(wildcard *.c)
+CXXSRCS ?= $(wildcard *.cpp)
+ASRCs   ?= $(wildcard *.s)
+ASRCS   ?= $(wildcard *.S)
+
 OBJODIR := $(ODIR)/$(TARGET)/$(FLAVOR)/obj
 
 OBJS := $(CSRCS:%.c=$(OBJODIR)/%.o) \
@@ -159,19 +191,19 @@ BINODIR := $(ODIR)/$(TARGET)/$(FLAVOR)/bin
 OBINS := $(GEN_BINS:%=$(BINODIR)/%)
 
 ifndef PDIR
-ifneq ($(wildcard $(TOP_DIR)/local/fs/*),)
-SPECIAL_MKTARGETS += spiffs-image
-else
-SPECIAL_MKTARGETS += spiffs-image-remove
+  ifneq ($(wildcard $(TOP_DIR)/local/fs/*),)
+    SPECIAL_MKTARGETS += spiffs-image
+  else
+    SPECIAL_MKTARGETS += spiffs-image-remove
+  endif
 endif
-endif
-
+endif   # TARGET
 #
 # Note:
 # https://gcc.gnu.org/onlinedocs/gcc/Optimize-Options.html
-# If you add global optimize options like "-O2" here
-# they will override "-Os" defined above.
-# "-Os" should be used to reduce code size
+# If you add global optimize options then they will override "-Os" defined above.
+# Note that "-Os" should NOT be used to reduce code size because of the runtime
+# impact of the extra non-aligned exception burdon.
 #
 CCFLAGS += 			\
 	-g			\
@@ -193,6 +225,8 @@ DFLAGS = $(CCFLAGS) $(DDEFINES) $(EXTRA_CCFLAGS) $(STD_CFLAGS) $(INCLUDES)
 # Functions
 #
 
+ifdef TARGET
+
 define ShortcutRule
 $(1): .subdirs $(2)/$(1)
 endef
@@ -225,39 +259,46 @@ $(BINODIR)/%.bin: $(IMAGEODIR)/%.out
 	$(summary) ESPTOOL $(patsubst $(TOP_DIR)/%,%,$(CURDIR))/$< $(FIRMWAREDIR)
 	$(ESPTOOL) elf2image --flash_mode dio --flash_freq 40m $< -o $(FIRMWAREDIR)
 
+endif # TARGET
 #############################################################
 # Rules base
 # Should be done in top-level makefile only
 #
 
-all: toolchain sdk_pruned pre_build .subdirs $(OBJS) $(OLIBS) $(OIMAGES) $(OBINS) $(SPECIAL_MKTARGETS)
+ifndef TARGET
+all: toolchain sdk_pruned pre_build .subdirs
+else
+all: .subdirs $(OBJS) $(OLIBS) $(OIMAGES) $(OBINS) $(SPECIAL_MKTARGETS)
+endif
 
 .PHONY: sdk_extracted
 .PHONY: sdk_pruned
 .PHONY: toolchain
 
-sdk_extracted: $(TOP_DIR)/sdk/.extracted-$(SDK_BASE_VER)
-sdk_pruned: $(SDK_DIR_DEPENDS) $(TOP_DIR)/sdk/.pruned-$(SDK_VER)
+sdk_extracted: $(TOP_DIR)/sdk/.extracted-$(SDK_VER)
+sdk_pruned: sdk_extracted toolchain $(TOP_DIR)/sdk/.pruned-$(SDK_VER)
 
-ifeq ($(OS),Windows_NT)
-toolchain:
-else
-toolchain: $(TOP_DIR)/tools/toolchains/esp8266-$(PLATFORM)-$(TOOLCHAIN_VERSION)/bin/xtensa-lx106-elf-gcc $(TOP_DIR)/tools/toolchains/esptool.py
+ifdef GITHUB_TOOLCHAIN
+  TOOLCHAIN_ROOT := $(TOP_DIR)/tools/toolchains/esp8266-linux-x86_64-$(TOOLCHAIN_VERSION)
+
+toolchain: $(TOOLCHAIN_ROOT)/bin $(ESPTOOL)
 
-$(TOP_DIR)/tools/toolchains/esp8266-$(PLATFORM)-$(TOOLCHAIN_VERSION)/bin/xtensa-lx106-elf-gcc: $(TOP_DIR)/cache/toolchain-esp8266-$(PLATFORM)-$(TOOLCHAIN_VERSION).tar.xz
+$(TOOLCHAIN_ROOT)/bin: $(TOP_DIR)/cache/toolchain-esp8266-$(GCCTOOLCHAIN).tar.xz
 	mkdir -p $(TOP_DIR)/tools/toolchains/
 	$(summary) EXTRACT $(patsubst $(TOP_DIR)/%,%,$<)
 	tar -xJf $< -C $(TOP_DIR)/tools/toolchains/
 	touch $@
 
-$(TOP_DIR)/cache/toolchain-esp8266-$(PLATFORM)-$(TOOLCHAIN_VERSION).tar.xz:
+$(TOP_DIR)/cache/toolchain-esp8266-$(GCCTOOLCHAIN).tar.xz:
 	mkdir -p $(TOP_DIR)/cache
 	$(summary) WGET $(patsubst $(TOP_DIR)/%,%,$@)
-	$(WGET) $(GITHUB_TOOLCHAIN)/releases/download/$(PLATFORM)-$(TOOLCHAIN_VERSION)/toolchain-esp8266-$(PLATFORM)-$(TOOLCHAIN_VERSION).tar.xz -O $@ \
+	$(WGET) $(GITHUB_TOOLCHAIN)/releases/download/$(GCCTOOLCHAIN)/toolchain-esp8266-$(GCCTOOLCHAIN).tar.xz -O $@ \
 	|| { rm -f "$@"; exit 1; }
+else
+toolchain: $(ESPTOOL)
 endif
 
-$(TOP_DIR)/tools/toolchains/esptool.py: $(TOP_DIR)/cache/esptool/v$(ESPTOOL_VER).tar.gz
+$(ESPTOOL): $(TOP_DIR)/cache/esptool/v$(ESPTOOL_VER).tar.gz
 	mkdir -p $(TOP_DIR)/tools/toolchains/
 	tar -C $(TOP_DIR)/tools/toolchains/ -xzf $< --strip-components=1 esptool-$(ESPTOOL_VER)/esptool.py
 	chmod +x $@
@@ -267,31 +308,32 @@ $(TOP_DIR)/cache/esptool/v$(ESPTOOL_VER).tar.gz:
 	mkdir -p $(TOP_DIR)/cache/esptool/
 	$(WGET) $(GITHUB_ESPTOOL)/archive/v$(ESPTOOL_VER).tar.gz -O $@ || { rm -f "$@"; exit 1; }
 
-$(TOP_DIR)/sdk/.extracted-$(SDK_BASE_VER): $(TOP_DIR)/cache/v$(SDK_FILE_VER).zip
+$(TOP_DIR)/sdk/.extracted-$(SDK_VER): $(TOP_DIR)/cache/$(SDK_FILE_VER).zip
 	mkdir -p "$(dir $@)"
 	$(summary) UNZIP $(patsubst $(TOP_DIR)/%,%,$<)
 	(cd "$(dir $@)" && \
-	 rm -fr esp_iot_sdk_v$(SDK_VER) ESP8266_NONOS_SDK-$(SDK_BASE_VER) && \
-	 unzip $(TOP_DIR)/cache/v$(SDK_FILE_VER).zip \
-	       'ESP8266_NONOS_SDK-$(SDK_BASE_VER)/lib/*' \
-	       'ESP8266_NONOS_SDK-$(SDK_BASE_VER)/ld/*.v6.ld' \
-	       'ESP8266_NONOS_SDK-$(SDK_BASE_VER)/include/*' \
-	       'ESP8266_NONOS_SDK-$(SDK_BASE_VER)/bin/esp_init_data_default_v05.bin' \
+	 rm -fr esp_iot_sdk_v$(SDK_VER) ESP8266_NONOS_SDK-* && \
+	 unzip $(TOP_DIR)/cache/$(SDK_FILE_VER).zip \
+	       '$(SDK_ZIP_ROOT)/lib/*' \
+	       '$(SDK_ZIP_ROOT)/ld/*.v6.ld' \
+	       '$(SDK_ZIP_ROOT)/include/*' \
+	       '$(SDK_ZIP_ROOT)/bin/esp_init_data_default_v05.bin' \
 	)
-	mv $(dir $@)/ESP8266_NONOS_SDK-$(SDK_BASE_VER) $(dir $@)/esp_iot_sdk_v$(SDK_BASE_VER)
+	mv $(dir $@)/$(SDK_ZIP_ROOT) $(dir $@)/esp_iot_sdk_v$(SDK_VER)
 	touch $@
 
 $(TOP_DIR)/sdk/.pruned-$(SDK_VER):
 	rm -f $(SDK_DIR)/lib/liblwip.a $(SDK_DIR)/lib/libssl.a $(SDK_DIR)/lib/libmbedtls.a
 	$(summary) PRUNE libmain.a libc.a
+	echo $(PATH)
 	$(AR) d $(SDK_DIR)/lib/libmain.a time.o
 	$(AR) d $(SDK_DIR)/lib/libc.a lib_a-time.o
 	touch $@
 
-$(TOP_DIR)/cache/v$(SDK_FILE_VER).zip:
+$(TOP_DIR)/cache/$(SDK_FILE_VER).zip:
 	mkdir -p "$(dir $@)"
 	$(summary) WGET $(patsubst $(TOP_DIR)/%,%,$@)
-	$(WGET) $(GITHUB_SDK)/archive/v$(SDK_FILE_VER).zip -O $@ || { rm -f "$@"; exit 1; }
+	$(WGET) $(GITHUB_SDK)/archive/$(SDK_FILE_VER).zip -O $@ || { rm -f "$@"; exit 1; }
 	(echo "$(SDK_FILE_SHA1)  $@" | sha1sum -c -) || { rm -f "$@"; exit 1; }
 
 clean:
@@ -329,15 +371,12 @@ endif
 .subdirs:
 	@set -e; $(foreach d, $(SUBDIRS), $(MAKE) -C $(d);)
 
-#.subdirs:
-#	$(foreach d, $(SUBDIRS), $(MAKE) -C $(d))
-
 ifneq ($(MAKECMDGOALS),clean)
-ifneq ($(MAKECMDGOALS),clobber)
-ifdef DEPS
-sinclude $(DEPS)
-endif
-endif
+  ifneq ($(MAKECMDGOALS),clobber)
+    ifdef DEPS
+      sinclude $(DEPS)
+    endif
+  endif
 endif
 
 .PHONY: spiffs-image-remove
@@ -365,6 +404,7 @@ pre_build:
 	@-rm -f $(TOP_DIR)/app/modules/server-ca.crt.h
 endif
 
+ifdef TARGET
 $(OBJODIR)/%.o: %.c
 	@mkdir -p $(dir $@);
 	$(summary) CC $(patsubst $(TOP_DIR)/%,%,$(CURDIR))/$<
@@ -424,6 +464,7 @@ $(foreach lib,$(GEN_LIBS),$(eval $(call MakeLibrary,$(basename $(lib)))))
 
 $(foreach image,$(GEN_IMAGES),$(eval $(call MakeImage,$(basename $(image)))))
 
+endif # TARGET
 #############################################################
 # Recursion Magic - Don't touch this!!
 #

+ 17 - 6
app/include/user_config.h

@@ -41,9 +41,12 @@
 
 // The Lua Flash Store (LFS) allows you to store Lua code in Flash memory and
 // the Lua VMS will execute this code directly from flash without needing any
-// RAM overhead.  Note that you should now configure LFS directly in the 
-// System Partition Table and not at build time.
+// RAM overhead.  You can now configure LFS directly in the System Partition
+// Table insted of at compile time. However for backwards compatibility setting
+// LUA_FLASH_STORE defines the default partition size if the NodeMCU partition
+// tool is not used.
 
+//#define LUA_FLASH_STORE                   0x10000
 
 // By default Lua executes the file init.lua at start up.  The following
 // define allows you to replace this with an alternative startup.  Warning:
@@ -68,11 +71,14 @@
 // general, limiting the size of the FS only to what your application needs
 // gives the fastest start-up and imaging times.
 
-// Note that you should now configure SPIFFS size and position directly in the 
-// System Partition Table and not at build time.
+// You can now configure SPIFFS size and position directly in the System
+// Partition Table.  However backwards compatibility SPIFFS_MAX_FILESYSTEM_SIZE
+// can be set and this defines the default SPIFFS partition size if the NodeMCU
+// partition tool is not used. The value (~0x0) means the maximum size remaining.
 
 #define BUILD_SPIFFS
 #define SPIFFS_CACHE 1          // Enable if you use you SPIFFS in R/W mode
+//#define SPIFFS_MAX_FILESYSTEM_SIZE 0x20000
 #define SPIFFS_MAX_OPEN_FILES 4 // maximum number of open files for SPIFFS
 #define FS_OBJ_NAME_LEN 31      // maximum length of a filename
 
@@ -211,9 +217,14 @@
 #define NODEMCU_SPIFFS0_PARTITION         6
 #define NODEMCU_SPIFFS1_PARTITION         7
 
-#define LUA_FLASH_STORE                   0x0
+#ifndef LUA_FLASH_STORE
+#  define LUA_FLASH_STORE                 0x0
+#endif
+
 #define SPIFFS_FIXED_LOCATION             0x0
-#define SPIFFS_MAX_FILESYSTEM_SIZE        (~0x0)
+#ifndef SPIFFS_MAX_FILESYSTEM_SIZE
+#  define SPIFFS_MAX_FILESYSTEM_SIZE      0xFFFFFFFF
+#endif
 //#define SPIFFS_SIZE_1M_BOUNDARY
 
 #define LUA_TASK_PRIO             USER_TASK_PRIO_0

+ 7 - 7
app/include/user_modules.h

@@ -7,7 +7,7 @@
 // includes general purpose interface modules which require at most two GPIO pins.
 // See https://github.com/nodemcu/nodemcu-firmware/pull/1127 for discussions.
 // New modules should be disabled by default and added in alphabetical order.
-#define LUA_USE_MODULES_ADC
+//#define LUA_USE_MODULES_ADC
 //#define LUA_USE_MODULES_ADS1115
 //#define LUA_USE_MODULES_ADXL345
 //#define LUA_USE_MODULES_AM2320
@@ -21,22 +21,22 @@
 //#define LUA_USE_MODULES_COLOR_UTILS
 //#define LUA_USE_MODULES_CRON
 //#define LUA_USE_MODULES_CRYPTO
-#define LUA_USE_MODULES_DHT
+//#define LUA_USE_MODULES_DHT
 //#define LUA_USE_MODULES_ENCODER
 //#define LUA_USE_MODULES_ENDUSER_SETUP // USE_DNS in dhcpserver.h needs to be enabled for this module to work.
 #define LUA_USE_MODULES_FILE
-//#define LUA_USE_MODULES_GDBSTUB
-#define LUA_USE_MODULES_GPIO
+#define LUA_USE_MODULES_GDBSTUB
+//#define LUA_USE_MODULES_GPIO
 //#define LUA_USE_MODULES_GPIO_PULSE
 //#define LUA_USE_MODULES_HDC1080
 //#define LUA_USE_MODULES_HMC5883L
 //#define LUA_USE_MODULES_HTTP
 //#define LUA_USE_MODULES_HX711
-#define LUA_USE_MODULES_I2C
+//#define LUA_USE_MODULES_I2C
 //#define LUA_USE_MODULES_L3G4200D
 //#define LUA_USE_MODULES_MCP4725
 //#define LUA_USE_MODULES_MDNS
-#define LUA_USE_MODULES_MQTT
+//#define LUA_USE_MODULES_MQTT
 #define LUA_USE_MODULES_NET
 #define LUA_USE_MODULES_NODE
 #define LUA_USE_MODULES_OW
@@ -54,7 +54,7 @@
 //#define LUA_USE_MODULES_SJSON
 //#define LUA_USE_MODULES_SNTP
 //#define LUA_USE_MODULES_SOMFY
-#define LUA_USE_MODULES_SPI
+//#define LUA_USE_MODULES_SPI
 //#define LUA_USE_MODULES_SQLITE3
 //#define LUA_USE_MODULES_STRUCT
 //#define LUA_USE_MODULES_SWITEC

+ 1 - 0
app/libc/c_string.h

@@ -15,6 +15,7 @@
 
 #define c_memcmp os_memcmp
 #define c_memcpy os_memcpy
+#define c_memmove os_memmove
 #define c_memset os_memset
 
 #define c_strcat os_strcat

+ 12 - 12
app/modules/file.c

@@ -435,7 +435,7 @@ static int file_g_read( lua_State* L, int n, int16_t end_char, int fd )
     int nread   = vfs_read(fd, p, nwanted);
 
     if (nread == VFS_RES_ERR || nread == 0) {
-      lua_pushnil(L);     
+      lua_pushnil(L);
       return 1;
     }
 
@@ -495,19 +495,19 @@ static int file_readline( lua_State* L )
 static int file_getfile( lua_State* L )
 {
   // Warning this code C calls other file_* routines to avoid duplication code.  These
-  // use Lua stack addressing of arguments, so this does Lua stack maniplation to 
+  // use Lua stack addressing of arguments, so this does Lua stack maniplation to
   // align these
   int ret_cnt = 0;
   lua_settop(L ,1);
   // Stack [1] = FD
-  file_open(L); 
+  file_open(L);
   // Stack [1] = filename; [2] = FD or nil
   if (!lua_isnil(L, -1)) {
     lua_remove(L, 1);  // dump filename, so [1] = FD
     file_fd_ud *ud = (file_fd_ud *)luaL_checkudata(L, 1, "file.obj");
-    ret_cnt = file_g_read(L, LUAI_MAXINT32, EOF, ud->fd); 
-    // Stack [1] = FD; [2] = contents if ret_cnt = 1; 
-    file_close(L);     // leaves Stack unchanged if [1] = FD 
+    ret_cnt = file_g_read(L, LUAI_MAXINT32, EOF, ud->fd);
+    // Stack [1] = FD; [2] = contents if ret_cnt = 1;
+    file_close(L);     // leaves Stack unchanged if [1] = FD
     lua_remove(L, 1);  // Dump FD leaving contents as [1] / ToS
   }
   return ret_cnt;
@@ -557,23 +557,23 @@ static int file_writeline( lua_State* L )
 static int file_putfile( lua_State* L )
 {
   // Warning this code C calls other file_* routines to avoid duplication code.  These
-  // use Lua stack addressing of arguments, so this does Lua stack maniplation to 
+  // use Lua stack addressing of arguments, so this does Lua stack maniplation to
   // align these
   int ret_cnt = 0;
   lua_settop(L, 2);
   lua_pushvalue(L, 2); //dup contents onto the ToS [3]
   lua_pushliteral(L, "w+");
   lua_replace(L, 2);
-  // Stack [1] = filename; [2] "w+" [3] contents; 
-  file_open(L); 
-  // Stack [1] = filename; [2] "w+" [3] contents; [4] FD or nil 
+  // Stack [1] = filename; [2] "w+" [3] contents;
+  file_open(L);
+  // Stack [1] = filename; [2] "w+" [3] contents; [4] FD or nil
 
   if (!lua_isnil(L, -1)) {
     lua_remove(L, 2);  //dump "w+" attribute literal
     lua_replace(L, 1);
-    // Stack [1] = FD; [2] contents 
+    // Stack [1] = FD; [2] contents
     file_write(L);
-    // Stack [1] = FD; [2] contents; [3] result status 
+    // Stack [1] = FD; [2] contents; [3] result status
     lua_remove(L, 2);  //dump contents
     file_close(L);
     lua_remove(L, 1); // Dump FD leaving status as ToS

+ 177 - 1
app/modules/node.c

@@ -1,5 +1,4 @@
 // Module for interfacing with system
-
 #include "module.h"
 #include "lauxlib.h"
 
@@ -572,6 +571,177 @@ static int node_random (lua_State *L) {
   return 1;
 }
 
+#ifdef DEVELOPMENT_TOOLS
+// Lua: rec = node.readrcr(id)
+static int node_readrcr (lua_State *L) {
+  int id  = luaL_checkinteger(L, 1);
+  char *data;
+  int n = platform_rcr_read(id, (void **)&data);
+  if (n == ~0) return 0;
+  lua_pushlstring(L, data, n);
+  return 1;
+}
+// Lua: n = node.writercr(id,rec)
+static int node_writercr (lua_State *L) {
+  int id = luaL_checkinteger(L, 1),l;
+  const char *data = lua_tolstring(L, 2, &l);
+  int n = platform_rcr_write(id, data, l);
+  lua_pushinteger(L, n);
+  return 1;
+}
+#endif
+
+typedef enum pt_t { lfs_addr=0, lfs_size, spiffs_addr, spiffs_size, max_pt} pt_t;
+
+static const LUA_REG_TYPE pt_map[] = {
+  { LSTRKEY( "lfs_addr" ),    LNUMVAL( lfs_addr ) },
+  { LSTRKEY( "lfs_size" ),    LNUMVAL( lfs_size ) },
+  { LSTRKEY( "spiffs_addr" ), LNUMVAL( spiffs_addr ) },
+  { LSTRKEY( "spiffs_size" ), LNUMVAL( spiffs_size ) },
+  { LNILKEY, LNILVAL }
+};
+
+// Lua: ptinfo = node.getpartitiontable()
+static int node_getpartitiontable (lua_State *L) {
+  uint32_t param[max_pt] = {0};
+  param[lfs_size]    = platform_flash_get_partition(NODEMCU_LFS0_PARTITION, param + lfs_addr);
+  param[spiffs_size] = platform_flash_get_partition(NODEMCU_SPIFFS0_PARTITION, param + spiffs_addr);
+
+  lua_settop(L, 0);
+  lua_createtable (L, 0, max_pt);                   /* at index 1 */
+  lua_pushrotable(L, (void*)pt_map);                /* at index 2 */
+  lua_pushnil(L);                                   /* first key at index 3 */
+  while (lua_next(L, 2) != 0) {                     /* key at index 3, and v at index 4 */
+    lua_pushvalue(L, 3);                            /* dup key to index 5 */
+    lua_pushinteger(L, param[lua_tointeger(L, 4)]); /* param [v] at index 6 */
+    lua_rawset(L, 1);
+    lua_pop(L, 1);                                  /* discard v */
+  }
+  lua_pop(L, 1);                                    /* discard pt_map reference */
+  return 1;
+}
+
+static void insert_partition(partition_item_t *p, int n, uint32_t type, uint32_t addr) {
+  if (n>0)
+    c_memmove(p+1, p, n*sizeof(partition_item_t)); /* overlapped so must be move not cpy */
+  p->type = type;
+  p->addr = addr;
+  p->size = 0;
+}
+
+static void delete_partition(partition_item_t *p, int n) {
+  if (n>0)
+    c_memmove(p, p+1, n*sizeof(partition_item_t)); /* overlapped so must be move not cpy */
+}
+
+#define SKIP (~0)
+#define IROM0_PARTITION  (SYSTEM_PARTITION_CUSTOMER_BEGIN + NODEMCU_IROM0TEXT_PARTITION)
+#define LFS_PARTITION    (SYSTEM_PARTITION_CUSTOMER_BEGIN + NODEMCU_LFS0_PARTITION)
+#define SPIFFS_PARTITION (SYSTEM_PARTITION_CUSTOMER_BEGIN + NODEMCU_SPIFFS0_PARTITION)
+
+// Lua: node.setpartitiontable(pt_settings)
+static int node_setpartitiontable (lua_State *L) {
+  partition_item_t *rcr_pt = NULL, *pt;
+  uint32_t flash_size = flash_rom_get_size_byte();
+  uint32_t i = platform_rcr_read(PLATFORM_RCR_PT, (void **) &rcr_pt);
+  uint32_t last = 0;
+  uint32_t n = i / sizeof(partition_item_t);
+  uint32_t param[max_pt] = {SKIP, SKIP, SKIP, SKIP};
+
+  luaL_argcheck(L, lua_istable(L, 1), 1, "must be table");
+  lua_settop(L, 1);
+  /* convert input table into 4 option array */
+  lua_pushrotable(L, (void*)pt_map);  /* at index 2 */
+  lua_pushnil(L);                   /* first key at index 3 */
+  while (lua_next(L, 1) != 0) {
+    /* 'key' (at index 3) and 'value' (at index 4) */
+    luaL_argcheck(L, lua_isstring(L, 3) && lua_isnumber(L, 4), 1, "invalid partition setting");
+    lua_pushvalue(L, 3);  /* dup key to index 5 */
+    lua_rawget(L, 2);     /* lookup in pt_map */
+    luaL_argcheck(L, !lua_isnil(L, -1), 1, "invalid partition setting");
+    param[lua_tointeger(L, 5)] = lua_tointeger(L, 4);
+    /* removes 'value'; keeps 'key' for next iteration */
+    lua_pop(L, 2);  /* discard value and lookup */
+  }
+ /*
+  * Allocate a scratch Partition Table as userdata on the Lua stack, and copy the
+  * current Flash PT into this for manipulation
+  */
+  lua_newuserdata(L, (n+2)*sizeof(partition_item_t));
+  pt = lua_touserdata (L, -1);
+  c_memcpy(pt, rcr_pt, n*sizeof(partition_item_t));
+  pt[n].type = 0; pt[n+1].type = 0;
+
+  for (i = 0; i < n; i ++) {
+    partition_item_t *p = pt + i;
+
+    if (p->type == IROM0_PARTITION && p[1].type != LFS_PARTITION) {
+      // if the LFS partition is not following IROM0 then slot a blank one in
+      insert_partition(p + 1, n-i-1, LFS_PARTITION, p->addr + p->size);
+      n++;
+
+    } else if (p->type == LFS_PARTITION) {
+      if (p[1].type != SPIFFS_PARTITION) {
+        // if the SPIFFS partition is not following LFS then slot a blank one in
+        insert_partition(p + 1, n-i-1, SPIFFS_PARTITION, 0);
+        n++;
+      }
+      // update the LFS options if set
+      if (param[lfs_addr] != SKIP) {
+        p->addr = param[lfs_addr];
+      }
+      if (param[lfs_size] != SKIP) {
+        p->size = param[lfs_size];
+      }
+    } else if (p->type == SPIFFS_PARTITION) {
+      // update the SPIFFS options if set
+      if (param[spiffs_addr] != SKIP) {
+        p->addr = param[spiffs_addr];
+        p->size = SKIP;
+      }
+      if (param[spiffs_size] != SKIP) {
+        // BOTCH: - at the moment the firmware doesn't boot if the SPIFFS partition
+        //          is deleted so the minimum SPIFFS size is 64Kb
+        p->size = param[spiffs_size] > 0x10000 ? param[spiffs_size] : 0x10000;
+      }
+      if (p->size == SKIP) {
+        if (p->addr < 0) {
+          // This allocate all the remaining flash to SPIFFS
+          p->addr = last;
+          p->size = flash_size - last;
+        } else {
+          p->size = flash_size - p->addr;
+        }
+      } else if (/* size is specified && */ p->addr == 0) {
+        // if the is addr not specified then start SPIFFS at 1Mb
+        // boundary if the size will fit otherwise make it consecutive
+        // to the previous partition.
+        p->addr = (p->size <= flash_size - 0x100000) ? 0x100000 : last;
+      }
+    }
+
+    if (p->size == 0) {
+      // Delete 0-sized partitions as the SDK barfs on these
+      delete_partition(p, n-i-1);
+      n--; i--;
+    } else {
+      // Do consistency tests on the partition
+      if (p->addr & (INTERNAL_FLASH_SECTOR_SIZE - 1) ||
+        p->size & (INTERNAL_FLASH_SECTOR_SIZE - 1) ||
+        p->addr < last ||
+        p->addr + p->size > flash_size) {
+        luaL_error(L, "value out of range");
+      }
+    }
+  }
+//  for (i = 0; i < n; i ++)
+//    dbg_printf("Partition %d: %04x %06x %06x\n", i, pt[i].type, pt[i].addr, pt[i].size);
+    platform_rcr_write(PLATFORM_RCR_PT, pt, n*sizeof(partition_item_t));
+    while(1); // Trigger WDT; the new PT will be loaded on reboot
+
+  return 0;
+}
+
 
 // Module function map
 
@@ -605,6 +775,10 @@ static const LUA_REG_TYPE node_map[] =
   { LSTRKEY( "sleep" ), LFUNCVAL( node_sleep ) },
 #ifdef PMSLEEP_ENABLE
   PMSLEEP_INT_MAP,
+#endif
+#ifdef DEVELOPMENT_TOOLS
+  { LSTRKEY( "readrcr" ), LFUNCVAL( node_readrcr ) },
+  { LSTRKEY( "writercr" ), LFUNCVAL( node_writercr ) },
 #endif
   { LSTRKEY( "chipid" ), LFUNCVAL( node_chipid ) },
   { LSTRKEY( "flashid" ), LFUNCVAL( node_flashid ) },
@@ -628,6 +802,8 @@ static const LUA_REG_TYPE node_map[] =
 #ifdef DEVELOPMENT_TOOLS
   { LSTRKEY( "osprint" ), LFUNCVAL( node_osprint ) },
 #endif
+  { LSTRKEY( "getpartitiontable" ), LFUNCVAL( node_getpartitiontable ) },
+  { LSTRKEY( "setpartitiontable" ), LFUNCVAL( node_setpartitiontable ) },
 
 // Combined to dsleep(us, option)
 // { LSTRKEY( "dsleepsetoption" ), LFUNCVAL( node_deepsleep_setoption) },

+ 125 - 4
app/platform/platform.c

@@ -903,10 +903,10 @@ uint32_t platform_s_flash_read( void *to, uint32_t fromaddr, uint32_t size )
     r = flash_read(fromaddr, to2, size2);
     if(SPI_FLASH_RESULT_OK == r)
     {
-      os_memmove(to,to2,size2);
+      c_memmove(to,to2,size2);  // This is overlapped so must be memmove and not memcpy
       char back[ INTERNAL_FLASH_READ_UNIT_SIZE ] __attribute__ ((aligned(INTERNAL_FLASH_READ_UNIT_SIZE)));
       r=flash_read(fromaddr+size2,(uint32*)back,INTERNAL_FLASH_READ_UNIT_SIZE);
-      os_memcpy((uint8_t*)to+size2,back,INTERNAL_FLASH_READ_UNIT_SIZE);
+      c_memcpy((uint8_t*)to+size2,back,INTERNAL_FLASH_READ_UNIT_SIZE);
     }
   }
   else
@@ -936,7 +936,7 @@ static uint32_t flash_map_meg_offset (void) {
   return m0 + m1;
 }
 
-uint32_t platform_flash_mapped2phys (uint32_t mapped_addr)  {
+uint32_t platform_flash_mapped2phys (uint32_t mapped_addr) {
   uint32_t meg = flash_map_meg_offset();
   return (meg&1) ? -1 : mapped_addr - INTERNAL_FLASH_MAPPED_ADDRESS + meg ;
 }
@@ -946,7 +946,7 @@ uint32_t platform_flash_phys2mapped (uint32_t phys_addr) {
   return (meg&1) ? -1 : phys_addr + INTERNAL_FLASH_MAPPED_ADDRESS - meg;
 }
 
-uint32_t platform_flash_get_partition (uint32_t part_id, uint32_t *addr)  {
+uint32_t platform_flash_get_partition (uint32_t part_id, uint32_t *addr) {
   partition_item_t pt = {0,0,0};
   system_partition_get_item(SYSTEM_PARTITION_CUSTOMER_BEGIN + part_id, &pt);
   if (addr) {
@@ -954,6 +954,127 @@ uint32_t platform_flash_get_partition (uint32_t part_id, uint32_t *addr)  {
   }
   return  pt.type == 0 ? 0 : pt.size;
 }
+/*
+ * The Reboot Config Records are stored in the 4K flash page at offset 0x10000 (in
+ * the linker section .irom0.ptable) and is used for configuration changes that
+ * persist across reboots. This page contains a sequence of records, each of which
+ * is word-aligned and comprises a header and body of length 0-64 words.  The 4-byte
+ * header comprises a length, a RCR id, and two zero fill bytes.  These are written
+ * using flash NAND writing rules, so any unused area (all 0xFF) can be overwritten
+ * by a new record without needing to erase the RCR page.  Ditto any existing
+ * record can be marked as deleted by over-writing the header with the id set to
+ * PLATFORM_RCR_DELETED (0x0).  Note that the last word is not used additions so a
+ * scan for PLATFORM_RCR_FREE will always terminate.
+ *
+ * The number of updates is extremely low, so it is unlikely (but possible) that
+ * the page might fill with the churn of new RCRs, so in this case the write function
+ * compacts the page by eliminating all deleted records.  This does require a flash
+ * sector erase.
+ *
+ * NOTE THAT THIS ALGO ISN'T 100% ROBUST, eg. a powerfail between the erase and the
+ * wite-back will leave the page unitialised; ditto a powerfail between the record
+ * appned and old deletion will leave two records.  However this is better than the
+ * general integrity of SPIFFS, for example and the vulnerable window is typically
+ * less than 1 mSec every configuration change.
+ */
+extern uint32_t _irom0_text_start[];
+#define RCR_WORD(i) (_irom0_text_start[i])
+#define WORDSIZE sizeof(uint32_t)
+#define FLASH_SECTOR_WORDS (INTERNAL_FLASH_SECTOR_SIZE/WORDSIZE)
+
+uint32_t platform_rcr_read (uint8_t rec_id, void **rec) {
+//DEBUG os_printf("platform_rcr_read(%d,%08x)\n",rec_id,rec);
+    platform_rcr_t *rcr = (platform_rcr_t *) &RCR_WORD(0);
+    uint32_t i = 0;
+   /*
+    * Chain down the RCR page looking for a record that matches the record
+    * ID. If found return the size of the record and optionally its address.
+    */
+   while (1) {
+        // copy RCR header into RAM to avoid unaligned exceptions
+        platform_rcr_t r = (platform_rcr_t) RCR_WORD(i);
+        if (r.id == rec_id) {
+            if (rec) *rec = &RCR_WORD(i+1);
+            return r.len * WORDSIZE;
+        } else if (r.id == PLATFORM_RCR_FREE) {
+            break;
+        }
+        i += 1 + r.len;
+    }
+    return ~0;
+}
+
+/*
+ * Chain down the RCR page and look for an existing record that matches the record
+ * ID and the first free record.  If there is enough room, then append the new
+ * record and mark any previous record as deleted.  If the page is full then GC,
+ * erase the page and rewrite with the GCed content.
+ */
+#define MAXREC 65
+uint32_t platform_rcr_write (uint8_t rec_id, const void *inrec, uint8_t n) {
+    uint32_t nwords = (n+WORDSIZE-1) / WORDSIZE;
+    uint32_t reclen = (nwords+1)*WORDSIZE;
+    uint32_t *prev=NULL, *new = NULL;
+
+    // make local stack copy of inrec including header and any trailing fill bytes
+    uint32_t rec[MAXREC];
+    if (nwords >= MAXREC)
+        return ~0;
+    rec[0] = 0; rec[nwords] = 0;
+    ((platform_rcr_t *) rec)->id = rec_id;
+    ((platform_rcr_t *) rec)->len = nwords;
+    c_memcpy(rec+1, inrec, n);  // let memcpy handle 0 and odd byte cases
+
+    // find previous copy if any and exit if the replacement is the same value
+    uint8_t np = platform_rcr_read (rec_id, (void **) &prev);
+    if (prev && !os_memcmp(prev-1, rec, reclen))
+        return n;
+
+    // find next free slot
+    platform_rcr_read (PLATFORM_RCR_FREE, (void **) &new);
+    uint32_t nfree = &RCR_WORD(FLASH_SECTOR_WORDS) - new;
+
+    // Is there enough room to fit the rec in the RCR page?
+    if (nwords < nfree) {  // Note inequality needed to leave at least one all set word
+        uint32_t addr = platform_flash_mapped2phys((uint32_t)&new[-1]);
+        platform_s_flash_write(rec, addr, reclen);
+
+        if (prev) { // If a previous exists, then overwrite the hdr as DELETED
+            platform_rcr_t rcr = {0};
+            addr = platform_flash_mapped2phys((uint32_t)&prev[-1]);
+            rcr.id = PLATFORM_RCR_DELETED; rcr.len = np/WORDSIZE;
+            platform_s_flash_write(&rcr, addr, WORDSIZE);
+        }
+
+    } else {
+        platform_rcr_t *rcr = (platform_rcr_t *) &RCR_WORD(0), newrcr = {0};
+        uint32_t flash_addr = platform_flash_mapped2phys((uint32_t)&RCR_WORD(0));
+        uint32_t *buf, i, l, pass;
+
+        for (pass = 1; pass <= 2; pass++)  {
+            for (i = 0, l = 0; i < FLASH_SECTOR_WORDS - nfree; ) {
+                platform_rcr_t r = rcr[i]; // again avoid unaligned exceptions
+                if (r.id == PLATFORM_RCR_FREE)
+                    break;
+                if (r.id != PLATFORM_RCR_DELETED && r.id != rec_id) {
+                    if (pass == 2) memcpy(buf + l, rcr + i, (r.len + 1)*WORDSIZE);
+                    l += r.len + 1;
+                }
+                i += r.len + 1;
+            }
+            if (pass == 2) memcpy(buf + l, rec, reclen);
+            l += nwords + 1;
+            if (pass == 1) buf = c_malloc(l * WORDSIZE);
+
+            if (l >= FLASH_SECTOR_WORDS || !buf)
+                return ~0;
+        }
+        platform_flash_erase_sector(flash_addr/INTERNAL_FLASH_SECTOR_SIZE);
+        platform_s_flash_write(buf, flash_addr, l*WORDSIZE);
+        c_free(buf);
+    }
+    return nwords*WORDSIZE;
+}
 
 void* platform_print_deprecation_note( const char *msg, const char *time_frame)
 {

+ 36 - 7
app/platform/platform.h

@@ -291,13 +291,6 @@ uint32_t platform_flash_mapped2phys (uint32_t mapped_addr);
 uint32_t platform_flash_phys2mapped (uint32_t phys_addr);
 uint32_t platform_flash_get_partition (uint32_t part_id, uint32_t *addr);
 
-// *****************************************************************************
-// Allocator support
-
-void* platform_get_first_free_ram( unsigned id );
-void* platform_get_last_free_ram( unsigned id );
-
-
 // *****************************************************************************
 // Other glue
 
@@ -324,4 +317,40 @@ void* platform_print_deprecation_note( const char *msg, const char *time_frame);
   if( !platform_ ## mod ## _check_ ## resmod ## _id( id, resid ) )\
     return luaL_error( L, #resmod" %d not valid with " #mod " %d", ( unsigned )resid, ( unsigned )id )
 
+// *****************************************************************************
+// Reboot config page
+/*
+ * The 4K flash page in the linker section .irom0.ptable (offset 0x10000) is used 
+ * for configuration changes that persist across reboots. This 4k page contains a
+ * sequence of records that are written using flash NAND writing rules.  See the
+ * header app/spiffs/spiffs_nucleus.h for a discussion of how SPIFFS uses these. A
+ * similar technique is used here.
+ *
+ * Each record is word aligned and the first two bytes of the record are its size 
+ * and record type. Type 0xFF means unused and type 0x00 means deleted.  New 
+ * records can be added by overwriting the first unused slot.  Records can be 
+ * replaced by adding the new version, then setting the type of the previous version
+ * to deleted.  This all works and can be implemented with platform_s_flash_write()
+ * upto the 4K limit.
+ *
+ * If a new record cannot fit into the page then the deleted records are GCed by 
+ * copying the active records into a RAM scratch pad, erasing the page and writing 
+ * them back.  Yes, this is powerfail unsafe for a few mSec, but this is no worse 
+ * than writing to SPIFFS and won't even occur in normal production use.   
+ */
+#define IROM_PTABLE_ATTR          __attribute__((section(".irom0.ptable")))
+#define PLATFORM_PARTITION(n)  (SYSTEM_PARTITION_CUSTOMER_BEGIN + n)
+#define PLATFORM_RCR_DELETED   0x0
+#define PLATFORM_RCR_PT        0x1
+#define PLATFORM_RCR_PHY_DATA  0x2   
+#define PLATFORM_RCR_REFLASH   0x3
+#define PLATFORM_RCR_FREE      0xFF
+typedef union {
+    uint32_t hdr;
+    struct { uint8_t len,id; };
+} platform_rcr_t;
+
+uint32_t platform_rcr_read (uint8_t rec_id, void **rec);
+uint32_t platform_rcr_write (uint8_t rec_id, const void *rec, uint8_t size);
+
 #endif

+ 185 - 107
app/user/user_main.c

@@ -33,43 +33,66 @@ static task_handle_t input_sig;
 static uint8 input_sig_flag = 0;
 
 /* Contents of esp_init_data_default.bin */
-extern const uint32_t init_data[];
-extern const uint32_t init_data_end[];
+extern const uint32_t init_data[], init_data_end[];
+#define INIT_DATA_SIZE ((init_data_end - init_data)*sizeof(uint32_t))
 __asm__(
   ".align 4\n"
   "init_data: .incbin \"" ESP_INIT_DATA_DEFAULT "\"\n"
   "init_data_end:\n"
 );
-extern const char _irom0_text_start[], _irom0_text_end[],_flash_used_end[];
+extern const char _irom0_text_start[], _irom0_text_end[], _flash_used_end[];
 #define IROM0_SIZE (_irom0_text_end - _irom0_text_start)
 
-#define INIT_DATA_SIZE (init_data_end - init_data)
 
 #define PRE_INIT_TEXT_ATTR        __attribute__((section(".p3.pre_init")))
 #define IROM_PTABLE_ATTR          __attribute__((section(".irom0.ptable")))
+#define USED_ATTR                 __attribute__((used))
 
 #define PARTITION(n)  (SYSTEM_PARTITION_CUSTOMER_BEGIN + n)
 
 #define SIZE_256K       0x00040000
 #define SIZE_1024K      0x00100000
+#define PT_CHUNK        0x00002000
+#define PT_ALIGN(n) ((n + (PT_CHUNK-1)) & (~((PT_CHUNK-1))))
 #define FLASH_BASE_ADDR ((char *) 0x40200000)
 
-//TODO: map the TLS server and client certs into NODEMCU_TLSCERT_PARTITION
-const partition_item_t partition_init_table[] IROM_PTABLE_ATTR = {
-    { PARTITION(NODEMCU_EAGLEROM_PARTITION),     0x00000,     0x0B000},
-    { SYSTEM_PARTITION_RF_CAL,                   0x0B000,      0x1000},
-    { SYSTEM_PARTITION_PHY_DATA,                 0x0C000,      0x1000},
-    { SYSTEM_PARTITION_SYSTEM_PARAMETER,         0x0D000,      0x3000},
-    { PARTITION(NODEMCU_IROM0TEXT_PARTITION),    0x10000,      0x0000},
-    { PARTITION(NODEMCU_LFS0_PARTITION),             0x0, LUA_FLASH_STORE},
-    { PARTITION(NODEMCU_SPIFFS0_PARTITION),          0x0, SPIFFS_MAX_FILESYSTEM_SIZE},
+#define NODEMCU_PARTITION_EAGLEROM  PLATFORM_PARTITION(NODEMCU_EAGLEROM_PARTITION)
+#define NODEMCU_PARTITION_IROM0TEXT PLATFORM_PARTITION(NODEMCU_IROM0TEXT_PARTITION)
+#define NODEMCU_PARTITION_LFS       PLATFORM_PARTITION(NODEMCU_LFS0_PARTITION)
+#define NODEMCU_PARTITION_SPIFFS    PLATFORM_PARTITION(NODEMCU_SPIFFS0_PARTITION)
+
+#define MAX_PARTITIONS 20
+#define WORDSIZE       sizeof(uint32_t)
+#define PTABLE_SIZE    7   /** THIS MUST BE MATCHED TO NO OF PT ENTRIES BELOW **/
+struct defaultpt {
+  platform_rcr_t hdr;
+  partition_item_t pt[PTABLE_SIZE+1]; // the +! is for the endmarker
+  };
+#define PT_LEN (NUM_PARTITIONS*sizeof(partition_item_t))
+/*
+ * See app/platform/platform.h for how the platform reboot config records are used
+ * and these records are allocated.  The first record is a default partition table
+ * and this is statically declared in compilation below.
+ */
+static const struct defaultpt rompt IROM_PTABLE_ATTR USED_ATTR  = {
+  .hdr = {.len = sizeof(struct defaultpt)/WORDSIZE - 1,
+          .id  = PLATFORM_RCR_PT},
+  .pt  = {
+    { NODEMCU_PARTITION_EAGLEROM,         0x00000,     0x0B000},
+    { SYSTEM_PARTITION_RF_CAL,            0x0B000,      0x1000},
+    { SYSTEM_PARTITION_PHY_DATA,          0x0C000,      0x1000},
+    { SYSTEM_PARTITION_SYSTEM_PARAMETER,  0x0D000,      0x3000},
+    { NODEMCU_PARTITION_IROM0TEXT,        0x10000,      0x0000},
+    { NODEMCU_PARTITION_LFS,              0x0,          LUA_FLASH_STORE},
+    { NODEMCU_PARTITION_SPIFFS,           0x0,          SPIFFS_MAX_FILESYSTEM_SIZE},
     {0,(uint32_t) &_irom0_text_end,0}
+  }
 };
-// The following enum must maintain the partition table order
-enum partition {iram0=0, rf_call, phy_data, sys_parm, irom0, lfs, spiffs};
-#define PTABLE_SIZE ((sizeof(partition_init_table)/sizeof(partition_item_t))-1)
-#define PT_CHUNK 0x8000
-#define PT_ALIGN(n) ((n + (PT_CHUNK-1)) & (~((PT_CHUNK-1))))
+//TODO: map the TLS server and client certs into NODEMCU_TLSCERT_PARTITION
+
+static uint32_t first_time_setup(partition_item_t *pt, uint32_t n, uint32_t flash_size);
+static void phy_data_setup (partition_item_t *pt, uint32_t n);
+
 /*
  * The non-OS SDK prolog has been fundamentally revised in V3.  See SDK EN document
  * Partition Table.md for further discussion. This version of user_main.c is a
@@ -77,118 +100,173 @@ enum partition {iram0=0, rf_call, phy_data, sys_parm, irom0, lfs, spiffs};
  *
  * SDK V3 significantly reduces the RAM footprint required by the SDK and introduces
  * the use of a partition table (PT) to control flash allocation. The NodeMCU uses
- * this PT for overall allocation of its flash resources.  A constant copy PT is
- * maintained at the start of IROM0 (flash offset 0x10000) -- see partition_init_table
- * declaration above -- to facilitate its modification either in the firmware binary
- * or in the flash itself. This is Flash PT used during startup to create the live PT
- * in RAM that is used by the SDK.
+ * this PT for overall allocation of its flash resources. The non_OS SDK calls the
+ * user_pre_init() entry to do all of this startup configuration.  Note that this
+ * runs with Icache enabled -- that is the IROM0 partition is already mapped the
+ * address space at 0x40210000 and so that most SDK services are available, such
+ * as system_get_flash_size_map() which returns the valid flash size (including the
+ * 8Mb and 16Mb variants).
  *
- * Note that user_pre_init() runs with Icache enabled -- that is the IROM0 partition
- * is already mapped the address space at 0x40210000 and so that most SDK services
- * are available, such as system_get_flash_size_map() which returns the valid flash
- * size (including the 8Mb and 16Mb variants).
+ * The first 4K page of IROM0 (flash offset 0x10000) is used to maintain a set of
+ * Resource Communication Records (RCR) for inter-boot configuration using a NAND
+ * write-once algo (see app/platform/platform.h).  One of the current records is the
+ * SDK3.0 PT. This build statically compiles in an initial version at the start of
+ * the PT, with a {0, _irom0_text_end,0} marker as the last record and some fields
+ * also that need to be recomputed at runtime. This version is either replaced
+ * by first boot processing after provisioning, or by a node.setpartitiontable()
+ * API call. These replacement PTs are complete and can be passed directly for use
+ * by the non-OS SDK.
  *
- * We will be separately releasing a host PC-base python tool to configure the PT,
- * etc.,  but the following code will initialise the PT to sensible defaults even if
- * this tool isn't used.
+ * Note that we have released a host PC-base python tool, nodemcu-partition.py, to
+ * configure the PT, etc during provisioning.
  */
-static int setup_partition_table(partition_item_t *pt, uint32_t *n) {
-
-// Flash size lookup is SIZE_256K*2^N where N is as follows (see SDK/user_interface.h)
-    static char flash_size_scaler[] =
-  /*   0   1   2   3   4   5   6   7   8   9  */
-  /*  ½M  ¼M  1M  2M  4M  2M  4M  4M  8M 16M  */
-   "\001\000\002\003\004\003\004\004\005\006";
+void user_pre_init(void) {
+#ifdef LUA_USE_MODULES_RTCTIME
+  // Note: Keep this as close to call_user_start() as possible, since it
+  // is where the cpu clock actually gets bumped to 80MHz.
+    rtctime_early_startup ();
+#endif
+    partition_item_t *rcr_pt = NULL, *pt;
     enum flash_size_map fs_size_code = system_get_flash_size_map();
+// Flash size lookup is SIZE_256K*2^N where N is as follows (see SDK/user_interface.h)
+                                     /*   0   1   2   3   4   5   6   7   8   9  */
+                                     /*  ½M  ¼M  1M  2M  4M  2M  4M  4M  8M 16M  */
+    static char flash_size_scaler[] = "\001\000\002\003\004\003\004\004\005\006";
     uint32_t flash_size = SIZE_256K << flash_size_scaler[fs_size_code];
-    uint32_t first_free_flash_addr = partition_init_table[PTABLE_SIZE].addr
-                                     - (uint32_t) FLASH_BASE_ADDR;
-    int i,j;
-
-    os_memcpy(pt, partition_init_table, PTABLE_SIZE * sizeof(*pt));
 
+    uint32_t i = platform_rcr_read(PLATFORM_RCR_PT, (void **) &rcr_pt);
+    uint32_t n = i / sizeof(partition_item_t);
 
     if (flash_size < SIZE_1024K) {
         os_printf("Flash size (%u) too small to support NodeMCU\n", flash_size);
-        return -1;
+        return;
     } else {
         os_printf("system SPI FI size:%u, Flash size: %u\n", fs_size_code, flash_size );
     }
 
-// Calculate the runtime sized partitions
-// The iram0, rf_call, phy_data, sys_parm partitions are as-is.
-    if (pt[irom0].size == 0) {
-        pt[irom0].size = first_free_flash_addr - pt[irom0].addr;
-    }
-    if (pt[lfs].addr == 0) {
-        pt[lfs].addr = PT_ALIGN(pt[irom0].addr + pt[irom0].size);
-        os_printf("LFS base: %08X\n", pt[lfs].addr);
-    }
-    if (pt[lfs].size == 0) {
-        pt[lfs].size = 0x10000;
-        os_printf("LFS size: %08X\n", pt[lfs].size);
+    pt = os_malloc(i);  // We will work on and register a RAM copy of the PT
+    // Return if anything is amiss; The SDK will halt if the PT hasn't been registered
+    if ( !rcr_pt || !pt || n * sizeof(partition_item_t) != i) {
+        return;
     }
-    if (pt[spiffs].addr == 0) {
-        pt[spiffs].addr = PT_ALIGN(pt[lfs].addr + pt[lfs].size);
-        os_printf("SPIFFS base: %08X\n", pt[spiffs].addr);
+    os_memcpy(pt, rcr_pt, i);
+
+    if (pt[n-1].type == 0) {
+        // If the last PT entry is a {0,XX,0} end marker, then we need first time setup
+        n = first_time_setup(pt, n-1, flash_size); // return n because setup might shrink the PT
     }
 
-    if (pt[spiffs].size == SPIFFS_MAX_FILESYSTEM_SIZE) {
-      pt[spiffs].size = flash_size - pt[spiffs].addr;
-        os_printf("SPIFFS size: %08X\n", pt[spiffs].size);
+    if (platform_rcr_read(PLATFORM_RCR_PHY_DATA, NULL)!=0) {
+        phy_data_setup(pt, n);
     }
 
-//  Check that the phys data partition has been initialised and if not then do this
-//  now to prevent the SDK halting on a "rf_cal[0] !=0x05,is 0xFF" error.
-    uint32_t init_data_hdr = 0xffffffff, data_addr = pt[phy_data].addr;
-    int status = spi_flash_read(data_addr, &init_data_hdr, sizeof (uint32_t));
-    if (status == SPI_FLASH_RESULT_OK && *(char *)&init_data_hdr != 0x05) {
-        uint32_t idata[INIT_DATA_SIZE];
-        os_printf("Writing Init Data to 0x%08x\n",data_addr);
-        spi_flash_erase_sector(data_addr/SPI_FLASH_SEC_SIZE);
-        os_memcpy(idata, init_data, sizeof(idata));
-        spi_flash_write(data_addr, idata, sizeof(idata));
-        os_delay_us(1000);
+    // Now register the partition and return
+// for (i=0;i<n;i++) os_printf("P%d: %3d %06x %06x\n", i, pt[i].type, pt[i].addr, pt[i].size);
+    if( fs_size_code > 1 && system_partition_table_regist(pt, n, fs_size_code)) {
+        return;
     }
+    os_printf("Invalid system partition table\n");
+    while(1); // Trigger WDT}
+}
 
-// Check partitions are page aligned and remove and any zero-length partitions.
-// This must be done last as this might break the enum partition ordering.
-    for (i = 0, j = 0; i < PTABLE_SIZE; i++) {
-        const partition_item_t *p = pt + i;
-        if ((p->addr| p->size) & (SPI_FLASH_SEC_SIZE-1)) {
-            os_printf("Partitions must be flash page aligned\n");
-            return -1;
-        }
-        if (p->size == 0)
-          continue;
-        if (j < i) {
-          pt[j] = *p;
-          p = pt + j;
+/*
+ * If the PLATFORM_RCR_PT record doesn't exist then the PHY_DATA partition might
+ * not have been initialised.  This must be set to the proper default init data
+ * otherwise the SDK will halt on the "rf_cal[0] !=0x05,is 0xFF" error.
+ */
+static void phy_data_setup (partition_item_t *pt, uint32_t n) {
+    uint8_t  header[sizeof(uint32_t)] = {0};
+    int i;
+
+    for (i = 0; i < n; i++) {
+        if (pt[i].type == SYSTEM_PARTITION_PHY_DATA) {
+            uint32_t addr = pt[i].addr;
+            platform_s_flash_read(header, addr, sizeof(header));
+            if (header[0] != 0x05) {
+                uint32_t sector = pt[i].addr/INTERNAL_FLASH_SECTOR_SIZE;
+                if (platform_flash_erase_sector(sector) == PLATFORM_OK) {
+                    os_printf("Writing Init Data to 0x%08x\n",addr);
+                    platform_s_flash_write(init_data, addr, INIT_DATA_SIZE);
+                }
+            }
+            // flag setup complete so we don't retry this every boot
+            platform_rcr_write(PLATFORM_RCR_PHY_DATA, &addr, 0);
+            return;
         }
-        os_printf("%2u: %08x %08x %08x\n", j, p->type, p->addr, p->size);
-        j++;
     }
-
-    *n = j;
-    return fs_size_code;
+    // If the PHY_DATA doesn't exist or the write fails then the
+    // SDK will raise the rf_cal error anyway, so just return.
 }
 
-void user_pre_init(void) {
-#ifdef LUA_USE_MODULES_RTCTIME
-  // Note: Keep this as close to call_user_start() as possible, since it
-  // is where the cpu clock actually gets bumped to 80MHz.
-    rtctime_early_startup ();
-#endif
-    static partition_item_t pt[PTABLE_SIZE];
+/*
+ * First time setup does the one-off PT calculations and checks.  If these are OK,
+ * then writes back a new RCR for the updated PT and triggers a reboot. It returns
+ * on failure.
+ */
+static uint32_t first_time_setup(partition_item_t *pt, uint32_t n, uint32_t flash_size) {
+    int i, j, last = 0, newn = n;
+    /*
+    * Scan down the PT adjusting and 0 entries to sensible defaults.  Also delete any
+    * zero-sized partitions (as the SDK barfs on these).
+    */
+    for (i = 0, j = 0; i < n; i ++) {
+        partition_item_t *p = pt + i;
+        switch (p->type) {
+
+          case NODEMCU_PARTITION_IROM0TEXT:
+            // If the IROM0 partition size is 0 then compute from the IROM0_SIZE. Note
+            // that the size in the end-marker is used by the nodemcu-partition.py
+            // script and not here.
+            if (p->size == 0) {
+                p->size = PT_ALIGN(IROM0_SIZE);
+            }
+            break;
+
+          case NODEMCU_PARTITION_LFS:
+            // Properly align the LFS partition size and make it consecutive to
+            // the previous partition.
+            p->size = PT_ALIGN(p->size);
+            if (p->addr == 0)
+                p->addr = last;
+            break;
+
+          case NODEMCU_PARTITION_SPIFFS:
+            if (p->size == ~0x0 && p->addr == 0) {
+                // This allocate all the remaining flash to SPIFFS
+                p->addr = last;
+                p->size = flash_size - last;
+            } else if (p->size == ~0x0) {
+                p->size = flash_size - p->addr;
+           }  else if (p->addr == 0) {
+                // if the is addr not specified then start SPIFFS at 1Mb
+                // boundary if the size will fit otherwise make it consecutive
+                // to the previous partition.
+                p->addr = (p->size <= flash_size - 0x100000) ? 0x100000 : last;
+            }
+        }
 
-    uint32_t pt_size;
-    uint32_t fs_size_code = setup_partition_table(pt, &pt_size);
-    if( fs_size_code > 0 && system_partition_table_regist(pt, pt_size, fs_size_code)) {
-        return;
+        if (p->size == 0) {
+            // Delete 0-sized partitions as the SDK barfs on these
+            newn--;
+        } else {
+            // Do consistency tests on the partition
+            if (p->addr & (INTERNAL_FLASH_SECTOR_SIZE - 1) ||
+                p->size & (INTERNAL_FLASH_SECTOR_SIZE - 1) ||
+                p->addr < last ||
+                p->addr + p->size > flash_size) {
+                os_printf("Partition %u invalid alignment\n", i);
+                while(1) {/*system_soft_wdt_feed ();*/}
+            }
+            if (j < i)   // shift the partition down if we have any deleted slots
+                pt[j] = *p;
+//os_printf("Partition %d: %04x %06x %06x\n", j, p->type, p->addr, p->size);
+            j++;
+            last = p->addr + p->size;
+        }
     }
-    os_printf("system_partition_table_regist fail (%u)\n", fs_size_code);
-    while(1);
 
+    platform_rcr_write(PLATFORM_RCR_PT, pt, newn*sizeof(partition_item_t));
+    while(1); // Trigger WDT; the new PT will be loaded on reboot
 }
 
 uint32 ICACHE_RAM_ATTR user_iram_memory_is_enabled(void) {
@@ -277,18 +355,18 @@ void user_init(void)
 #endif
     system_init_done_cb(nodemcu_init);
 }
-
+#if 0
 /*
  * The SDK now establishes exception handlers for EXCCAUSE errors: ILLEGAL,
  * INSTR_ERROR, LOAD_STORE_ERROR, PRIVILEGED, UNALIGNED, LOAD_PROHIBITED,
  * STORE_PROHIBITED.  These handlers are established in SDK/app_main.c.
  * LOAD_STORE_ERROR is handled by SDK/user_exceptions.o:load_non_32_wide_handler()
  * which is a fork of our version. The remaining are handled by a static function
- * at SDK:app+main.c:offset 0x0348.
- *
+ * at SDK:app+main.c:offset 0x0348.  This wrappoer is only needed for debugging.
+ */
 void __real__xtos_set_exception_handler (uint32_t cause, exception_handler_fn fn);
 void __wrap__xtos_set_exception_handler (uint32_t cause, exception_handler_fn fn) {
     os_printf("Exception handler %x  %x\n", cause, fn);
     __real__xtos_set_exception_handler (cause, fn);
 }
- */
+#endif

+ 43 - 0
docs/modules/node.md

@@ -239,6 +239,24 @@ do
 end
 ```
 
+## node.getpartitiontable()
+
+Get the current LFS and SPIFFS partition information.
+
+#### Syntax
+`node.getpartitiontable()`
+
+#### Parameters
+none
+
+#### Returns
+An array containing entries for `lfs_addr`, `lfs_size`, `spiffs_addr` and `spiffs_size`. The address values are offsets relative to the startof the Flash memory.
+
+#### Example
+```lua
+print("The LFS size is " .. node.getpartitiontable().lfs_size)
+```
+
 ## node.heap()
 
 Returns the current available heap size in bytes. Note that due to fragmentation, actual allocations of this size may not be possible.
@@ -407,6 +425,31 @@ target CPU frequency (number)
 node.setcpufreq(node.CPU80MHZ)
 ```
 
+## node.setpartitiontable()
+
+Sets the current LFS and / or SPIFFS partition information.
+
+#### Syntax
+`node.setpartitiontable(partition_info)`
+
+!!! note
+	This function is typically only used once during initial provisioning after first flashing the firmware.  It does some consistency checks to validate the specified parameters, and it then reboots the ESP module to load the new partition table. If the LFS or SPIFFS regions have changed then you will need to reload LFS, reformat the SPIFSS and reload its contents.
+
+#### Parameters
+An array containing one or more of the following enties. The address values are byte offsets relative to the startof the Flash memory. The size values are in bytes. Note that these parameters must be a multiple of 8Kb to align to Flash page boundaries.
+-  `lfs_addr`.  The base address of the LFS region.
+-  `lfs_size`.  The size of the LFS region.
+-  `spiffs_addr`. The base address of the SPIFFS region.
+-  `spiffs_size`. The size of the SPIFFS region.
+
+#### Returns
+Not applicable.  The ESP module will be rebooted for a valid new set, or a Lua error will be thown if inconsistencies are detected.
+
+#### Example
+```lua
+node.setpartitiontable{lfs_size = 0x20000, spiffs_addr = 0x120000, spiffs_size = 0x20000}
+```
+
 
 ## node.sleep()
 

+ 1 - 1
ld/nodemcu.ld

@@ -233,7 +233,7 @@ SECTIONS
   .irom0.text : ALIGN(0x1000)
   {
     _irom0_text_start = ABSOLUTE(.);
-    *(.irom0.ptable)
+    KEEP(*(.irom0.ptable))
     . = ALIGN(0x1000);
     *(.servercert.flash)
     *(.clientcert.flash)

+ 177 - 80
tools/nodemcu-partition.py

@@ -6,7 +6,7 @@
 # heavily from and including content from esptool.py with full acknowledgement
 # under GPL 2.0, with said content: Copyright (C) 2014-2016 Fredrik Ahlberg, Angus
 # Gratton, Espressif Systems  (Shanghai) PTE LTD, other contributors as noted.
-# https://github.com/espressif/esptool
+# 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
@@ -37,13 +37,14 @@ import copy
 import inspect
 import struct
 import string
+import math
 
 __version__     = '1.0'
 __program__     = 'nodemcu-partition.py'
 ROM0_Seg        =   0x010000
 FLASH_PAGESIZE  =   0x001000
 FLASH_BASE_ADDR = 0x40200000
-PARTITION_TYPES = {
+PARTITION_TYPE  = {
        4: 'RF_CAL',
        5: 'PHY_DATA',
        6: 'SYSTEM_PARAMETER',
@@ -55,6 +56,10 @@ PARTITION_TYPES = {
      106: 'SPIFFS0',
      107: 'SPIFFS1'}
 
+IROM0TEXT = 102
+LFS       = 103
+SPIFFS    = 106
+
 MAX_PT_SIZE = 20*3
 FLASH_SIG          = 0xfafaa150
 FLASH_SIG_MASK     = 0xfffffff0
@@ -62,7 +67,14 @@ FLASH_SIG_ABSOLUTE = 0x00000001
 WORDSIZE           = 4
 WORDBITS           = 32
 
-PACK_INT    = struct.Struct("<I")
+DEFAULT_FLASH_SIZE   = 4*1024*1024
+PLATFORM_RCR_DELETED =   0x0
+PLATFORM_RCR_PT      =   0x1
+PLATFORM_RCR_FREE    =  0xFF
+
+SPIFFS_USE_ALL      = 0xFFFFFFFF
+
+PACK_INT = struct.Struct("<I")
 
 class FatalError(RuntimeError):
     def __init__(self, message):
@@ -72,6 +84,37 @@ class FatalError(RuntimeError):
         message += " (result was %s)" % hexify(result)
         return FatalError(message)
 
+def alignPT(n):
+    return 2*FLASH_PAGESIZE*int(math.ceil(n/2/FLASH_PAGESIZE))
+
+def unpack_RCR(data):
+    RCRword,recs, i = [PACK_INT.unpack_from(data,i)[0] \
+                          for i in range(0, FLASH_PAGESIZE, WORDSIZE)], \
+                      [],0
+    while RCRword[i] % 256 != PLATFORM_RCR_FREE:
+        Rlen, Rtype = RCRword[i] % 256, (RCRword[i]/256) % 256
+        if Rtype != PLATFORM_RCR_DELETED:
+            rec = [Rtype,[RCRword[j] for j in range(i+1,i+1+Rlen)]]
+            if Rtype == PLATFORM_RCR_PT:
+                PTrec = rec[1]
+            else:
+                recs.append(rec)
+        i = i + Rlen + 1
+
+    if PTrec is not None:
+        return PTrec,recs
+
+    FatalError("No partition table found")
+
+def repack_RCR(recs):
+    data = []
+    for r in recs:
+        Rtype, Rdata = r
+        data.append(256*Rtype + len(Rdata))
+        data.extend(Rdata)
+
+    return ''.join([PACK_INT.pack(i) for i in data])
+
 def load_PT(data, args):
     """
     Load the Flash copy of the Partition Table from the first segment of the IROM0
@@ -80,60 +123,107 @@ def load_PT(data, args):
 
     The (possibly) updated PT is then returned with the LFS sizing.
     """
-    pt = [PACK_INT.unpack_from(data,4*i)[0] for i in range(0, MAX_PT_SIZE)]
-    n, flash_used_end, rewrite = 0, 0, False
-    LFSaddr, LFSsize = None, None
-
-    # The partition table format is a set of 3*uint32 fields (type, addr, size), 
-    # with the last slot being an end marker (0,size,0) where size is the size of 
-    # the firmware image.
-
-    pt_map = dict()
-    for i in range(0,MAX_PT_SIZE,3):
-        if pt[i] == 0:
-            n = i // 3
-            break
-        elif pt[i] in PARTITION_TYPES:
-            pt_map[PARTITION_TYPES[pt[i]]] = i       
-        else:
-            raise FatalError("Unknown partition type: %u" % pt[i]) 
-
-    flash_used_end = pt[3*n+1]
- 
-    if not ('IROM0TEXT' in pt_map and 'LFS0' in pt_map):
-        raise FatalError("Partition table must contain IROM0 and LFS segments")
-
-    i = pt_map['IROM0TEXT']
-    if pt[i+2] == 0:
-        pt[i+2] = (flash_used_end - FLASH_BASE_ADDR) - pt[i+1]
-
-    j = pt_map['LFS0']
-    if args.la is not None: 
-        pt[j+1] = args.la
-    elif pt[j+1] == 0:
-        pt[j+1] = pt[i+1] + pt[i+2]
 
-    if args.ls is not None:
-        pt[j+2] = args.ls
-    elif pt[j+2] == 0:
-        pt[j+2] = 0x10000
-
-    k = pt_map['SPIFFS0']
-    if args.sa is not None: 
-        pt[k+1] = args.sa
-    elif pt[k+1] == 0:
-        pt[k+1] = pt[j+1] + pt[j+2]
-
-    if args.ss is not None:
-        pt[k+2] = args.ss
-
-    LFSaddr, LFSsize = pt[j+1], pt[j+2]
-    print ('\nDump of Partition Table\n')
-
-    for i in range(0,3*n,3):
-        print ('%-18s  0x%06x  0x%06x' % (PARTITION_TYPES[pt[i]], pt[i+1], pt[i+2]))
-
-    return pt, pt_map, n
+    PTrec,recs = unpack_RCR(data)
+    flash_size = fs.args if args.fs is not None else DEFAULT_FLASH_SIZE
+
+    # The partition table format is a set of 3*uint32 fields (type, addr, size),
+    # with the optional last slot being an end marker (0,size,0) where size is
+    # of the firmware image.
+
+    if PTrec[-3] == 0:             # Pick out the ROM size and remove the marker
+        defaultIROM0size = PTrec[-2] - FLASH_BASE_ADDR
+        del PTrec[-3:]
+    else:
+        defaultIROM0size = None
+
+    # The SDK objects to zero-length partitions so if the developer sets the
+    # size of the LFS and/or the SPIFFS partition to 0 then this is removed.
+    # If it is subsequently set back to non-zero then it needs to be reinserted.
+    # In reality the sizing algos assume that the LFS follows the IROM0TEXT one
+    # and SPIFFS is the last partition.  We will need to revisit these algos if
+    # we adopt a more flexible partiton allocation policy.  *** BOTCH WARNING ***
+
+    for i in range (0, len(PTrec), 3):
+        if PTrec[i] == IROM0TEXT and args.ls is not None and \
+            (len(PTrec) == i+3  or PTrec[i+3] != LFS):
+            PTrec[i+3:i+3] = [LFS, 0, 0]
+            break
+    if PTrec[-3] != SPIFFS:
+        PTrec.extend([SPIFFS, 0, 0])
+
+    lastEnd, newPT, map = 0,[], dict()
+    print "  Partition          Start   Size \n  ------------------ ------ ------"
+    for i in range (0, len(PTrec), 3):
+        Ptype, Paddr, Psize = PTrec[i:i+3]
+
+        if Ptype == IROM0TEXT:
+            # If the IROM0 partition size is 0 then compute from the IROM0_SIZE.
+            # Note that this script uses the size in the end-marker as a default
+            if Psize == 0:
+                if defaultIROM0size is None:
+                    raise FatalError("Cannot set the IROM0 partition size")
+                Psize = alignPT(defaultIROM0size)
+
+        elif Ptype == LFS:
+            #  Properly align the LFS partition size and make it consecutive to
+            #  the previous partition.
+            if args.la is not None:
+                Paddr = args.la
+            if args.ls is not None:
+                Psize = args.ls
+            Psize = alignPT(Psize)
+            if Paddr == 0:
+                Paddr = lastEnd
+            if Psize > 0:
+                map['LFS'] = {"addr" : Paddr, "size" : Psize}
+
+        elif Ptype == SPIFFS:
+            # The logic here is convolved.  Explicit start and length can be
+            # set, but the SPIFFS region is aslo contrained by the end of the
+            # previos partition and the end of Flash.  The size = -1 value
+            # means use up remaining flash and the SPIFFS will be moved to the
+            # 1Mb boundary if the address is default and the specified size
+            # allows this.
+            if args.sa is not None:
+                Paddr = args.sa
+            if args.ss is not None:
+                Psize = args.ss if args.ss >= 0 else SPIFFS_USE_ALL
+            if Psize == SPIFFS_USE_ALL:
+                #  This allocate all the remaining flash to SPIFFS
+                if Paddr < lastEnd:
+                    Paddr = lastEnd
+                Psize = flash_size - Paddr
+            else:
+                if Paddr == 0:
+                    #  if the is addr not specified then start SPIFFS at 1Mb
+                    #  boundary if the size will fit otherwise make it consecutive
+                    #  to the previous partition.
+                    Paddr = 0x100000  if Psize <= flash_size - 0x100000 else lastEnd
+                elif Paddr < lastEnd:
+                    Paddr = lastEnd
+                if Psize > flash_size - Paddr:
+                    Psize = flash_size - Paddr
+            if Psize > 0:
+                map['SPIFFS'] = {"addr" : Paddr, "size" : Psize}
+
+        if Psize > 0:
+            Pname = PARTITION_TYPE[Ptype] if Ptype in PARTITION_TYPE \
+                                          else ("Type %d" % Ptype)
+            print("  %-18s %06x %06x"% (Pname, Paddr, Psize))
+           #  Do consistency tests on the partition
+            if (Paddr & (FLASH_PAGESIZE - 1)) > 0 or \
+               (Psize & (FLASH_PAGESIZE - 1)) > 0 or \
+                Paddr < lastEnd or \
+                Paddr + Psize > flash_size:
+                print (lastEnd, flash_size)
+                raise FatalError("Partition %u invalid alignment\n" % (i/3))
+
+            newPT.extend([Ptype, Paddr, Psize])
+            lastEnd = Paddr + Psize
+
+    recs.append([PLATFORM_RCR_PT,newPT])
+    return recs, map
 
 def relocate_lfs(data, addr, size):
     """
@@ -144,8 +234,7 @@ def relocate_lfs(data, addr, size):
     aligned and so first needs scaling by the wordsize.)
     """
     addr += FLASH_BASE_ADDR
-    w = [PACK_INT.unpack_from(data,WORDSIZE*i)[0] \
-            for i in range(0, len(data) // WORDSIZE)]
+    w = [PACK_INT.unpack_from(data,i)[0] for i in range(0, len(data),WORDSIZE)]
     flash_sig, flash_size = w[0], w[1]
 
     assert ((flash_sig & FLASH_SIG_MASK) == FLASH_SIG and
@@ -155,6 +244,7 @@ def relocate_lfs(data, addr, size):
     flash_size //= WORDSIZE
     flags_size = (flash_size + WORDBITS - 1) // WORDBITS
 
+    print WORDSIZE*flash_size, size, len(data), WORDSIZE*(flash_size + flags_size)
     assert (WORDSIZE*flash_size <= size and
             len(data) == WORDSIZE*(flash_size + flags_size))
 
@@ -175,10 +265,10 @@ def main():
 
     def arg_auto_int(x):
         ux = x.upper()
-        if "MB" in ux:
-            return int(ux[:ux.index("MB")]) * 1024 * 1024
-        elif "KB" in ux:
-            return int(ux[:ux.index("KB")]) * 1024
+        if "M" in ux:
+            return int(ux[:ux.index("M")]) * 1024 * 1024
+        elif "K" in ux:
+            return int(ux[:ux.index("K")]) * 1024
         else:
             return int(ux, 0)
 
@@ -187,22 +277,24 @@ def main():
     # ---------- process the arguments ---------- #
 
     a = argparse.ArgumentParser(
-        description='%s V%s - ESP8266 NodeMCU Loader Utility' % 
+        description='%s V%s - ESP8266 NodeMCU Loader Utility' %
                      (__program__, __version__),
-        prog='esplfs')
+        prog=__program__)
     a.add_argument('--port', '-p', help='Serial port device')
     a.add_argument('--baud', '-b',  type=arg_auto_int,
         help='Serial port baud rate used when flashing/reading')
-    a.add_argument('--lfs-addr', '-la', dest="la", type=arg_auto_int,
+    a.add_argument('--flash_size', '-fs', dest="fs", type=arg_auto_int,
+        help='Flash size used in SPIFFS allocation (Default 4MB)')
+    a.add_argument('--lfs_addr', '-la', dest="la", type=arg_auto_int,
         help='(Overwrite) start address of LFS partition')
-    a.add_argument('--lfs-size', '-ls', dest="ls", type=arg_auto_int,
+    a.add_argument('--lfs_size', '-ls', dest="ls", type=arg_auto_int,
         help='(Overwrite) length of LFS partition')
-    a.add_argument('--lfs-file', '-lf', dest="lf", help='LFS image file')
-    a.add_argument('--spiffs-addr', '-sa', dest="sa", type=arg_auto_int,
+    a.add_argument('--lfs_file', '-lf', dest="lf", help='LFS image file')
+    a.add_argument('--spiffs_addr', '-sa', dest="sa", type=arg_auto_int,
         help='(Overwrite) start address of SPIFFS partition')
-    a.add_argument('--spiffs-size', '-ss', dest="ss", type=arg_auto_int,
+    a.add_argument('--spiffs_size', '-ss', dest="ss", type=arg_auto_int,
         help='(Overwrite) length of SPIFFS partition')
-    a.add_argument('--spiffs-file', '-sf', dest="sf", help='SPIFFS image file')
+    a.add_argument('--spiffs_file', '-sf', dest="sf", help='SPIFFS image file')
 
     arg = a.parse_args()
 
@@ -228,11 +320,11 @@ def main():
     with open(pt_file,"rb") as f:
         data = f.read()
 
-    pt, pt_map, n = load_PT(data, arg)
-    n = n+1
+    # ---------- Update the PT if necessary ---------- #
 
-    odata = ''.join([PACK_INT.pack(pt[i]) for i in range(0,3*n)]) + \
-            "\xFF" * len(data[3*4*n:])
+    recs, pt_map = load_PT(data, arg)
+    odata = repack_RCR(recs)
+    odata = odata + "\xFF" * (FLASH_PAGESIZE - len(odata))
 
     # ---------- If the PT has changed then use esptool to rewrite it ---------- #
 
@@ -246,13 +338,16 @@ def main():
         esptool.main(espargs)
 
     if arg.lf is not None:
-        i     = pt_map['LFS0']
-        la,ls = pt[i+1], pt[i+2]
+        if 'LFS' not in pt_map:
+            raise FatalError("No LFS partition; cannot write LFS image")
+        la,ls = pt_map['LFS']['addr'], pt_map['LFS']['size']
 
         # ---------- Read and relocate the LFS image ---------- #
-    
+
         with gzip.open(arg.lf) as f:
             lfs = f.read()
+            if len(lfs) > ls:
+                raise FatalError("LFS partition to small for LFS image")
             lfs = relocate_lfs(lfs, la, ls)
 
         # ---------- Write to a temp file and use esptool to write it to flash ---------- #
@@ -264,7 +359,9 @@ def main():
         esptool.main(espargs)
 
     if arg.sf is not None:
-        sa = pt[pt_map['SPIFFS0']+1]
+        if 'SPIFFS' not in pt_map:
+            raise FatalError("No SPIFSS partition; cannot write SPIFFS image")
+        sa,ss = pt_map['SPIFFS']['addr'], pt_map['SPIFFS']['size']
 
         # ---------- Write to a temp file and use esptool to write it to flash ---------- #
 
@@ -274,8 +371,8 @@ def main():
 
     # ---------- Clean up temp directory ---------- #
 
-    espargs = base + ['--after', 'hard_reset', 'flash_id']
-    esptool.main(espargs)
+#    espargs = base + ['--after', 'hard_reset', 'flash_id']
+#    esptool.main(espargs)
 
     shutil.rmtree(tmpdir)