microbit: Initial merge of file fystem codebase
This commit is contained in:
parent
3cf56b8f44
commit
b71d1e37a1
6 changed files with 2317 additions and 0 deletions
|
@ -72,6 +72,11 @@ DEALINGS IN THE SOFTWARE.
|
|||
#define MICROBIT_HEAP_END (CORTEX_M0_STACK_BASE - MICROBIT_STACK_SIZE)
|
||||
#endif
|
||||
|
||||
// Defines the size of a physical FLASH page in RAM.
|
||||
#ifndef PAGE_SIZE
|
||||
#define PAGE_SIZE 1024
|
||||
#endif
|
||||
|
||||
// Enables or disables the MicroBitHeapllocator. Note that if disabled, no reuse of the SRAM normally
|
||||
// reserved for SoftDevice is possible, and out of memory condition will no longer be trapped...
|
||||
// i.e. panic() will no longer be triggered on memory full conditions.
|
||||
|
@ -318,6 +323,16 @@ DEALINGS IN THE SOFTWARE.
|
|||
#define MICROBIT_DEFAULT_SERIAL_MODE SYNC_SLEEP
|
||||
#endif
|
||||
|
||||
//
|
||||
// File System configuration defaults
|
||||
//
|
||||
#ifndef MBFS_BLOCK_SIZE
|
||||
#define MBFS_BLOCK_SIZE 256
|
||||
#endif
|
||||
|
||||
#ifndef MBFS_CACHE_SIZE
|
||||
#define MBFS_CACHE_SIZE 0
|
||||
#endif
|
||||
|
||||
//
|
||||
// I/O Options
|
||||
|
|
481
inc/drivers/MicroBitFileSystem.h
Normal file
481
inc/drivers/MicroBitFileSystem.h
Normal file
|
@ -0,0 +1,481 @@
|
|||
#ifndef MICROBIT_FILE_SYSTEM_H
|
||||
#define MICROBIT_FILE_SYSTEM_H
|
||||
|
||||
#include "MicroBitConfig.h"
|
||||
#include "MicroBitFlash.h"
|
||||
|
||||
|
||||
// Configuration options.
|
||||
#define MBFS_FILENAME_LENGTH 16
|
||||
#define MBFS_MAGIC "MICROBIT_FS_1_0"
|
||||
|
||||
// open() flags.
|
||||
#define MB_READ 0x01
|
||||
#define MB_WRITE 0x02
|
||||
#define MB_CREAT 0x04
|
||||
#define MB_APPEND 0x08
|
||||
|
||||
// seek() flags.
|
||||
#define MB_SEEK_SET 0x01
|
||||
#define MB_SEEK_END 0x02
|
||||
#define MB_SEEK_CUR 0x04
|
||||
|
||||
// Status flags
|
||||
#define MBFS_STATUS_INITIALISED 0x01
|
||||
|
||||
// FileTable codes
|
||||
#define MBFS_UNUSED 0xFFFF
|
||||
#define MBFS_EOF 0xEFFF
|
||||
#define MBFS_DELETED 0x0000
|
||||
|
||||
// DirectorEntry flags
|
||||
#define MBFS_DIRECTORY_ENTRY_FREE 0x8000
|
||||
#define MBFS_DIRECTORY_ENTRY_VALID 0x4000
|
||||
#define MBFS_DIRECTORY_ENTRY_DIRECTORY 0x2000
|
||||
#define MBFS_DIRECTORY_ENTRY_NEW 0xffff
|
||||
#define MBFS_DIRECTORY_ENTRY_DELETED 0x0000
|
||||
|
||||
// Enumeration of BLOCK YPES
|
||||
#define MBFS_BLOCK_TYPE_FILE 1
|
||||
#define MBFS_BLOCK_TYPE_DIRECTORY 2
|
||||
#define MBFS_BLOCK_TYPE_FILETABLE 3
|
||||
|
||||
|
||||
|
||||
//
|
||||
// Every file in the file system has a file descriptor.
|
||||
// These are held in directory entries.
|
||||
//
|
||||
struct DirectoryEntry
|
||||
{
|
||||
char file_name[MBFS_FILENAME_LENGTH]; // Name of the file
|
||||
uint16_t first_block; // Logical block address of the start of the file.
|
||||
uint16_t flags; // Status of the file.
|
||||
uint32_t length; // Length of the file in bytes.
|
||||
};
|
||||
|
||||
struct Directory
|
||||
{
|
||||
DirectoryEntry entry[0];
|
||||
};
|
||||
|
||||
//
|
||||
// A FileDescriptor holds contextual information needed for each open file.
|
||||
//
|
||||
struct FileDescriptor
|
||||
{
|
||||
// read/write/creat flags.
|
||||
uint16_t flags;
|
||||
|
||||
// FileDescriptor id
|
||||
uint16_t id;
|
||||
|
||||
// current file position, in bytes.
|
||||
uint32_t seek;
|
||||
|
||||
// the current file size. n.b. this may be different to that stored in the DirectoryEntry.
|
||||
uint32_t length;
|
||||
|
||||
// the directory entry of this file.
|
||||
DirectoryEntry *dirent;
|
||||
|
||||
// the directory entry of our parent directory.
|
||||
DirectoryEntry *directory;
|
||||
|
||||
// We maintain a chain of open file descriptors. Reference to the next FileDescriptor in the chain.
|
||||
FileDescriptor *next;
|
||||
|
||||
// Optional writeback cache, to minimise FLASH write operations at the expense of RAM.
|
||||
uint16_t cacheLength;
|
||||
uint8_t cache[MBFS_CACHE_SIZE];
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Class definition for the MicroBit File system
|
||||
*
|
||||
* Microbit file system class. Presents a POSIX-like interface consisting of:
|
||||
* - open()
|
||||
* - close()
|
||||
* - read()
|
||||
* - write()
|
||||
* - seek()
|
||||
* - remove()
|
||||
*
|
||||
* Only a single instance shoud exist at any given time.
|
||||
*/
|
||||
class MicroBitFileSystem
|
||||
{
|
||||
private:
|
||||
|
||||
// Status flags
|
||||
uint32_t status;
|
||||
|
||||
// The instance of MicroBitFlash - the interface used for all flash writes/erasures
|
||||
MicroBitFlash flash;
|
||||
|
||||
// Total Number of logical pages available for file data (including the file table)
|
||||
int fileSystemSize;
|
||||
|
||||
// Memory address of the start of the file system.
|
||||
uint16_t* fileSystemTable = NULL;
|
||||
|
||||
// Size of the file table
|
||||
uint16_t fileSystemTableSize;
|
||||
|
||||
// Cache of the last block allocated.
|
||||
uint16_t lastBlockAllocated = 0;
|
||||
|
||||
// Reference to the root directory of the file system.
|
||||
DirectoryEntry *rootDirectory = NULL;
|
||||
|
||||
// Chain of open files.
|
||||
FileDescriptor *openFiles = NULL;
|
||||
/**
|
||||
* Initialize the flash storage system
|
||||
*
|
||||
* The file system is located dynamically, based on where the program code
|
||||
* and code data finishes. This avoids having to allocate a fixed flash
|
||||
* region for builds even without MicroBitFileSystem.
|
||||
*
|
||||
* This method checks if the file system already exists, and loads it it.
|
||||
* If not, it will determines the optimal size of the file system, if necessary, and format the space
|
||||
*
|
||||
* @return MICROBIT_OK on success, or an error code.
|
||||
*/
|
||||
int init(uint32_t flashStart, int flashPages);
|
||||
|
||||
/**
|
||||
* Attempts to detect and load an exisitng file file system.
|
||||
*
|
||||
* @return MICROBIT_OK on success, or MICROBIT_NO_DATA if the file system could not be found.
|
||||
*/
|
||||
int load();
|
||||
|
||||
/**
|
||||
* Allocate a free logical block.
|
||||
* This is chosen at random from the blocks available, to even out the wear on the physical device.
|
||||
* @return NULL on error, page address on success
|
||||
*/
|
||||
uint16_t getFreeBlock();
|
||||
|
||||
/**
|
||||
* Allocates a free physical block.
|
||||
* This is chosen at random from the blocks available, to even out the wear on the physical device.
|
||||
* @return NULL on error, page address on success
|
||||
*/
|
||||
uint32_t* getFreePage();
|
||||
|
||||
/**
|
||||
* Retrieve the DirectoryEntry assoiated with the given file's DIRECTORY (not the file itself).
|
||||
*
|
||||
* @param filename A fully qualified filename, from the root.
|
||||
*
|
||||
* @return A pointer to the DirectoryEntry for the given file's directory, or NULL if no entry is found.
|
||||
*/
|
||||
DirectoryEntry* getDirectoryOf(char const * filename);
|
||||
|
||||
/**
|
||||
* Retrieve the DirectoryEntry for the given filename.
|
||||
*
|
||||
* @param filename A fully or partially qualified filename.
|
||||
* @param directory The directory to search. If ommitted, the root directory will be used.
|
||||
*
|
||||
* @return A pointer to the DirectoryEntry for the given file, or NULL if no entry is found.
|
||||
*/
|
||||
DirectoryEntry* getDirectoryEntry(char const * filename, const DirectoryEntry *directory = NULL);
|
||||
|
||||
/**
|
||||
* Create a new DirectoryEntry with the given filename and flags.
|
||||
*
|
||||
* @param filename A fully or partially qualified filename.
|
||||
* @param directory The directory in which to create the entry
|
||||
* @param isDirectory true if the entry being created is itself a directory
|
||||
*
|
||||
* @return A pointer to the new DirectoryEntry for the given file, or NULL if it was not possible to allocated resources.
|
||||
*/
|
||||
DirectoryEntry* createFile(char const * filename, DirectoryEntry *directory, bool isDirectory);
|
||||
|
||||
/**
|
||||
* Allocate a free DiretoryEntry in the given directory, extending and refreshing the directory block if necessary.
|
||||
*
|
||||
* @param directory The directory to add a DirectoryEntry to
|
||||
* @return A pointer to the new DirectoryEntry for the given file, or NULL if it was not possible to allocated resources.
|
||||
*/
|
||||
DirectoryEntry* createDirectoryEntry(DirectoryEntry *directory);
|
||||
|
||||
/**
|
||||
* Refresh the physical page associated with the given block.
|
||||
* Any logical blocks marked for deletion on that page are recycled.
|
||||
*
|
||||
* @param block the block to recycle.
|
||||
* @param isDirectory set to tru of the block contains a directory to enable deep DirectoryEntry recycling.
|
||||
* @return MICROBIT_OK on success.
|
||||
*/
|
||||
int recycleBlock(uint16_t block, int type = MBFS_BLOCK_TYPE_FILE);
|
||||
|
||||
/**
|
||||
* Refresh the physical pages associated with the file table.
|
||||
* Any logical blocks marked for deletion on those pages are recycled back to UNUSED.
|
||||
**
|
||||
* @return MICROBIT_OK on success.
|
||||
*/
|
||||
int recycleFileTable();
|
||||
|
||||
/**
|
||||
* Retrieve a memory pointer for the start of the physical memory page containing the given block.
|
||||
*
|
||||
* @param block A valid block in a file.
|
||||
*
|
||||
* @return A pointer to the physical page in FLASH memory holding the given block.
|
||||
*/
|
||||
uint32_t *getPage(uint16_t block);
|
||||
|
||||
/**
|
||||
* Retrieve a memory pointer for the start of the given block.
|
||||
*
|
||||
* @param block A valid block in a file.
|
||||
*
|
||||
* @return A pointer to the FLASH memory associated with the given block.
|
||||
*/
|
||||
uint32_t *getBlock(uint16_t block);
|
||||
|
||||
/**
|
||||
* Retrieve the next block in a chain.
|
||||
*
|
||||
* @param block A valid block in a file.
|
||||
*
|
||||
* @return The block number of the next block in the file.
|
||||
*/
|
||||
uint16_t getNextFileBlock(uint16_t block);
|
||||
|
||||
/**
|
||||
* Determine the logical block that contains the given address.
|
||||
*
|
||||
* @param address A valid memory location within the file system space.
|
||||
*
|
||||
* @return The block number containing the given address.
|
||||
*/
|
||||
uint16_t getBlockNumber(void *address);
|
||||
|
||||
/**
|
||||
* Determine the number of logical blocks required to hold the file table.
|
||||
*
|
||||
* @return The number of logical blocks required to hold the file table.
|
||||
*/
|
||||
uint16_t calculateFileTableSize();
|
||||
|
||||
/*
|
||||
* Update a file table entry to a given value.
|
||||
*
|
||||
* @param block The block to update.
|
||||
* @param value The value to store in the file table.
|
||||
* @return MICROBIT_OK on success.
|
||||
*/
|
||||
int fileTableWrite(uint16_t block, uint16_t value);
|
||||
|
||||
/**
|
||||
* Searches the list of open files for one with the given identifier.
|
||||
*
|
||||
* @param fd A previsouly opened file identifier, as returned by open().
|
||||
* @param remove Remove the file descriptor from the list if true.
|
||||
* @return A FileDescriptor matching the given ID, or NULL if the file is not open.
|
||||
*/
|
||||
FileDescriptor* getFileDescriptor(int fd, bool remove = false);
|
||||
|
||||
/**
|
||||
* Initialises a new file system
|
||||
*
|
||||
* @return MICROBIT_OK on success, or an error code..
|
||||
*/
|
||||
int format();
|
||||
|
||||
/**
|
||||
* Flush a given file's cache back to FLASH memory.
|
||||
*
|
||||
* @param file File descriptor to flush.
|
||||
* @return The number of bytes written.
|
||||
*
|
||||
*/
|
||||
int writeBack(FileDescriptor *file);
|
||||
|
||||
/**
|
||||
* Write a given buffer to the file provided.
|
||||
*
|
||||
* @param file FileDescriptor of the file to write
|
||||
* @param buffer The start of the buffer to write
|
||||
* @param length The number of bytes to write
|
||||
* @return The number of bytes written.
|
||||
*/
|
||||
int writeBuffer(FileDescriptor *file, uint8_t* buffer, int length);
|
||||
|
||||
public:
|
||||
|
||||
static MicroBitFileSystem *defaultFileSystem;
|
||||
|
||||
/**
|
||||
* Constructor. Creates an instance of a MicroBitFileSystem.
|
||||
*/
|
||||
MicroBitFileSystem(uint32_t flashStart = 0, int flashPages = 0);
|
||||
|
||||
/**
|
||||
* Open a new file, and obtain a new file handle (int) to
|
||||
* read/write/seek the file. The flags are:
|
||||
* - MB_READ : read from the file.
|
||||
* - MB_WRITE : write to the file.
|
||||
* - MB_CREAT : create a new file, if it doesn't already exist.
|
||||
*
|
||||
* If a file is opened that doesn't exist, and MB_CREAT isn't passed,
|
||||
* an error is returned, otherwise the file is created.
|
||||
*
|
||||
* @todo The same file can only be opened by a single handle at once.
|
||||
* @todo Add MB_APPEND flag.
|
||||
*
|
||||
* @param filename name of the file to open, must be null terminated.
|
||||
* @param flags
|
||||
* @return return the file handle,MICROBIT_NOT_SUPPORTED if the file system has
|
||||
* not been initialised MICROBIT_INVALID_PARAMETER if the filename is
|
||||
* too large, MICROBIT_NO_RESOURCES if the file system is full.
|
||||
*
|
||||
* @code
|
||||
* MicroBitFileSystem f();
|
||||
* int fd = f.open("test.txt", MB_WRITE|MB_CREAT);
|
||||
* if(fd<0)
|
||||
* print("file open error");
|
||||
* @endcode
|
||||
*/
|
||||
int open(char const * filename, uint32_t flags);
|
||||
|
||||
/**
|
||||
* Close the specified file handle.
|
||||
* File handle resources are then made available for future open() calls.
|
||||
*
|
||||
* close() must be called at some point to ensure the filesize in the
|
||||
* FT is synced with the cached value in the FD.
|
||||
*
|
||||
* @warning if close() is not called, the FT may not be correct,
|
||||
* leading to data loss.
|
||||
*
|
||||
* @param fd file descriptor - obtained with open().
|
||||
* @return non-zero on success, MICROBIT_NOT_SUPPORTED if the file system has not
|
||||
* been initialised, MICROBIT_INVALID_PARAMETER if the given file handle
|
||||
* is invalid.
|
||||
*
|
||||
* @code
|
||||
* MicroBitFileSystem f();
|
||||
* int fd = f.open("test.txt", MB_READ);
|
||||
* if(!f.close(fd))
|
||||
* print("error closing file.");
|
||||
* @endcode
|
||||
*/
|
||||
int close(int fd);
|
||||
|
||||
/**
|
||||
* Move the current position of a file handle, to be used for
|
||||
* subsequent read/write calls.
|
||||
*
|
||||
* The offset modifier can be:
|
||||
* - MB_SEEK_SET set the absolute seek position.
|
||||
* - MB_SEEK_CUR set the seek position based on the current offset.
|
||||
* - MB_SEEK_END set the seek position from the end of the file.
|
||||
* E.g. to seek to 2nd-to-last byte, use offset=-1.
|
||||
*
|
||||
* @param fd file handle, obtained with open()
|
||||
* @param offset new offset, can be positive/negative.
|
||||
* @param flags
|
||||
* @return new offset position on success, MICROBIT_NOT_SUPPORTED if the file system
|
||||
* is not intiialised, MICROBIT_INVALID_PARAMETER if the flag given is invalid
|
||||
* or the file handle is invalid.
|
||||
*
|
||||
* @code
|
||||
* MicroBitFileSystem f;
|
||||
* int fd = f.open("test.txt", MB_READ);
|
||||
* f.seek(fd, -100, MB_SEEK_END); //100 bytes before end of file.
|
||||
* @endcode
|
||||
*/
|
||||
int seek(int fd, int offset, uint8_t flags);
|
||||
|
||||
/**
|
||||
* Write data to the file.
|
||||
*
|
||||
* Write from buffer, length bytes to the current seek position.
|
||||
* On each invocation to write, the seek position of the file handle
|
||||
* is incremented atomically, by the number of bytes returned.
|
||||
*
|
||||
* The cached filesize in the FD is updated on this call. Also, the
|
||||
* FT file size is updated only if a new page(s) has been written too,
|
||||
* to reduce the number of FT writes.
|
||||
*
|
||||
* @param fd File handle
|
||||
* @param buffer the buffer from which to write data
|
||||
* @param length number of bytes to write
|
||||
* @return number of bytes written on success, MICROBIT_NO_RESOURCES if data did
|
||||
* not get written to flash or the file system has not been initialised,
|
||||
* or this file was not opened with the MB_WRITE flag set, MICROBIT_INVALID_PARAMETER
|
||||
* if the given file handle is invalid.
|
||||
*
|
||||
* @code
|
||||
* MicroBitFileSystem f();
|
||||
* int fd = f.open("test.txt", MB_WRITE);
|
||||
* if(f.write(fd, "hello!", 7) != 7)
|
||||
* print("error writing");
|
||||
* @endcode
|
||||
*/
|
||||
int write(int fd, uint8_t* buffer, int length);
|
||||
|
||||
/**
|
||||
* Read data from the file.
|
||||
*
|
||||
* Read len bytes from the current seek position in the file, into the
|
||||
* buffer. On each invocation to read, the seek position of the file
|
||||
* handle is incremented atomically, by the number of bytes returned.
|
||||
*
|
||||
* @param fd File handle, obtained with open()
|
||||
* @param buffer to store data
|
||||
* @param len number of bytes to read
|
||||
* @return number of bytes read on success, MICROBIT_NOT_SUPPORTED if the file
|
||||
* system is not initialised, or this file was not opened with the
|
||||
* MB_READ flag set, MICROBIT_INVALID_PARAMETER if the given file handle
|
||||
* is invalid.
|
||||
*
|
||||
* @code
|
||||
* MicroBitFileSystem f;
|
||||
* int fd = f.open("read.txt", MB_READ);
|
||||
* if(f.read(fd, buffer, 100) != 100)
|
||||
* print("read error");
|
||||
* @endcode
|
||||
*/
|
||||
int read(int fd, uint8_t* buffer, int len);
|
||||
|
||||
/**
|
||||
* Remove a file from the system, and free allocated assets
|
||||
* (including assigned blocks which are returned for use by other files).
|
||||
*
|
||||
* @todo the file must not already have an open file handle.
|
||||
*
|
||||
* @param filename name of the file to remove.
|
||||
* @return MICROBIT_OK on success, MICROBIT_INVALID_PARAMETER if the given filename
|
||||
* does not exist, MICROBIT_CANCELLED if something went wrong
|
||||
*
|
||||
* @code
|
||||
* MicroBitFileSystem f;
|
||||
* if(!f.remove("file.txt"))
|
||||
* print("file could not be removed")
|
||||
* @endcode
|
||||
*/
|
||||
int remove(char const * filename);
|
||||
|
||||
/**
|
||||
* Creates a new directory with the given name and location
|
||||
*
|
||||
* @param name The fully qualified name of the new directory.
|
||||
* @return MICROBIT_OK on success, MICROBIT_INVALID_PARAMETER if the path is invalid, or MICROBT_NO_RESOURCES if the FileSystem is full.
|
||||
*/
|
||||
int createDirectory(char const *name);
|
||||
|
||||
void debugFAT();
|
||||
void debugRootDirectory();
|
||||
int debugDirectory(char *name);
|
||||
void debugDirectory(DirectoryEntry *directory);
|
||||
};
|
||||
|
||||
#endif
|
138
inc/drivers/MicroBitFlash.h
Normal file
138
inc/drivers/MicroBitFlash.h
Normal file
|
@ -0,0 +1,138 @@
|
|||
#ifndef MICROBIT_FLASH_H_
|
||||
#define MICROBIT_FLASH_H_
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#define DEFAULT_SCRATCH_ADDR 0x3B000
|
||||
#define PAGE_SIZE 1024
|
||||
|
||||
typedef enum flash_mode_t
|
||||
{
|
||||
WR_WRITE,
|
||||
WR_MEMSET
|
||||
} flash_mode;
|
||||
|
||||
class MicroBitFlash
|
||||
{
|
||||
private:
|
||||
|
||||
|
||||
/**
|
||||
* Write to flash memory, assuming that a write is valid
|
||||
* (using need_erase).
|
||||
*
|
||||
* @param page_address address of memory to write to.
|
||||
* Must be word aligned.
|
||||
* @param buffer address to write from, must be word-aligned.
|
||||
* @param len number of uint32_t words to write.
|
||||
*/
|
||||
void flash_burn(uint32_t* page_address, uint32_t* buffer, int len);
|
||||
|
||||
/**
|
||||
* Write to address in flash, implementing either flash_write (copy
|
||||
* data from buffer), or flash_memset (set all bytes in flash to
|
||||
* provided constant.
|
||||
*
|
||||
* Function ensures that data is written correctly:
|
||||
* - erase page if necessary (using need_erase)
|
||||
* - preserve non-target flash by copying to scratch page.
|
||||
*
|
||||
* flash_write_mem assumes that the provided scratch page is already clean,
|
||||
* i.e. all bytes = 0xFF. The page is erased after use, all bytes reset to
|
||||
* 0xFF.
|
||||
*
|
||||
* @param address in flash to write to
|
||||
* @param from_buffer address to write data from
|
||||
* @param write_byte if writing the same byte to add addressed bytes
|
||||
* @param len number of bytes to write to/copy to
|
||||
* @param m mode to use this function, memset/memcpy.
|
||||
* @param scratch_addr scratch page address (must be page-aligned)
|
||||
* @return non-zero on success, zero on error.
|
||||
*/
|
||||
int flash_write_mem(uint8_t* address, uint8_t* from_buffer,
|
||||
uint8_t write_byte, int len, flash_mode m,
|
||||
uint8_t* scratch_addr);
|
||||
|
||||
/**
|
||||
* Check if an erase is required to write to a region in flash memory.
|
||||
* This is determined if, for any byte:
|
||||
* ~O & N != 0x00
|
||||
* Where O=Original byte, N = New byte.
|
||||
*
|
||||
* @param source to write from
|
||||
* @param flash_address to write to
|
||||
* @param len number of uint8_t to check.
|
||||
* @return non-zero if erase required, zero otherwise.
|
||||
*/
|
||||
int need_erase(uint8_t* source, uint8_t* flash_addr, int len);
|
||||
|
||||
public:
|
||||
/**
|
||||
* Default constructor.
|
||||
*/
|
||||
MicroBitFlash();
|
||||
|
||||
/**
|
||||
* Writes the given number of bytes to the address in flash specified.
|
||||
* Neither address nor buffer need be word-aligned.
|
||||
* @param address location in flash to write to.
|
||||
* @param buffer location in memory to write from.
|
||||
* @length number of bytes to burn
|
||||
* @param scratch_addr if specified, scratch page to use. Use default
|
||||
* otherwise.
|
||||
* @return non-zero on sucess, zero on error.
|
||||
*
|
||||
* Example:
|
||||
* @code
|
||||
* MicroBitFlash flash();
|
||||
* uint32_t word = 0x01;
|
||||
* flash.flash_write((uint8_t*)0x38000, &word, sizeof(word))
|
||||
* @endcode
|
||||
*/
|
||||
int flash_write(uint8_t* address, uint8_t* buffer, int length,
|
||||
uint8_t* scratch_addr);
|
||||
|
||||
/**
|
||||
* Set bytes in [address, address+length] to byte.
|
||||
* Neither address nor buffer need be word-aligned.
|
||||
* @param address to write to
|
||||
* @param byte byte to burn to flash
|
||||
* @param length number of bytes to write to.
|
||||
* @param scratch_addr if specified, scratch page to use. Use default
|
||||
* otherwise.
|
||||
* @return non-zero on success, zero on error.
|
||||
*
|
||||
* Example:
|
||||
* @code
|
||||
* MicroBitFlash flash();
|
||||
* flash.flash_memset((uint8_t*)0x38000, 0x66, 12); //12 bytes to 0x66.
|
||||
* @endcode
|
||||
*/
|
||||
int flash_memset(uint8_t* address, uint8_t byte, int length,
|
||||
uint8_t* scratch_addr);
|
||||
|
||||
/**
|
||||
* Erase bytes in memory, from set address.
|
||||
* @param address to erase bytes from (needn't be word-aligned.
|
||||
* @param length number of bytes to erase (set to 0xFF).
|
||||
* @param scratch_addr if specified, scratch page to use. Use default
|
||||
* otherwise.
|
||||
* @return non-zero on success, zero on error.
|
||||
*
|
||||
* Example:
|
||||
* @code
|
||||
* MicroBitFlash flash();
|
||||
* flash.flash_erase((uint8_t*)0x38000, 10240); //erase a flash page.
|
||||
* @endcode
|
||||
*/
|
||||
int flash_erase_mem(uint8_t* address, int length,
|
||||
uint8_t* scratch_addr);
|
||||
|
||||
/**
|
||||
* Erase an entire page.
|
||||
* @param page_address address of first word of page.
|
||||
*/
|
||||
void erase_page(uint32_t* page_address);
|
||||
};
|
||||
|
||||
#endif
|
|
@ -41,6 +41,8 @@ set(YOTTA_AUTO_MICROBIT-DAL_CPP_FILES
|
|||
"drivers/MicroBitStorage.cpp"
|
||||
"drivers/MicroBitThermometer.cpp"
|
||||
"drivers/TimedInterruptIn.cpp"
|
||||
"drivers/MicroBitFlash.cpp"
|
||||
"drivers/MicroBitFileSystem.cpp"
|
||||
|
||||
"bluetooth/MicroBitAccelerometerService.cpp"
|
||||
"bluetooth/MicroBitBLEManager.cpp"
|
||||
|
|
1404
source/drivers/MicroBitFileSystem.cpp
Normal file
1404
source/drivers/MicroBitFileSystem.cpp
Normal file
File diff suppressed because it is too large
Load diff
277
source/drivers/MicroBitFlash.cpp
Normal file
277
source/drivers/MicroBitFlash.cpp
Normal file
|
@ -0,0 +1,277 @@
|
|||
#include "MicroBitFlash.h"
|
||||
#include "mbed.h" // NVIC
|
||||
|
||||
#define MIN(a,b) ((a)<(b)?(a):(b))
|
||||
|
||||
#define WORD_ADDR(x) (((uint32_t)x) & 0xFFFFFFFC)
|
||||
|
||||
/**
|
||||
* Default Constructor
|
||||
*/
|
||||
MicroBitFlash::MicroBitFlash()
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if an erase is required to write to a region in flash memory.
|
||||
* This is determined if, for any byte:
|
||||
* ~O & N != 0x00
|
||||
* Where O=Original byte, N = New byte.
|
||||
*
|
||||
* @param source to write from
|
||||
* @param flash_address to write to
|
||||
* @param len number of uint8_t to check.
|
||||
* @return non-zero if erase required, zero otherwise.
|
||||
*/
|
||||
int MicroBitFlash::need_erase(uint8_t* source, uint8_t* flash_addr, int len)
|
||||
{
|
||||
// Erase is necessary if for any byte:
|
||||
// O & ~N != 0
|
||||
// Where O = original, and N = new byte.
|
||||
|
||||
for(;len>0;len--)
|
||||
{
|
||||
if((~*(flash_addr++) & *(source++)) != 0x00) return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Erase an entire page
|
||||
* @param page_address address of first word of page
|
||||
*/
|
||||
void MicroBitFlash::erase_page(uint32_t* pg_addr)
|
||||
{
|
||||
|
||||
// Turn on flash erase enable and wait until the NVMC is ready:
|
||||
NRF_NVMC->CONFIG = (NVMC_CONFIG_WEN_Een);
|
||||
while (NRF_NVMC->READY == NVMC_READY_READY_Busy) { }
|
||||
|
||||
// Erase page:
|
||||
NRF_NVMC->ERASEPAGE = (uint32_t)pg_addr;
|
||||
while (NRF_NVMC->READY == NVMC_READY_READY_Busy) { }
|
||||
|
||||
// Turn off flash erase enable and wait until the NVMC is ready:
|
||||
NRF_NVMC->CONFIG = (NVMC_CONFIG_WEN_Ren << NVMC_CONFIG_WEN_Pos);
|
||||
while (NRF_NVMC->READY == NVMC_READY_READY_Busy) { }
|
||||
}
|
||||
|
||||
/**
|
||||
* Write to flash memory, assuming that a write is valid
|
||||
* (using need_erase).
|
||||
*
|
||||
* @param page_address address of memory to write to.
|
||||
* Must be word aligned.
|
||||
* @param buffer address to write from, must be word-aligned.
|
||||
* @param len number of uint32_t words to write.
|
||||
*/
|
||||
void MicroBitFlash::flash_burn(uint32_t* addr, uint32_t* buffer, int size)
|
||||
{
|
||||
|
||||
// Turn on flash write enable and wait until the NVMC is ready:
|
||||
NRF_NVMC->CONFIG = (NVMC_CONFIG_WEN_Wen << NVMC_CONFIG_WEN_Pos);
|
||||
while (NRF_NVMC->READY == NVMC_READY_READY_Busy) {};
|
||||
|
||||
for(int i=0;i<size;i++)
|
||||
{
|
||||
*(addr+i) = *(buffer+i);
|
||||
while (NRF_NVMC->READY == NVMC_READY_READY_Busy) {};
|
||||
}
|
||||
|
||||
// Turn off flash write enable and wait until the NVMC is ready:
|
||||
NRF_NVMC->CONFIG = (NVMC_CONFIG_WEN_Ren << NVMC_CONFIG_WEN_Pos);
|
||||
while (NRF_NVMC->READY == NVMC_READY_READY_Busy) {};
|
||||
}
|
||||
|
||||
/**
|
||||
* Write to address in flash, implementing either flash_write (copy
|
||||
* data from buffer), or flash_memset (set all bytes in flash to
|
||||
* provided constant.
|
||||
*
|
||||
* Function ensures that data is written correctly:
|
||||
* - erase page if necessary (using need_erase)
|
||||
* - preserve non-target flash by copying to scratch page.
|
||||
*
|
||||
* flash_write_mem assumes that the provided scratch page is already clean,
|
||||
* i.e. all bytes = 0xFF. The page is erased after use, all bytes reset to
|
||||
* 0xFF.
|
||||
*
|
||||
* @param address in flash to write to
|
||||
* @param from_buffer address to write data from
|
||||
* @param write_byte if writing the same byte to add addressed bytes
|
||||
* @param len number of bytes to write to/copy to
|
||||
* @param m mode to use this function, memset/memcpy.$a
|
||||
* @param scratch_addr scratch page address (must be page-aligned)
|
||||
* @return non-zero on success, zero on error.
|
||||
*/
|
||||
int MicroBitFlash::flash_write_mem(uint8_t* address, uint8_t* from_buffer,
|
||||
uint8_t write_byte, int length, flash_mode m, uint8_t* scratch_addr)
|
||||
{
|
||||
|
||||
// Check that scratch_addr is aligned on a page boundary.
|
||||
if((uint32_t)scratch_addr & 0x3FF)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
// page number.
|
||||
int page = (uint32_t)address / PAGE_SIZE;
|
||||
|
||||
//page address.
|
||||
uint32_t* pgAddr = (uint32_t*)(page * PAGE_SIZE);
|
||||
|
||||
// offset to write from within page.
|
||||
int offset = (uint32_t)address % PAGE_SIZE;
|
||||
|
||||
// uBit.serial.printf("flash_write to 0x%x, from 0x%x, length: 0x%x\n",
|
||||
// address, from_buffer, length);
|
||||
// uBit.serial.printf(" - offset = %d, pgAddr = 0x%x, page = %d\n",
|
||||
// offset, pgAddr, page);
|
||||
|
||||
uint8_t* writeFrom = (uint8_t*)pgAddr;
|
||||
int start = WORD_ADDR(offset);
|
||||
int end = WORD_ADDR((offset+length+4));
|
||||
int erase = need_erase(from_buffer, address, length);
|
||||
|
||||
// Preserve the data by writing to the scratch page.
|
||||
if(erase)
|
||||
{
|
||||
this->flash_burn((uint32_t*)scratch_addr, pgAddr, PAGE_SIZE/4);
|
||||
this->erase_page(pgAddr);
|
||||
writeFrom = (uint8_t*)scratch_addr;
|
||||
start = 0;
|
||||
end = PAGE_SIZE;
|
||||
}
|
||||
|
||||
uint32_t writeWord = 0;
|
||||
|
||||
for(int i=start;i<end;i++)
|
||||
{
|
||||
int byteOffset = i%4;
|
||||
|
||||
if(i >= offset && i < (offset + length))
|
||||
{
|
||||
if(m == WR_WRITE)
|
||||
{
|
||||
// Write from buffer.
|
||||
writeWord |= (from_buffer[i-offset] << ((byteOffset)*8));
|
||||
}
|
||||
else if(m == WR_MEMSET)
|
||||
{
|
||||
// Write constant.
|
||||
writeWord |= ((uint32_t)write_byte << (byteOffset*8));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
writeWord |= (writeFrom[i] << ((byteOffset)*8));
|
||||
}
|
||||
|
||||
if( ((i+1)%4) == 0)
|
||||
{
|
||||
this->flash_burn(pgAddr + (i/4), &writeWord, 1);
|
||||
writeWord = 0;
|
||||
}
|
||||
}
|
||||
|
||||
// If the scratch page was used, reset it to 0xFF.
|
||||
if(erase)
|
||||
{
|
||||
this->erase_page((uint32_t*)scratch_addr);
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes the given number of bytes to the address in flash specified.
|
||||
* Neither address nor buffer need be word-aligned.
|
||||
* @param address location in flash to write to.
|
||||
* @param buffer location in memory to write from.
|
||||
* @length number of bytes to burn
|
||||
* @param scratch_addr if specified, scratch page to use. Use default
|
||||
* otherwise.
|
||||
* @return non-zero on sucess, zero on error.
|
||||
*
|
||||
* Example:
|
||||
* @code
|
||||
* MicroBitFlash flash();
|
||||
* uint32_t word = 0x01;
|
||||
* flash.flash_write((uint8_t*)0x38000, &word, sizeof(word))
|
||||
* @endcode
|
||||
*/
|
||||
int MicroBitFlash::flash_write(uint8_t* address, uint8_t* from_buffer,
|
||||
int length, uint8_t* scratch_addr)
|
||||
{
|
||||
if(scratch_addr == NULL)
|
||||
{
|
||||
return this->flash_write_mem(address, from_buffer, 0, length,
|
||||
WR_WRITE,(uint8_t*)DEFAULT_SCRATCH_ADDR);
|
||||
}
|
||||
else
|
||||
{
|
||||
return this->flash_write_mem(address, from_buffer, 0, length,
|
||||
WR_WRITE, scratch_addr);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set bytes in [address, address+length] to byte.
|
||||
* Neither address nor buffer need be word-aligned.
|
||||
* @param address to write to
|
||||
* @param byte byte to burn to flash
|
||||
* @param length number of bytes to write to.
|
||||
* @param scratch_addr if specified, scratch page to use. Use default
|
||||
* otherwise.
|
||||
* @return non-zero on success, zero on error.
|
||||
*
|
||||
* Example:
|
||||
* @code
|
||||
* MicroBitFlash flash();
|
||||
* flash.flash_memset((uint8_t*)0x38000, 0x66, 12); //12 bytes to 0x66.
|
||||
* @endcode
|
||||
*/
|
||||
int MicroBitFlash::flash_memset(uint8_t* address, uint8_t write_byte,
|
||||
int length, uint8_t* scratch_addr)
|
||||
{
|
||||
if(scratch_addr == NULL)
|
||||
{
|
||||
return this->flash_write_mem(address, NULL, write_byte, length,
|
||||
WR_MEMSET,(uint8_t*)DEFAULT_SCRATCH_ADDR);
|
||||
}
|
||||
else
|
||||
{
|
||||
return this->flash_write_mem(address, NULL, write_byte, length,
|
||||
WR_MEMSET, scratch_addr);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Erase bytes in memory, from set address.
|
||||
* @param address to erase bytes from (needn't be word-aligned.
|
||||
* @param length number of bytes to erase (set to 0xFF).
|
||||
* @param scratch_addr if specified, scratch page to use. Use default
|
||||
* otherwise.
|
||||
* @return non-zero on success, zero on error.
|
||||
*
|
||||
* Example:
|
||||
* @code
|
||||
* MicroBitFlash flash();
|
||||
* flash.flash_erase((uint8_t*)0x38000, 10240); //erase a flash page.
|
||||
* @endcode
|
||||
*/
|
||||
int MicroBitFlash::flash_erase_mem(uint8_t* address, int length,
|
||||
uint8_t* scratch_addr)
|
||||
{
|
||||
if(scratch_addr == NULL)
|
||||
{
|
||||
return this->flash_write_mem(address, NULL, 0xFF, length,
|
||||
WR_MEMSET,(uint8_t*)DEFAULT_SCRATCH_ADDR);
|
||||
}
|
||||
else
|
||||
{
|
||||
return this->flash_write_mem(address, NULL, 0xff, length,
|
||||
WR_MEMSET, scratch_addr);
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in a new issue