summaryrefslogtreecommitdiffstatshomepage
diff options
context:
space:
mode:
-rw-r--r--cc3200/bootmgr/bootloader.mk1
-rw-r--r--cc3200/bootmgr/flc.h15
-rw-r--r--cc3200/bootmgr/main.c152
-rw-r--r--cc3200/ftp/ftp.c2
-rw-r--r--cc3200/ftp/updater.c68
-rw-r--r--cc3200/ftp/updater.h11
-rw-r--r--cc3200/mptask.c4
-rw-r--r--docs/wipy/general.rst32
8 files changed, 200 insertions, 85 deletions
diff --git a/cc3200/bootmgr/bootloader.mk b/cc3200/bootmgr/bootloader.mk
index f8ce0095cc..3105d6666c 100644
--- a/cc3200/bootmgr/bootloader.mk
+++ b/cc3200/bootmgr/bootloader.mk
@@ -19,6 +19,7 @@ BOOT_CPPDEFINES = -Dgcc -DBOOTLOADER -DTARGET_IS_CC3200 -DSL_TINY
BOOT_HAL_SRC_C = $(addprefix hal/,\
cpu.c \
interrupt.c \
+ gpio.c \
pin.c \
prcm.c \
shamd5.c \
diff --git a/cc3200/bootmgr/flc.h b/cc3200/bootmgr/flc.h
index 7415d1ef61..4b2aca9ac5 100644
--- a/cc3200/bootmgr/flc.h
+++ b/cc3200/bootmgr/flc.h
@@ -43,7 +43,9 @@ extern "C"
*******************************************************************************/
#define IMG_BOOT_INFO "/sys/bootinfo.bin"
#define IMG_FACTORY "/sys/factimg.bin"
-#define IMG_UPDATE "/sys/updtimg.bin"
+#define IMG_UPDATE1 "/sys/updtimg1.bin"
+#define IMG_UPDATE2 "/sys/updtimg2.bin"
+#define IMG_PREFIX "/sys/updtimg"
#define IMG_SRVPACK "/sys/servicepack.ucf"
#define SRVPACK_SIGN "/sys/servicepack.sig"
@@ -55,7 +57,7 @@ extern "C"
/******************************************************************************
Special file sizes
*******************************************************************************/
-#define IMG_SIZE (232 * 1024) /* 16KB are reserved for the bootloader and at least 8KB for the heap*/
+#define IMG_SIZE (192 * 1024) /* 16KB are reserved for the bootloader and at least 48KB for the heap*/
#define SRVPACK_SIZE (16 * 1024)
#define SIGN_SIZE (2 * 1024)
#define CA_KEY_SIZE (4 * 1024)
@@ -64,7 +66,8 @@ extern "C"
Active Image
*******************************************************************************/
#define IMG_ACT_FACTORY 0
-#define IMG_ACT_UPDATE 1
+#define IMG_ACT_UPDATE1 1
+#define IMG_ACT_UPDATE2 2
#define IMG_STATUS_CHECK 0
#define IMG_STATUS_READY 1
@@ -72,13 +75,13 @@ extern "C"
/******************************************************************************
Boot Info structure
*******************************************************************************/
-typedef struct sBootInfo
+typedef struct _sBootInfo_t
{
_u8 ActiveImg;
_u8 Status;
+ _u8 PrevImg;
_u8 : 8;
- _u8 : 8;
-}sBootInfo_t;
+} sBootInfo_t;
/******************************************************************************
diff --git a/cc3200/bootmgr/main.c b/cc3200/bootmgr/main.c
index 166161dde7..95df47c297 100644
--- a/cc3200/bootmgr/main.c
+++ b/cc3200/bootmgr/main.c
@@ -64,11 +64,14 @@
#define BOOTMGR_HASH_SIZE 32
#define BOOTMGR_BUFF_SIZE 512
-#define BOOTMGR_WAIT_SAFE_MODE_MS 2400
-#define BOOTMGR_WAIT_SAFE_MODE_TOOGLE_MS 400
+#define BOOTMGR_WAIT_SAFE_MODE_0_MS 3000
+#define BOOTMGR_WAIT_SAFE_MODE_0_BLINK_MS 500
-#define BOOTMGR_SAFE_MODE_ENTER_MS 1600
-#define BOOTMGR_SAFE_MODE_ENTER_TOOGLE_MS 160
+#define BOOTMGR_WAIT_SAFE_MODE_1_MS 3000
+#define BOOTMGR_WAIT_SAFE_MODE_1_BLINK_MS 250
+
+#define BOOTMGR_WAIT_SAFE_MODE_2_MS 1500
+#define BOOTMGR_WAIT_SAFE_MODE_2_BLINK_MS 100
//*****************************************************************************
// Exported functions declarations
@@ -79,9 +82,10 @@ extern void bootmgr_run_app (_u32 base);
// Local functions declarations
//*****************************************************************************
static void bootmgr_board_init (void);
-static bool bootmgr_verify (void);
+static bool bootmgr_verify (_u8 *image);
static void bootmgr_load_and_execute (_u8 *image);
-static bool safe_mode_boot (void);
+static bool wait_while_blinking (uint32_t wait_time, uint32_t period, bool force_wait);
+static void wait_for_safe_boot (sBootInfo_t *psBootInfo);
static void bootmgr_image_loader (sBootInfo_t *psBootInfo);
//*****************************************************************************
@@ -140,14 +144,14 @@ void SimpleLinkSockEventHandler(SlSockEvent_t *pSock)
//! Board Initialization & Configuration
//*****************************************************************************
static void bootmgr_board_init(void) {
- // Set vector table base
+ // set the vector table base
MAP_IntVTableBaseSet((unsigned long)&g_pfnVectors[0]);
- // Enable Processor Interrupts
+ // enable processor interrupts
MAP_IntMasterEnable();
MAP_IntEnable(FAULT_SYSTICK);
- // Mandatory MCU Initialization
+ // mandatory MCU initialization
PRCMCC3200MCUInit();
mperror_bootloader_check_reset_cause();
@@ -157,10 +161,10 @@ static void bootmgr_board_init(void) {
antenna_init0();
#endif
- // Enable the Data Hashing Engine
+ // enable the data hashing engine
CRYPTOHASH_Init();
- // Init the system led and the system switch
+ // init the system led and the system switch
mperror_init0();
// clear the safe boot flag, since we can't trust its content after reset
@@ -170,15 +174,15 @@ static void bootmgr_board_init(void) {
//*****************************************************************************
//! Verifies the integrity of the new application binary
//*****************************************************************************
-static bool bootmgr_verify (void) {
+static bool bootmgr_verify (_u8 *image) {
SlFsFileInfo_t FsFileInfo;
_u32 reqlen, offset = 0;
_i32 fHandle;
// open the file for reading
- if (0 == sl_FsOpen((_u8 *)IMG_UPDATE, FS_MODE_OPEN_READ, NULL, &fHandle)) {
+ if (0 == sl_FsOpen(image, FS_MODE_OPEN_READ, NULL, &fHandle)) {
// get the file size
- sl_FsGetInfo((_u8 *)IMG_UPDATE, 0, &FsFileInfo);
+ sl_FsGetInfo(image, 0, &FsFileInfo);
if (FsFileInfo.FileLen > BOOTMGR_HASH_SIZE) {
FsFileInfo.FileLen -= BOOTMGR_HASH_SIZE;
@@ -242,47 +246,72 @@ static void bootmgr_load_and_execute (_u8 *image) {
}
//*****************************************************************************
-//! Check for the safe mode pin
+//! Wait while the safe mode pin is being held high and blink the system led
+//! with the specified period
//*****************************************************************************
-static bool safe_mode_boot (void) {
- _u32 count = 0;
- while (MAP_GPIOPinRead(MICROPY_SAFE_BOOT_PORT, MICROPY_SAFE_BOOT_PORT_PIN) &&
- ((BOOTMGR_WAIT_SAFE_MODE_TOOGLE_MS * count++) < BOOTMGR_WAIT_SAFE_MODE_MS)) {
+static bool wait_while_blinking (uint32_t wait_time, uint32_t period, bool force_wait) {
+ _u32 count;
+ for (count = 0; (force_wait || MAP_GPIOPinRead(MICROPY_SAFE_BOOT_PORT, MICROPY_SAFE_BOOT_PORT_PIN)) &&
+ ((period * count) < wait_time); count++) {
// toogle the led
MAP_GPIOPinWrite(MICROPY_SYS_LED_PORT, MICROPY_SYS_LED_PORT_PIN, ~MAP_GPIOPinRead(MICROPY_SYS_LED_PORT, MICROPY_SYS_LED_PORT_PIN));
- UtilsDelay(UTILS_DELAY_US_TO_COUNT(BOOTMGR_WAIT_SAFE_MODE_TOOGLE_MS * 1000));
+ UtilsDelay(UTILS_DELAY_US_TO_COUNT(period * 1000));
}
- mperror_deinit_sfe_pin();
return MAP_GPIOPinRead(MICROPY_SAFE_BOOT_PORT, MICROPY_SAFE_BOOT_PORT_PIN) ? true : false;
}
//*****************************************************************************
-//! Load the proper image based on information from boot info and executes it.
+//! Check for the safe mode pin
+//*****************************************************************************
+static void wait_for_safe_boot (sBootInfo_t *psBootInfo) {
+ if (wait_while_blinking(BOOTMGR_WAIT_SAFE_MODE_0_MS, BOOTMGR_WAIT_SAFE_MODE_0_BLINK_MS, false)) {
+ // go back one step in time
+ psBootInfo->ActiveImg = psBootInfo->PrevImg;
+ if (wait_while_blinking(BOOTMGR_WAIT_SAFE_MODE_1_MS, BOOTMGR_WAIT_SAFE_MODE_1_BLINK_MS, false)) {
+ // go back directly to the factory image
+ psBootInfo->ActiveImg = IMG_ACT_FACTORY;
+ wait_while_blinking(BOOTMGR_WAIT_SAFE_MODE_2_MS, BOOTMGR_WAIT_SAFE_MODE_2_BLINK_MS, true);
+ }
+ // turn off the system led
+ MAP_GPIOPinWrite(MICROPY_SYS_LED_PORT, MICROPY_SYS_LED_PORT_PIN, 0);
+ // request a safe boot to the application
+ PRCMRequestSafeBoot();
+ }
+ // uninit the safe boot pin
+ mperror_deinit_sfe_pin();
+}
+
+//*****************************************************************************
+//! Load the proper image based on the information from the boot info
+//! and launch it.
//*****************************************************************************
static void bootmgr_image_loader(sBootInfo_t *psBootInfo) {
_i32 fhandle;
- if (safe_mode_boot()) {
- _u32 count = 0;
- while ((BOOTMGR_SAFE_MODE_ENTER_TOOGLE_MS * count++) < BOOTMGR_SAFE_MODE_ENTER_MS) {
- // toogle the led
- MAP_GPIOPinWrite(MICROPY_SYS_LED_PORT, MICROPY_SYS_LED_PORT_PIN, ~MAP_GPIOPinRead(MICROPY_SYS_LED_PORT, MICROPY_SYS_LED_PORT_PIN));
- UtilsDelay(UTILS_DELAY_US_TO_COUNT(BOOTMGR_SAFE_MODE_ENTER_TOOGLE_MS * 1000));
- }
- psBootInfo->ActiveImg = IMG_ACT_FACTORY;
- // turn the led off
- MAP_GPIOPinWrite(MICROPY_SYS_LED_PORT, MICROPY_SYS_LED_PORT_PIN, 0);
- // request a safe boot to the application
- PRCMRequestSafeBoot();
+ _u8 *image;
+
+ // search for the active image
+ switch (psBootInfo->ActiveImg) {
+ case IMG_ACT_UPDATE1:
+ image = (unsigned char *)IMG_UPDATE1;
+ break;
+ case IMG_ACT_UPDATE2:
+ image = (unsigned char *)IMG_UPDATE2;
+ break;
+ default:
+ image = (unsigned char *)IMG_FACTORY;
+ break;
}
- // do we have a new update image that needs to be verified?
- else if ((psBootInfo->ActiveImg == IMG_ACT_UPDATE) && (psBootInfo->Status == IMG_STATUS_CHECK)) {
- if (!bootmgr_verify()) {
- // delete the corrupted file
- sl_FsDel((_u8 *)IMG_UPDATE, 0);
- // switch to the factory image
- psBootInfo->ActiveImg = IMG_ACT_FACTORY;
+
+ // do we have a new image that needs to be verified?
+ if ((psBootInfo->ActiveImg != IMG_ACT_FACTORY) && (psBootInfo->Status == IMG_STATUS_CHECK)) {
+ if (!bootmgr_verify(image)) {
+ // verification failed, delete the broken file
+ sl_FsDel(image, 0);
+ // switch to the previous image
+ psBootInfo->ActiveImg = psBootInfo->PrevImg;
+ psBootInfo->PrevImg = IMG_ACT_FACTORY;
}
- // in any case, set the status as "READY"
+ // in any case, change the status to "READY"
psBootInfo->Status = IMG_STATUS_READY;
// write the new boot info
if (!sl_FsOpen((unsigned char *)IMG_BOOT_INFO, FS_MODE_OPEN_WRITE, NULL, &fhandle)) {
@@ -292,24 +321,34 @@ static void bootmgr_image_loader(sBootInfo_t *psBootInfo) {
}
}
- // now boot the active image
- if (IMG_ACT_UPDATE == psBootInfo->ActiveImg) {
- bootmgr_load_and_execute((unsigned char *)IMG_UPDATE);
- }
- else {
- bootmgr_load_and_execute((unsigned char *)IMG_FACTORY);
+ // this one might modify the boot info hence it MUST be called after
+ // bootmgr_verify! (so that the changes are not saved to flash)
+ wait_for_safe_boot(psBootInfo);
+
+ // select the active image again, since it might have changed
+ switch (psBootInfo->ActiveImg) {
+ case IMG_ACT_UPDATE1:
+ image = (unsigned char *)IMG_UPDATE1;
+ break;
+ case IMG_ACT_UPDATE2:
+ image = (unsigned char *)IMG_UPDATE2;
+ break;
+ default:
+ image = (unsigned char *)IMG_FACTORY;
+ break;
}
+ bootmgr_load_and_execute(image);
}
//*****************************************************************************
//! Main function
//*****************************************************************************
int main (void) {
- sBootInfo_t sBootInfo = { .ActiveImg = IMG_ACT_FACTORY, .Status = IMG_STATUS_READY };
+ sBootInfo_t sBootInfo = { .ActiveImg = IMG_ACT_FACTORY, .Status = IMG_STATUS_READY, .PrevImg = IMG_ACT_FACTORY };
bool bootapp = false;
_i32 fhandle;
- // Board Initialization
+ // board setup
bootmgr_board_init();
// start simplelink since we need it to access the sflash
@@ -322,12 +361,13 @@ int main (void) {
}
sl_FsClose(fhandle, 0, 0, 0);
}
+ // boot info file not present (or read failed)
if (!bootapp) {
// create a new boot info file
_u32 BootInfoCreateFlag = _FS_FILE_OPEN_FLAG_COMMIT | _FS_FILE_PUBLIC_WRITE | _FS_FILE_PUBLIC_READ;
if (!sl_FsOpen ((unsigned char *)IMG_BOOT_INFO, FS_MODE_OPEN_CREATE((2 * sizeof(sBootInfo_t)),
BootInfoCreateFlag), NULL, &fhandle)) {
- // Write the default boot info.
+ // write the default boot info.
if (sizeof(sBootInfo_t) == sl_FsWrite(fhandle, 0, (unsigned char *)&sBootInfo, sizeof(sBootInfo_t))) {
bootapp = true;
}
@@ -343,14 +383,14 @@ int main (void) {
// stop simplelink
sl_Stop(SL_STOP_TIMEOUT);
- // if we've reached this point, then it means a fatal error occurred and the application
- // could not be loaded, so, loop forever and signal the crash to the user
+ // if we've reached this point, then it means that a fatal error has occurred and the
+ // application could not be loaded, so, loop forever and signal the crash to the user
while (true) {
// keep the bld on
MAP_GPIOPinWrite(MICROPY_SYS_LED_PORT, MICROPY_SYS_LED_PORT_PIN, MICROPY_SYS_LED_PORT_PIN);
- __asm volatile(" dsb \n"
- " isb \n"
- " wfi \n");
+ __asm volatile(" dsb \n"
+ " isb \n"
+ " wfi \n");
}
}
diff --git a/cc3200/ftp/ftp.c b/cc3200/ftp/ftp.c
index 2aba00d539..651cd1ef90 100644
--- a/cc3200/ftp/ftp.c
+++ b/cc3200/ftp/ftp.c
@@ -764,6 +764,8 @@ static void ftp_process_cmd (void) {
ftp_send_reply(150, NULL);
}
else {
+ // to unlock the updater
+ updater_finnish();
ftp_data.state = E_FTP_STE_END_TRANSFER;
ftp_send_reply(550, NULL);
}
diff --git a/cc3200/ftp/updater.c b/cc3200/ftp/updater.c
index 974b2f72d7..2f983b16c6 100644
--- a/cc3200/ftp/updater.c
+++ b/cc3200/ftp/updater.c
@@ -37,6 +37,7 @@
#include "modnetwork.h"
#include "modwlan.h"
#include "debug.h"
+#include "osi.h"
/******************************************************************************
DEFINE PRIVATE CONSTANTS
@@ -61,15 +62,37 @@ typedef struct {
/******************************************************************************
DECLARE PRIVATE DATA
******************************************************************************/
-static updater_data_t updater_data;
+static updater_data_t updater_data = { .path = NULL, .fhandle = -1, .fsize = 0, .foffset = 0 };
+static OsiLockObj_t updater_LockObj;
+static sBootInfo_t sBootInfo;
/******************************************************************************
DEFINE PUBLIC FUNCTIONS
******************************************************************************/
+__attribute__ ((section (".boot")))
+void updater_pre_init (void) {
+ // create the updater lock
+ ASSERT(OSI_OK == sl_LockObjCreate(&updater_LockObj, "UpdaterLock"));
+}
+
bool updater_check_path (void *path) {
+ sl_LockObjLock (&updater_LockObj, SL_OS_WAIT_FOREVER);
if (!strcmp(UPDATER_IMG_PATH, path)) {
- updater_data.path = IMG_UPDATE;
updater_data.fsize = IMG_SIZE;
+ updater_data.path = IMG_UPDATE1;
+// the launchxl doesn't have enough flash space for 2 user update images
+#ifdef WIPY
+ // check which one should be the next active image
+ _i32 fhandle;
+ if (!sl_FsOpen((unsigned char *)IMG_BOOT_INFO, FS_MODE_OPEN_READ, NULL, &fhandle)) {
+ ASSERT (sizeof(sBootInfo_t) == sl_FsRead(fhandle, 0, (unsigned char *)&sBootInfo, sizeof(sBootInfo_t)));
+ sl_FsClose(fhandle, 0, 0, 0);
+ if ((sBootInfo.Status == IMG_STATUS_CHECK && sBootInfo.ActiveImg == IMG_ACT_UPDATE2) ||
+ sBootInfo.ActiveImg == IMG_ACT_UPDATE1) {
+ updater_data.path = IMG_UPDATE2;
+ }
+ }
+#endif
} else if (!strcmp(UPDATER_SRVPACK_PATH, path)) {
updater_data.path = IMG_SRVPACK;
updater_data.fsize = SRVPACK_SIZE;
@@ -86,6 +109,7 @@ bool updater_check_path (void *path) {
updater_data.path = KEY_FILE;
updater_data.fsize = CA_KEY_SIZE;
} else {
+ sl_LockObjUnlock (&updater_LockObj);
return false;
}
return true;
@@ -106,7 +130,6 @@ bool updater_start (void) {
result = true;
}
sl_LockObjUnlock (&wlan_LockObj);
-
return result;
}
@@ -124,35 +147,56 @@ bool updater_write (uint8_t *buf, uint32_t len) {
}
void updater_finnish (void) {
- sBootInfo_t sBootInfo;
_i32 fhandle;
if (updater_data.fhandle > 0) {
sl_LockObjLock (&wlan_LockObj, SL_OS_WAIT_FOREVER);
// close the file being updated
sl_FsClose(updater_data.fhandle, NULL, NULL, 0);
-
- if (!strcmp (IMG_UPDATE, updater_data.path)) {
- // open the boot info file for reading
+#ifdef WIPY
+ // if we still have an image pending for verification, leave the boot info as it is
+ if (!strncmp(IMG_PREFIX, updater_data.path, strlen(IMG_PREFIX)) && sBootInfo.Status != IMG_STATUS_CHECK) {
+#else
+ if (!strncmp(IMG_PREFIX, updater_data.path, strlen(IMG_PREFIX))) {
+#endif
+#ifdef DEBUG
if (!sl_FsOpen((unsigned char *)IMG_BOOT_INFO, FS_MODE_OPEN_READ, NULL, &fhandle)) {
+
ASSERT (sizeof(sBootInfo_t) == sl_FsRead(fhandle, 0, (unsigned char *)&sBootInfo, sizeof(sBootInfo_t)));
sl_FsClose(fhandle, 0, 0, 0);
- // open the file for writing
+#endif
+ // open the boot info file for writing
ASSERT (sl_FsOpen((unsigned char *)IMG_BOOT_INFO, FS_MODE_OPEN_WRITE, NULL, &fhandle) == 0);
+#ifdef DEBUG
}
else {
+ // the boot info file doesn't exist yet
_u32 BootInfoCreateFlag = _FS_FILE_OPEN_FLAG_COMMIT | _FS_FILE_PUBLIC_WRITE | _FS_FILE_PUBLIC_READ;
ASSERT (sl_FsOpen ((unsigned char *)IMG_BOOT_INFO, FS_MODE_OPEN_CREATE((2 * sizeof(sBootInfo_t)),
BootInfoCreateFlag), NULL, &fhandle) == 0);
}
-
- // write the new boot info
- sBootInfo.ActiveImg = IMG_ACT_UPDATE;
+#endif
+
+ // save the new boot info
+#ifdef WIPY
+ sBootInfo.PrevImg = sBootInfo.ActiveImg;
+ if (sBootInfo.ActiveImg == IMG_ACT_UPDATE1) {
+ sBootInfo.ActiveImg = IMG_ACT_UPDATE2;
+ } else {
+ sBootInfo.ActiveImg = IMG_ACT_UPDATE1;
+ }
+// the launchxl doesn't have enough flash space for 2 user updates
+#else
+ sBootInfo.PrevImg = IMG_ACT_FACTORY;
+ sBootInfo.ActiveImg = IMG_ACT_UPDATE1;
+#endif
sBootInfo.Status = IMG_STATUS_CHECK;
ASSERT (sizeof(sBootInfo_t) == sl_FsWrite(fhandle, 0, (unsigned char *)&sBootInfo, sizeof(sBootInfo_t)));
sl_FsClose(fhandle, 0, 0, 0);
}
sl_LockObjUnlock (&wlan_LockObj);
+ updater_data.fhandle = -1;
}
- updater_data.fhandle = -1;
+ sl_LockObjUnlock (&updater_LockObj);
}
+
diff --git a/cc3200/ftp/updater.h b/cc3200/ftp/updater.h
index 518d76c4c7..b581d0fc8a 100644
--- a/cc3200/ftp/updater.h
+++ b/cc3200/ftp/updater.h
@@ -28,10 +28,11 @@
#ifndef UPDATER_H_
#define UPDATER_H_
-bool updater_check_path (void *path);
-bool updater_start (void);
-bool updater_write (uint8_t *buf, uint32_t len);
-void updater_finnish (void);
-bool updater_verify (uint8_t *rbuff, uint8_t *hasbuff);
+extern void updater_pre_init (void);
+extern bool updater_check_path (void *path);
+extern bool updater_start (void);
+extern bool updater_write (uint8_t *buf, uint32_t len);
+extern void updater_finnish (void);
+extern bool updater_verify (uint8_t *rbuff, uint8_t *hasbuff);
#endif /* UPDATER_H_ */
diff --git a/cc3200/mptask.c b/cc3200/mptask.c
index 152637e158..bae0e56493 100644
--- a/cc3200/mptask.c
+++ b/cc3200/mptask.c
@@ -67,6 +67,7 @@
#include "pybtimer.h"
#include "mpcallback.h"
#include "cryptohash.h"
+#include "updater.h"
/******************************************************************************
DECLARE PRIVATE CONSTANTS
@@ -279,6 +280,9 @@ STATIC void mptask_pre_init (void) {
// this one allocates memory for the WLAN semaphore
wlan_pre_init();
+ // this one allocates memory for the updater semaphore
+ updater_pre_init();
+
// this one allocates memory for the Socket semaphore
modusocket_pre_init();
diff --git a/docs/wipy/general.rst b/docs/wipy/general.rst
index 67f47f21e4..976fb6891f 100644
--- a/docs/wipy/general.rst
+++ b/docs/wipy/general.rst
@@ -32,15 +32,35 @@ Boot modes
----------
If you power up normally, or press the reset button, the WiPy will boot
-into standard mode: the ``boot.py`` file will be executed first, then
+into standard mode; the ``boot.py`` file will be executed first, then
``main.py`` will run.
You can override this boot sequence by pulling ``GPIO28`` **up** (connect
-it to the 3v3 output pin) during reset. The heart beat LED will flash slowly
-3 times to signal that safe boot is being requested, and then 4 more times
-quickly to let you know that safe boot is going to be performed. While safe
-booting, the WiPy runs the factory firmware and skips the execution of
-``boot.py`` and ``main.py``. This is useful to recover from any crash situation.
+it to the 3v3 output pin) during reset. This procedure also allows going
+back in time to old firmware versions. The WiPy can hold up to 3 different
+firmware versions, which are: the factory firmware plus 2 user updates.
+
+After reset, if ``GPIO28`` is held high, the heart beat LED will start flashing
+slowly, if after 3 seconds the pin is still being held high, the LED will start
+blinking a bit faster and the WiPy will select the previous user update to boot.
+If the previous user update is the desired firmware image, ``GPIO28`` must be
+released before 3 more seconds elapse. If 3 seconds later the pin is still high,
+the factory firmware will be selected, the LED will flash quickly for 1.5 seconds
+and the WiPy will proceed to boot. The firmware selection mechanism is as follows:
+
+
+**Safe Boot Pin** ``GPIO28`` **released during:**
+
++-------------------------+-------------------------+----------------------------+
+| 1st 3 secs window | 2nd 3 secs window | Final 1.5 secs window |
++=========================+=========================+============================+
+| | Normal boot, *latest* | | Safe boot, *previous* | | Safe boot, the *factory* |
+| | firmware is selected | | user update selected | | firmware is selected |
++-------------------------+-------------------------+----------------------------+
+
+When selecting a previous firmware version, safe boot mode is entered, meaning
+that the execution of both ``boot.py`` and ``main.py`` is skipped. This is
+useful to recover from crash situations caused by the user scripts.
The heart beat LED
------------------