|
| 1 | +/** |
| 2 | + * Builds and reads a micro:bit MicroPython File System from Intel Hex data. |
| 3 | + * |
| 4 | + * Follows this implementation: |
| 5 | + * https://github.com/bbcmicrobit/micropython/blob/v1.0.1/source/microbit/filesystem.c |
| 6 | + * |
| 7 | + * How it works: |
| 8 | + * The File system size is calculated based on the UICR data addded to the |
| 9 | + * MicroPython final hex to determine the limits of the filesystem space. |
| 10 | + * Based on how many space there is available it calculates how many free |
| 11 | + * chunks it can fit, each chunk being of CHUNK_LEN size in bytes. |
| 12 | + * There is one spare page which holds persistent configuration data that is |
| 13 | + * used by MicroPython for bulk erasing, so we also mark it as such here. |
| 14 | + * |
| 15 | + * Each chunk is enumerated with an index number. The first chunk starts with |
| 16 | + * index 1 (as value 0 is reserved to indicate a Freed chunk) at the bottom of |
| 17 | + * the File System (lowest address), and the indexes increase sequentially. |
| 18 | + * Each chunk consists of a one byte marker at the head and a one tail byte. |
| 19 | + * The byte at the tail is a pointer to the next chunk index. |
| 20 | + * The head byte marker is either one of the values in the ChunkMarker enum, to |
| 21 | + * indicate the a special type of chunk, or a pointer to the previous chunk |
| 22 | + * index. |
| 23 | + * The special markers indicate whether the chunk is the start of a file, if it |
| 24 | + * is Unused, if it is Freed (same as unused, but not yet erased) or if this |
| 25 | + * is the start of a flash page used for Persistent Data (bulk erase operation). |
| 26 | + * |
| 27 | + * A file consists of a double linked list of chunks. The first chunk in a |
| 28 | + * file, indicated by the FileStart marker, contains the data end offset for |
| 29 | + * the last chunk and the file name. |
| 30 | + */ |
1 | 31 | import MemoryMap from 'nrf-intel-hex';
|
2 | 32 |
|
3 | 33 | import {
|
@@ -167,37 +197,14 @@ class FsFile {
|
167 | 197 | }
|
168 | 198 | this._dataBytes = data;
|
169 | 199 | // Generate a single byte array with the filesystem data bytes.
|
170 |
| - const fileHeader = this.generateFileHeaderBytes(); |
| 200 | + const fileHeader = this._generateFileHeaderBytes(); |
171 | 201 | this._fsDataBytes = new Uint8Array(
|
172 | 202 | fileHeader.length + this._dataBytes.length
|
173 | 203 | );
|
174 | 204 | this._fsDataBytes.set(fileHeader, 0);
|
175 | 205 | this._fsDataBytes.set(this._dataBytes, fileHeader.length);
|
176 | 206 | }
|
177 | 207 |
|
178 |
| - /** |
179 |
| - * Generates a byte array for the file header as expected by the MicroPython |
180 |
| - * file system. |
181 |
| - * |
182 |
| - * @return Byte array with the header data. |
183 |
| - */ |
184 |
| - generateFileHeaderBytes(): Uint8Array { |
185 |
| - const headerSize = |
186 |
| - CHUNK_HEADER_END_OFFSET_LEN + |
187 |
| - CHUNK_HEADER_NAME_LEN + |
188 |
| - this._filenameBytes.length; |
189 |
| - const endOffset = (headerSize + this._dataBytes.length) % CHUNK_DATA_LEN; |
190 |
| - const fileNameOffset: number = headerSize - this._filenameBytes.length; |
191 |
| - // Format header byte array |
192 |
| - const headerBytes: Uint8Array = new Uint8Array(headerSize); |
193 |
| - headerBytes[ChunkFormatIndex.EndOffset - 1] = endOffset; |
194 |
| - headerBytes[ChunkFormatIndex.NameLength - 1] = this._filenameBytes.length; |
195 |
| - for (let i = fileNameOffset; i < headerSize; ++i) { |
196 |
| - headerBytes[i] = this._filenameBytes[i - fileNameOffset]; |
197 |
| - } |
198 |
| - return headerBytes; |
199 |
| - } |
200 |
| - |
201 | 208 | /**
|
202 | 209 | * Generate an array of file system chunks for all this file content.
|
203 | 210 | *
|
@@ -272,6 +279,29 @@ class FsFile {
|
272 | 279 | }
|
273 | 280 | return chunksUsed * CHUNK_LEN;
|
274 | 281 | }
|
| 282 | + |
| 283 | + /** |
| 284 | + * Generates a byte array for the file header as expected by the MicroPython |
| 285 | + * file system. |
| 286 | + * |
| 287 | + * @return Byte array with the header data. |
| 288 | + */ |
| 289 | + private _generateFileHeaderBytes(): Uint8Array { |
| 290 | + const headerSize = |
| 291 | + CHUNK_HEADER_END_OFFSET_LEN + |
| 292 | + CHUNK_HEADER_NAME_LEN + |
| 293 | + this._filenameBytes.length; |
| 294 | + const endOffset = (headerSize + this._dataBytes.length) % CHUNK_DATA_LEN; |
| 295 | + const fileNameOffset: number = headerSize - this._filenameBytes.length; |
| 296 | + // Format header byte array |
| 297 | + const headerBytes: Uint8Array = new Uint8Array(headerSize); |
| 298 | + headerBytes[ChunkFormatIndex.EndOffset - 1] = endOffset; |
| 299 | + headerBytes[ChunkFormatIndex.NameLength - 1] = this._filenameBytes.length; |
| 300 | + for (let i = fileNameOffset; i < headerSize; ++i) { |
| 301 | + headerBytes[i] = this._filenameBytes[i - fileNameOffset]; |
| 302 | + } |
| 303 | + return headerBytes; |
| 304 | + } |
275 | 305 | }
|
276 | 306 |
|
277 | 307 | /**
|
|
0 commit comments