forked from espruino/Espruino
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathjswrap_flash.c
More file actions
542 lines (494 loc) · 17.5 KB
/
Copy pathjswrap_flash.c
File metadata and controls
542 lines (494 loc) · 17.5 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
/*
* This file is part of Espruino, a JavaScript interpreter for Microcontrollers
*
* Copyright (C) 2013 Gordon Williams <gw@pur3.co.uk>
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*
* ----------------------------------------------------------------------------
* This file is designed to be parsed during the build process
*
* JavaScript Flash IO functions
* ----------------------------------------------------------------------------
*/
#include "jswrap_flash.h"
#include "jsvarcache.h"
#include "jshardware.h"
#include "jsvariterator.h"
#include "jsinteractive.h"
#ifdef USE_HEATSHRINK
#include "compress_heatshrink.h"
#define COMPRESS heatshrink_encode
#define DECOMPRESS heatshrink_decode
#else
#include "compress_rle.h"
#define COMPRESS rle_encode
#define DECOMPRESS rle_decode
#endif
#ifdef LINUX
// file IO for load/save
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#endif
/*JSON{
"type" : "library",
"class" : "Flash",
"ifndef" : "SAVE_ON_FLASH"
}
This module allows access to read and write the STM32's flash memory.
It should be used with extreme caution, as it is easy to overwrite parts of Flash
memory belonging to Espruino or even its bootloader. If you damage the bootloader
then you may need external hardware such as a USB-TTL converter to restore it. For
more information on restoring the bootloader see `Advanced Reflashing` in your
board's reference pages.
To see which areas of memory you can and can't overwrite, look at the values
reported by `process.memory()`.
*/
/*JSON{
"type" : "staticmethod",
"ifndef" : "SAVE_ON_FLASH",
"class" : "Flash",
"name" : "getPage",
"generate" : "jswrap_flash_getPage",
"params" : [
["addr","int","An address in memory"]
],
"return" : ["JsVar","An object of the form `{ addr : #, length : #}`, where `addr` is the start address of the page, and `length` is the length of it (in bytes). Returns undefined if no page at address"]
}
Returns the start and length of the flash page containing the given address.
*/
JsVar *jswrap_flash_getPage(int addr) {
uint32_t pageStart, pageLength;
if (!jshFlashGetPage((uint32_t)addr, &pageStart, &pageLength))
return 0;
JsVar *obj = jsvNewObject();
if (!obj) return 0;
jsvObjectSetChildAndUnLock(obj, "addr", jsvNewFromInteger((JsVarInt)pageStart));
jsvObjectSetChildAndUnLock(obj, "length", jsvNewFromInteger((JsVarInt)pageLength));
return obj;
}
/*JSON{
"type" : "staticmethod",
"ifndef" : "SAVE_ON_FLASH",
"class" : "Flash",
"name" : "getFree",
"generate" : "jswrap_flash_getFree",
"return" : ["JsVar", "Array of objects with `addr` and `length` properties"]
}
This method returns an array of objects of the form `{addr : #, length : #}`, representing
contiguous areas of flash memory in the chip that are not used for anything.
The memory areas returned are on page boundaries. This means that you can
safely erase the page containing any address here, and you won't risk
deleting part of the Espruino firmware.
*/
JsVar *jswrap_flash_getFree() {
JsVar *arr = jshFlashGetFree();
if (!arr) arr=jsvNewEmptyArray();
return arr;
}
/*JSON{
"type" : "staticmethod",
"ifndef" : "SAVE_ON_FLASH",
"class" : "Flash",
"name" : "erasePage",
"generate" : "jswrap_flash_erasePage",
"params" : [
["addr","JsVar","An address in the page that is to be erased"]
]
}
Erase a page of flash memory
*/
void jswrap_flash_erasePage(JsVar *addr) {
if (!jsvIsInt(addr)) {
jsExceptionHere(JSET_ERROR, "Address should be an integer, got %t", addr);
return;
}
jshFlashErasePage((uint32_t)jsvGetInteger(addr));
}
/*JSON{
"type" : "staticmethod",
"ifndef" : "SAVE_ON_FLASH",
"class" : "Flash",
"name" : "write",
"generate" : "jswrap_flash_write",
"params" : [
["data","JsVar","The data to write. This must be a multiple of 4 bytes."],
["addr","int","The address to start writing from, this must be on a word boundary (a multiple of 4)"]
]
}
Write data into memory at the given address - IN MULTIPLES OF 4 BYTES.
In flash memory you may only turn bits that are 1 into bits that are 0. If
you're writing data into an area that you have already written (so `read`
doesn't return all `0xFF`) you'll need to call `erasePage` to clear the
entire page.
*/
void jswrap_flash_write(JsVar *data, int addr) {
if (jsvIsUndefined(data)) {
jsExceptionHere(JSET_ERROR, "Data is not defined");
return;
}
JSV_GET_AS_CHAR_ARRAY(flashData, flashDataLen, data);
if ((addr&3) || (flashDataLen&3)) {
jsExceptionHere(JSET_ERROR, "Data and address must be multiples of 4");
return;
}
if (flashData && flashDataLen)
jshFlashWrite(flashData, (unsigned int)addr, (unsigned int)flashDataLen);
}
/*JSON{
"type" : "staticmethod",
"ifndef" : "SAVE_ON_FLASH",
"class" : "Flash",
"name" : "read",
"generate" : "jswrap_flash_read",
"params" : [
["length","int","The amount of data to read (in bytes)"],
["addr","int","The address to start writing from"]
],
"return" : ["JsVar","A Uint8Array of data"]
}
Read flash memory from the given address
*/
JsVar *jswrap_flash_read(int length, int addr) {
if (length<=0) return 0;
JsVar *arr = jsvNewTypedArray(ARRAYBUFFERVIEW_UINT8, length);
if (!arr) return 0;
JsvArrayBufferIterator it;
jsvArrayBufferIteratorNew(&it, arr, 0);
while (jsvArrayBufferIteratorHasElement(&it)) {
char c;
jshFlashRead(&c, (uint32_t)(addr++), 1);
jsvArrayBufferIteratorSetByteValue(&it, c);
jsvArrayBufferIteratorNext(&it);
}
jsvArrayBufferIteratorFree(&it);
return arr;
}
#ifndef LINUX
// cbdata = uint32_t[end_address, address, data]
void jsfSaveToFlash_writecb(unsigned char ch, uint32_t *cbdata) {
// Only write if we can fit in flash
if (cbdata[1]<cbdata[0]) {
// write only a word at a time
cbdata[2]=(uint32_t)(ch<<24) | (cbdata[2]>>8);
if ((cbdata[1]&3)==3)
jshFlashWrite(&cbdata[2], cbdata[1]&(uint32_t)~3, 4);
}
// inc address ptr
cbdata[1]++;
if ((cbdata[1]&1023)==0) jsiConsolePrint(".");
}
// cbdata = uint32_t[address, errorcount]
void jsfSaveToFlash_checkcb(unsigned char ch, uint32_t *cbdata) {
unsigned char data;
jshFlashRead(&data,cbdata[0]++, 1);
if (data!=ch) cbdata[1]++; // error count
}
// cbdata = uint32_t[end_address, address]
int jsfLoadFromFlash_readcb(uint32_t *cbdata) {
if (cbdata[1]>=cbdata[0]) return -1; // at end
unsigned char data;
jshFlashRead(&data, cbdata[1]++, 1);
return data;
}
#else
int jsfLoadFromFlash_readcb(uint32_t *cbdata) {
unsigned char ch;
if (fread(&ch,1,1,(FILE*)cbdata)==1) return ch;
return -1;
}
void jsfSaveToFlash_writecb(unsigned char ch, uint32_t *cbdata) {
fwrite(&ch,1,1,(FILE*)cbdata);
}
#endif
// ------------------------------------------------------------------------
// ------------------------------------------------------------------------
// Global flash read/write
// ------------------------------------------------------------------------
// ------------------------------------------------------------------------
/* 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
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");
}
}
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);
uint32_t *basePtr = (uint32_t *)_jsvGetAddressOf(1);
uint32_t pageStart, pageLength;
bool tryAgain = true;
bool success = false;
uint32_t writtenBytes;
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;
if (bootCodeLen+64 < jsuGetFreeStack())
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");
bootCodeLen = 0;
}
}
}
while (tryAgain) {
tryAgain = false;
jsiConsolePrint("Erasing Flash...");
uint32_t addr = FLASH_SAVED_CODE_START;
if (jshFlashGetPage((uint32_t)addr, &pageStart, &pageLength)) {
jshFlashErasePage(pageStart);
while (pageStart+pageLength < FLASH_MAGIC_LOCATION) { // until end address
jsiConsolePrint(".");
addr = pageStart+pageLength; // next page
if (!jshFlashGetPage((uint32_t)addr, &pageStart, &pageLength)) break;
jshFlashErasePage(pageStart);
}
}
// Now start writing
cbData[0] = FLASH_MAGIC_LOCATION; // end of available flash
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)) {
bootCodeLen = (uint32_t)jsvGetStringLength(bootCode);
if (bootCodeLen) {
// Only write code if we actually have any
originalBootCodeInfo = bootCodeLen;
if (flags & SFF_BOOT_CODE_ALWAYS)
originalBootCodeInfo |= 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++;
}
} else if (originalBootCode) {
// previously saved boot code that we want to keep
assert(originalBootCode && bootCodeLen);
size_t i;
for (i=0;i<bootCodeLen;i++)
jsfSaveToFlash_writecb(originalBootCode[i], cbData);
}
// write size of boot code to flash
jshFlashWrite(&originalBootCodeInfo, FLASH_BOOT_CODE_INFO_LOCATION, 4);
// state....
if (flags & SFF_SAVE_STATE) {
COMPRESS((unsigned char*)basePtr, dataSize, jsfSaveToFlash_writecb, cbData);
}
endOfData = cbData[1];
// make sure we write everything in buffer
jsfSaveToFlash_writecb(0,cbData);
jsfSaveToFlash_writecb(0,cbData);
jsfSaveToFlash_writecb(0,cbData);
writtenBytes = endOfData - FLASH_SAVED_CODE_START;
if (cbData[1]>=cbData[0]) {
jsiConsolePrintf("\nERROR: Too big to save to flash (%d vs %d bytes)\n", writtenBytes, FLASH_MAGIC_LOCATION-FLASH_SAVED_CODE_START);
jsvSoftInit();
jspSoftInit();
if (jsiFreeMoreMemory()) {
jsiConsolePrint("Deleting command history and trying again...\n");
while (jsiFreeMoreMemory());
tryAgain = true;
}
jspSoftKill();
jsvSoftKill();
} else {
success = true;
}
}
if (success) {
jsiConsolePrintf("\nCompressed %d bytes to %d", dataSize, writtenBytes);
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_DATA_LOCATION;
if (bootCodeLen) {
cbData[0] += bootCodeLen;
}
cbData[1] = 0; // increment if fails
// TODO: check boot code written ok
if (flags & SFF_SAVE_STATE)
COMPRESS((unsigned char*)basePtr, dataSize, jsfSaveToFlash_checkcb, cbData);
uint32_t errors = cbData[1];
if (!jsfFlashContainsCode()) {
jsiConsolePrint("\nFlash Magic Byte is wrong");
errors++;
}
if (errors)
jsiConsolePrintf("\nThere were %d errors!\n", errors);
else
jsiConsolePrint("\nDone!\n");
}
#endif
}
/// Load the RAM image from flash (this is the actual interpreter state)
void jsfLoadStateFromFlash() {
#ifdef LINUX
FILE *f = fopen("espruino.state","rb");
if (f) {
unsigned int jsVarCount;
fread(&jsVarCount, sizeof(unsigned int), 1, f);
jsiConsolePrintf("\nDecompressing to %d bytes...", jsVarCount*sizeof(JsVar));
jsvSetMemoryTotal(jsVarCount);
/*JsVarRef i;
for (i=1;i<=jsVarCount;i++) {
fread(_jsvGetAddressOf(i),1,sizeof(JsVar),f);
}*/
DECOMPRESS(jsfLoadFromFlash_readcb, (uint32_t*)f, (unsigned char*)_jsvGetAddressOf(1));
fclose(f);
} else {
jsiConsolePrint("\nFile open of espruino.state failed... \n");
}
#else // !LINUX
if (!jsfFlashContainsCode()) {
jsiConsolePrintf("No code in flash!\n");
return;
}
// unsigned int dataSize = jsvGetMemoryTotal() * sizeof(JsVar);
uint32_t *basePtr = (uint32_t *)_jsvGetAddressOf(1);
uint32_t cbData[2];
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");
return;
}
jsiConsolePrintf("Loading %d bytes from flash...\n", len);
DECOMPRESS(jsfLoadFromFlash_readcb, cbData, (unsigned char*)basePtr);
#endif
}
/** 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) {
char *code = 0;
#ifdef LINUX
FILE *f = fopen("espruino.boot","rb");
if (!f) return false;
fseek(f, 0, SEEK_END);
long len = ftell(f);
fseek(f, 0, SEEK_SET);
code = malloc(len+1); // null
fread(code, len, 1, f);
code[len] = 0;
fclose(f);
#else // !LINUX
if (!jsfFlashContainsCode()) 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 = (char *)(FLASH_DATA_LOCATION);
#endif
jsvUnLock(jspEvaluate(code, true /* We are expecting this ptr to hang around */));
return true;
}
bool jsfFlashContainsCode() {
#ifdef LINUX
FILE *f = fopen("espruino.state","rb");
if (f) fclose(f);
return f!=0;
#else // !LINUX
int magic;
jshFlashRead(&magic, FLASH_MAGIC_LOCATION, sizeof(magic));
return magic == (int)FLASH_MAGIC;
#endif
}