Skip to content

Commit

Permalink
jsvEvaluate now uses memory area for execution of JS strings (fix #…
Browse files Browse the repository at this point in the history
…817)

            Add `E.setBootCode` to allow JS scripts to be run without being in RAM (fix #740)
  • Loading branch information
gfwilliams committed Mar 31, 2016
1 parent 3c76ff4 commit 92b8b58
Show file tree
Hide file tree
Showing 6 changed files with 183 additions and 75 deletions.
2 changes: 2 additions & 0 deletions ChangeLog
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@
Added better micro:bit `show()` that works well with Graphics
Add `require("Flash").getFree()` as multiplatform way to find free flash pages (fix #815)
Add the ability to set clock frequencies on STM32F4 chips (like Pico) with E.setClock (fix #52)
`jsvEvaluate` now uses memory area for execution of JS strings (fix #817)
Add `E.setBootCode` to allow JS scripts to be run without being in RAM (fix #740)

1v85 : Ensure HttpServerResponse.writeHead actually sends the header right away
- enables WebSocket Server support from JS
Expand Down
12 changes: 6 additions & 6 deletions src/jsinteractive.c
Original file line number Diff line number Diff line change
Expand Up @@ -430,7 +430,7 @@ static JsVarRef _jsiInitNamedArray(const char *name) {

// Used when recovering after being flashed
// 'claim' anything we are using
void jsiSoftInit() {
void jsiSoftInit(bool hasBeenReset) {
jsErrorFlags = 0;
events = jsvNewEmptyArray();
inputLine = jsvNewFromEmptyString();
Expand All @@ -456,7 +456,7 @@ void jsiSoftInit() {
jswInit();

// Run 'boot code' - textual JS in flash
jsfLoadBootCodeFromFlash();
jsfLoadBootCodeFromFlash(hasBeenReset);

// Now run initialisation code
JsVar *initCode = jsvObjectGetChild(execInfo.hiddenRoot, JSI_INIT_CODE_NAME, 0);
Expand Down Expand Up @@ -740,7 +740,7 @@ void jsiSemiInit(bool autoLoad) {
}

// Softinit may run initialisation code that will overwrite defaults
jsiSoftInit();
jsiSoftInit(!autoLoad);

#ifdef ESP8266
jshSoftInit();
Expand Down Expand Up @@ -1958,11 +1958,11 @@ void jsiIdle() {
jsiSoftKill();
jspSoftKill();
jsvSoftKill();
jsfSaveToFlash(true, 0);
jsfSaveToFlash(SFF_SAVE_STATE, 0);
jshReset();
jsvSoftInit();
jspSoftInit();
jsiSoftInit();
jsiSoftInit(false /* not been reset */);
}
if ((s&JSIS_TODO_FLASH_LOAD) == JSIS_TODO_FLASH_LOAD) {
jsiStatus &= (JsiStatus)~JSIS_TODO_FLASH_LOAD;
Expand All @@ -1974,7 +1974,7 @@ void jsiIdle() {
jsfLoadStateFromFlash();
jsvSoftInit();
jspSoftInit();
jsiSoftInit();
jsiSoftInit(false /* not been reset */);
}
jsiSetBusy(BUSY_INTERACTIVE, false);
}
Expand Down
23 changes: 18 additions & 5 deletions src/jswrap_espruino.c
Original file line number Diff line number Diff line change
Expand Up @@ -740,14 +740,27 @@ JsVar *jswrap_espruino_memoryArea(int addr, int len) {
"name" : "setBootCode",
"generate" : "jswrap_espruino_setBootCode",
"params" : [
["code","JsVar","The address of the memory area"]
["code","JsVar","The code to execute (as a string)"],
["alwaysExec","bool","Whether to always execute the code (even after a reset)"]
]
}
This writes JavaScript code to Espruino, which will *always be executed
after a reset*.
This writes JavaScript code into Espruino's flash memory, to be executed on
startup. It differs from `save()` in that `save()` saves the whole state of
the interpreter, whereas this just saves JS code that is executed at boot.
Code will be executed before `onInit()` and `E.on('init', ...)`.
If `alwaysExec` is `true`, the code will be executed even after a call to
`reset()`. This is useful if you're making something that you want to
program, but you want some code that is always built in (for instance
setting up a display or keyboard).
**Note:** this removes any code that was previously saved with `save()`
*/
void jswrap_espruino_setBootCode(JsVar *code) {
jsfSaveToFlash(false, code);
void jswrap_espruino_setBootCode(JsVar *code, bool alwaysExec) {
JsvSaveFlashFlags flags = 0;
if (alwaysExec) flags |= SFF_BOOT_CODE_ALWAYS;
jsfSaveToFlash(flags, code);
}


Expand Down
2 changes: 1 addition & 1 deletion src/jswrap_espruino.h
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ JsVar *jswrap_espruino_toArrayBuffer(JsVar *str);
JsVar *jswrap_espruino_toUint8Array(JsVar *args);
JsVar *jswrap_espruino_toString(JsVar *args);
JsVar *jswrap_espruino_memoryArea(int addr, int len);
void jswrap_espruino_setBootCode(JsVar *code);
void jswrap_espruino_setBootCode(JsVar *code, bool alwaysExec);
int jswrap_espruino_setClock(JsVar *options);

int jswrap_espruino_reverseByte(int v);
Expand Down
206 changes: 146 additions & 60 deletions src/jswrap_flash.c
Original file line number Diff line number Diff line change
Expand Up @@ -229,40 +229,84 @@ void jsfSaveToFlash_writecb(unsigned char ch, uint32_t *cbdata) {
// ------------------------------------------------------------------------
// ------------------------------------------------------------------------

void jsfSaveToFlash(bool saveState, JsVar *bootCode) {
/* On Linux systems:
*
* State data is saved to espruino.state
* Boot code (text JS) is saved to espruino.boot
*
* On embedded systems:
*
* Code is saved starting from FLASH_SAVED_CODE_START.
* A magic number written to FLASH_MAGIC_LOCATION (the end of saved code)
* determines whether flash data has been successfully written or not
* The first word at FLASH_SAVED_CODE_START is the amount of Boot code
* that is saved
* The second word at FLASH_SAVED_CODE_START+4 is the end address of
* decompressed JS code
* Boot code starts at FLASH_SAVED_CODE_START+8
* Saved state starts at FLASH_SAVED_CODE_START+8+boot_code_length
*
*/

#define BOOT_CODE_LENGTH_MASK 0x00FFFFFF
#define BOOT_CODE_RUN_ALWAYS 0x80000000

#define FLASH_BOOT_CODE_INFO_LOCATION FLASH_SAVED_CODE_START
#define FLASH_STATE_END_LOCATION (FLASH_SAVED_CODE_START+4)
#define FLASH_DATA_LOCATION (FLASH_SAVED_CODE_START+8)

void jsfSaveToFlash(JsvSaveFlashFlags flags, JsVar *bootCode) {
#ifdef LINUX
FILE *f = fopen("espruino.state","wb");
if (f) {
unsigned int jsVarCount = jsvGetMemoryTotal();
jsiConsolePrintf("\nSaving %d bytes...", jsVarCount*sizeof(JsVar));
fwrite(&jsVarCount, sizeof(unsigned int), 1, f);
/*JsVarRef i;
for (i=1;i<=jsVarCount;i++) {
fwrite(_jsvGetAddressOf(i),1,sizeof(JsVar),f);
}*/
COMPRESS((unsigned char*)_jsvGetAddressOf(1), jsVarCount*sizeof(JsVar), jsfSaveToFlash_writecb, (uint32_t*)f);
fclose(f);
jsiConsolePrint("\nDone!\n");
if (bootCode) {
FILE *f = fopen("espruino.boot","wb");
if (f) {
JsvStringIterator it;
jsvStringIteratorNew(&it, bootCode, 0);
while (jsvStringIteratorHasChar(&it)) {
char ch = jsvStringIteratorGetChar(&it);
fwrite(&ch, 1, 1, f);
jsvStringIteratorNext(&it);
}
fclose(f);
} else {
jsiConsolePrint("\nFile open of espruino.boot failed... \n>");
}
}

#ifdef DEBUG
jsiConsolePrint("Checking...\n");
FILE *f = fopen("espruino.state","rb");
fread(&jsVarCount, sizeof(unsigned int), 1, f);
if (jsVarCount != jsvGetMemoryTotal())
jsiConsolePrint("Error: memory sizes different\n");
unsigned char *decomp = (unsigned char*)malloc(jsVarCount*sizeof(JsVar));
DECOMPRESS(jsfLoadFromFlash_readcb, (uint32_t *)f, decomp);
fclose(f);
unsigned char *comp = (unsigned char *)_jsvGetAddressOf(1);
size_t j;
for (j=0;j<jsVarCount*sizeof(JsVar);j++)
if (decomp[j]!=comp[j])
jsiConsolePrintf("Error at %d: original %d, decompressed %d\n", j, comp[j], decomp[j]);
free(decomp);
jsiConsolePrint("Done!\n>");
#endif
} else {
jsiConsolePrint("\nFile open failed... \n>");
if (flags & SFF_SAVE_STATE) {
FILE *f = fopen("espruino.state","wb");
if (f) {
unsigned int jsVarCount = jsvGetMemoryTotal();
jsiConsolePrintf("\nSaving %d bytes...", jsVarCount*sizeof(JsVar));
fwrite(&jsVarCount, sizeof(unsigned int), 1, f);
/*JsVarRef i;
for (i=1;i<=jsVarCount;i++) {
fwrite(_jsvGetAddressOf(i),1,sizeof(JsVar),f);
}*/
COMPRESS((unsigned char*)_jsvGetAddressOf(1), jsVarCount*sizeof(JsVar), jsfSaveToFlash_writecb, (uint32_t*)f);
fclose(f);
jsiConsolePrint("\nDone!\n");

#ifdef DEBUG
jsiConsolePrint("Checking...\n");
FILE *f = fopen("espruino.state","rb");
fread(&jsVarCount, sizeof(unsigned int), 1, f);
if (jsVarCount != jsvGetMemoryTotal())
jsiConsolePrint("Error: memory sizes different\n");
unsigned char *decomp = (unsigned char*)malloc(jsVarCount*sizeof(JsVar));
DECOMPRESS(jsfLoadFromFlash_readcb, (uint32_t *)f, decomp);
fclose(f);
unsigned char *comp = (unsigned char *)_jsvGetAddressOf(1);
size_t j;
for (j=0;j<jsVarCount*sizeof(JsVar);j++)
if (decomp[j]!=comp[j])
jsiConsolePrintf("Error at %d: original %d, decompressed %d\n", j, comp[j], decomp[j]);
free(decomp);
jsiConsolePrint("Done!\n>");
#endif
} else {
jsiConsolePrint("\nFile open of espruino.state failed... \n>");
}
}
#else // !LINUX
unsigned int dataSize = jsvGetMemoryTotal() * sizeof(JsVar);
Expand All @@ -274,6 +318,29 @@ void jsfSaveToFlash(bool saveState, JsVar *bootCode) {
uint32_t endOfData;
uint32_t cbData[3];

/* If we didn't specify boot code this time, but boot code was set previously,
* load it into RAM so we can keep it. */
uint32_t originalBootCodeInfo = 0;
char *originalBootCode = 0;
uint32_t bootCodeLen = 0;
if (!jsvIsString(bootCode) && jsfFlashContainsCode()) {
jshFlashRead(&originalBootCodeInfo, FLASH_BOOT_CODE_INFO_LOCATION, 4);
bootCodeLen = originalBootCodeInfo & BOOT_CODE_LENGTH_MASK;
if (bootCodeLen) {
if (originalBootCodeInfo & BOOT_CODE_RUN_ALWAYS)
flags |= SFF_BOOT_CODE_ALWAYS;
else
flags &= ~SFF_BOOT_CODE_ALWAYS;
originalBootCode = (char *)alloca(bootCodeLen);
if (originalBootCode) {
jshFlashRead(originalBootCode, FLASH_DATA_LOCATION, bootCodeLen);
} else {
// There may not be room on the stack, in which case we'll warn
jsWarn("Unable to keep Boot Code - not enough room on the stack\n");
}
}
}

while (tryAgain) {
tryAgain = false;
jsiConsolePrint("Erasing Flash...");
Expand All @@ -290,25 +357,39 @@ void jsfSaveToFlash(bool saveState, JsVar *bootCode) {
}
// Now start writing
cbData[0] = FLASH_MAGIC_LOCATION; // end of available flash
cbData[1] = FLASH_SAVED_CODE_START+8;
cbData[1] = FLASH_DATA_LOCATION;
cbData[2] = 0; // word data (can only save a word ata a time)
jsiConsolePrint("\nWriting...");
// boot code....
if (jsvIsString(bootCode)) {
uint32_t bootCodeLen = (uint32_t)jsvGetStringLength(bootCode)+1;
jshFlashWrite(&bootCodeLen, FLASH_SAVED_CODE_START, 4); // write size of boot code to flash

JsvStringIterator it;
jsvStringIteratorNew(&it, bootCode, 0);
while (jsvStringIteratorHasChar(&it)) {
jsfSaveToFlash_writecb(jsvStringIteratorGetChar(&it), cbData);
jsvStringIteratorNext(&it);
bootCodeLen = (uint32_t)jsvGetStringLength(bootCode);
uint32_t bootCodeFlags = 0;
if (bootCodeLen) {
// Only write code if we actually have any
bootCodeFlags = bootCodeLen;
if (flags & SFF_BOOT_CODE_ALWAYS) bootCodeFlags |= BOOT_CODE_RUN_ALWAYS;
JsvStringIterator it;
jsvStringIteratorNew(&it, bootCode, 0);
while (jsvStringIteratorHasChar(&it)) {
jsfSaveToFlash_writecb(jsvStringIteratorGetChar(&it), cbData);
jsvStringIteratorNext(&it);
}
// terminate with a 0!
jsfSaveToFlash_writecb(0, cbData);
bootCodeLen++;
}
// terminate with a 0!
jsfSaveToFlash_writecb(0, cbData);
// write size of boot code to flash
jshFlashWrite(&bootCodeFlags, FLASH_BOOT_CODE_INFO_LOCATION, 4);
} else if (originalBootCode) {
// previously saved boot code that we want to keep
assert(originalBootCode && bootCodeLen);
jshFlashWrite(&originalBootCodeInfo, FLASH_BOOT_CODE_INFO_LOCATION, 4); // write size of boot code to flash
size_t i;
for (i=0;i<bootCodeLen;i++)
jsfSaveToFlash_writecb(originalBootCode[i], cbData);
}
// state....
if (saveState) {
if (flags & SFF_SAVE_STATE) {
COMPRESS((unsigned char*)basePtr, dataSize, jsfSaveToFlash_writecb, cbData);
}
endOfData = cbData[1];
Expand Down Expand Up @@ -336,21 +417,20 @@ void jsfSaveToFlash(bool saveState, JsVar *bootCode) {

if (success) {
jsiConsolePrintf("\nCompressed %d bytes to %d", dataSize, writtenBytes);
jshFlashWrite(&endOfData, FLASH_SAVED_CODE_START+4, 4); // write position of end of data, at start of address space
jshFlashWrite(&endOfData, FLASH_STATE_END_LOCATION, 4); // write position of end of data, at start of address space

uint32_t magic = FLASH_MAGIC;
jshFlashWrite(&magic, FLASH_MAGIC_LOCATION, 4);


jsiConsolePrint("\nChecking...");
cbData[0] = FLASH_SAVED_CODE_START+8;
if (jsvIsString(bootCode)) {
uint32_t bootCodeLen = (uint32_t)jsvGetStringLength(bootCode)+1;
cbData[0] = FLASH_DATA_LOCATION;
if (bootCodeLen) {
cbData[0] += bootCodeLen;
}
cbData[1] = 0; // increment if fails
// TODO: check boot code written ok
if (saveState)
if (flags & SFF_SAVE_STATE)
COMPRESS((unsigned char*)basePtr, dataSize, jsfSaveToFlash_checkcb, cbData);
uint32_t errors = cbData[1];

Expand Down Expand Up @@ -397,11 +477,12 @@ void jsfLoadStateFromFlash() {
uint32_t *basePtr = (uint32_t *)_jsvGetAddressOf(1);

uint32_t cbData[2];
int32_t bootCodeLen;
jshFlashRead(&bootCodeLen, FLASH_SAVED_CODE_START, 4); // length of boot code
jshFlashRead(&cbData[0], FLASH_SAVED_CODE_START+4, 4); // end address
cbData[1] = FLASH_SAVED_CODE_START+8; // start address
if (bootCodeLen>0) cbData[1] += bootCodeLen;
uint32_t bootCodeLen;
jshFlashRead(&bootCodeLen, FLASH_BOOT_CODE_INFO_LOCATION, 4); // length of boot code
bootCodeLen &= BOOT_CODE_LENGTH_MASK;
jshFlashRead(&cbData[0], FLASH_STATE_END_LOCATION, 4); // end address
cbData[1] = FLASH_DATA_LOCATION; // start address
if (bootCodeLen) cbData[1] += bootCodeLen;
uint32_t len = cbData[0]-FLASH_SAVED_CODE_START;
if (len>1000000) {
jsiConsolePrintf("Invalid saved code in flash!\n");
Expand All @@ -412,8 +493,10 @@ void jsfLoadStateFromFlash() {
#endif
}

/// Load bootup code from flash (this is textual JS code). return true if it exists
bool jsfLoadBootCodeFromFlash() {
/** Load bootup code from flash (this is textual JS code). return true if it exists and was executed.
* isReset should be set if we're loading after a reset (eg, does the user expect this to be run or not)
*/
bool jsfLoadBootCodeFromFlash(bool isReset) {
const char *code = 0;
#ifdef LINUX
FILE *f = fopen("espruino.boot","rb");
Expand All @@ -430,11 +513,14 @@ bool jsfLoadBootCodeFromFlash() {
#else // !LINUX
if (!jsfFlashContainsCode()) return false;

int32_t bootCodeLen;
jshFlashRead(&bootCodeLen, FLASH_SAVED_CODE_START, 4); // length of boot code
if (bootCodeLen<=0) return false;
uint32_t bootCodeInfo;
jshFlashRead(&bootCodeInfo, FLASH_BOOT_CODE_INFO_LOCATION, 4); // length of boot code
uint32_t bootCodeLen = bootCodeInfo & BOOT_CODE_LENGTH_MASK;
if (!bootCodeLen) return false;
// Don't execute code if we've reset and code shouldn't always be run
if (isReset && !(bootCodeInfo & BOOT_CODE_RUN_ALWAYS)) return false;

code = (const char *)(FLASH_SAVED_CODE_START+8);
code = (const char *)(FLASH_DATA_LOCATION);
#endif
jsvUnLock(jspEvaluate(code));
return true;
Expand Down
13 changes: 10 additions & 3 deletions src/jswrap_flash.h
Original file line number Diff line number Diff line change
Expand Up @@ -19,11 +19,18 @@ void jswrap_flash_erasePage(JsVar *addr);
void jswrap_flash_write(JsVar *data, int addr);
JsVar *jswrap_flash_read(int length, int addr);

typedef enum {
SFF_SAVE_STATE = 1, // Should we save state to flash?
SFF_BOOT_CODE_ALWAYS = 2 // When saving boot code, ensure it should always be run - even after reset
} JsvSaveFlashFlags;

/// Save contents of JsVars into Flash. If bootCode is specified, save bootup code too.
void jsfSaveToFlash(bool saveState, JsVar *bootCode);
void jsfSaveToFlash(JsvSaveFlashFlags flags, JsVar *bootCode);
/// Load the RAM image from flash (this is the actual interpreter state)
void jsfLoadStateFromFlash();
/// Load bootup code from flash (this is textual JS code). return true if it exists
bool jsfLoadBootCodeFromFlash();
/** Load bootup code from flash (this is textual JS code). return true if it exists and was executed.
* isReset should be set if we're loading after a reset (eg, does the user expect this to be run or not)
*/
bool jsfLoadBootCodeFromFlash(bool isReset);
/// Returns true if flash contains something useful
bool jsfFlashContainsCode();

0 comments on commit 92b8b58

Please sign in to comment.