Merge pull request #219 from lancaster-university/microbit-mbfs

Microbit mbfs
This commit is contained in:
Joe Finney 2016-10-10 22:34:44 +01:00 committed by GitHub
commit 4569369a95
8 changed files with 2565 additions and 0 deletions

View File

@ -72,6 +72,35 @@ 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
// Defines where in memory persistent data is stored.
#ifndef KEY_VALUE_STORE_PAGE
#define KEY_VALUE_STORE_PAGE (PAGE_SIZE * (NRF_FICR->CODESIZE - 17))
#endif
#ifndef BLE_BOND_DATA_PAGE
#define BLE_BOND_DATA_PAGE (PAGE_SIZE * (NRF_FICR->CODESIZE - 18))
#endif
#ifndef DEFAULT_SCRATCH_PAGE
#define DEFAULT_SCRATCH_PAGE (PAGE_SIZE * (NRF_FICR->CODESIZE - 19))
#endif
// Address of the end of the current program in FLASH memory.
// This is recorded by the C/C++ linker, but the symbol name varies depending on which compiler is used.
#if defined(__arm)
extern uint32_t Image$$ER_IROM1$$RO$$Limit;
#define FLASH_PROGRAM_END (uint32_t) (&Image$$ER_IROM1$$RO$$Limit)
#else
extern uint32_t __etext;
#define FLASH_PROGRAM_END (uint32_t) (&__etext)
#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.
@ -325,6 +354,26 @@ DEALINGS IN THE SOFTWARE.
#define MICROBIT_DEFAULT_SERIAL_MODE SYNC_SLEEP
#endif
//
// File System configuration defaults
//
//
// Defines the logical block size for the file system.
// Must be a factor of the physical PAGE_SIZE (ideally a power of two less).
//
#ifndef MBFS_BLOCK_SIZE
#define MBFS_BLOCK_SIZE 256
#endif
//
// FileSystem writeback cache size, in bytes. Defines how many bytes will be stored
// in RAM before being written back to FLASH. Set to zero to disable this feature.
// Should be <= MBFS_BLOCK_SIZE.
//
#ifndef MBFS_CACHE_SIZE
#define MBFS_CACHE_SIZE 16
#endif
//
// I/O Options

175
inc/drivers/MicroBitFile.h Normal file
View File

@ -0,0 +1,175 @@
#ifndef MICROBIT_FILE_H
#define MICROBIT_FILE_H
#include "mbed.h"
#include "MicroBitConfig.h"
#include "MicroBitFileSystem.h"
#include "ManagedString.h"
#define READ MB_READ
#define WRITE MB_WRITE
#define READ_AND_WRITE READ | WRITE
#define CREATE MB_CREAT
class MicroBitFile
{
int fileHandle;
ManagedString fileName;
public:
/**
* Creates an instance of a MicroBitFile and creates a new file if required.
*
* @param fileName the name of the file to create/open.
*
* @param mode One of: READ, WRITE, READ_AND_WRITE. Defaults to READ_AND_WRITE.
*/
MicroBitFile(ManagedString fileName, int mode = READ | WRITE | CREATE);
/**
* Seeks to a position in this MicroBitFile instance from the beginning of the file.
*
* @param offset the number of bytes to seek, relative to the beginning of the file.
*
* @return the new seek position, MICROBIT_NOT_SUPPORTED if the current file handle is invalid,
* MICROBIT_INVALID_PARAMETER if the given offset is negative.
*/
int setPosition(int position);
/**
* Returns the current position of the seek head for the current file.
*
* @return the new seek position, MICROBIT_NOT_SUPPORTED if the current file handle is invalid.
*/
int getPosition();
/**
* Writes the given bytes to this MicroBitFile instance at the current position.
*
* @param bytes a pointer to the bytes to write to this file.
*
* @param len the number of bytes to write.
*
* @return The number of bytes written, MICROBIT_NOT_SUPPORTED if the current file
* handle is invalid, MICROBIT_INVALID_PARAMETER if bytes is invalid, or
* len is negative.
*/
int write(const char *bytes, int len);
/**
* Writes the given ManagedString to this MicroBitFile instance at the current position.
*
* @param s The ManagedString to write to this file.
*
* @return The number of bytes written, MICROBIT_NOT_SUPPORTED if the current file
* handle is invalid, MICROBIT_INVALID_PARAMETER if bytes is invalid, or
* len is negative.
*/
int write(ManagedString s);
/**
* Reads a single character from the file at the current position.
*
* @return the character, or MICROBIT_NOT_SUPPORTED if the current file handle
* is invalid.
*/
int read();
/**
* Reads from the file into a given buffer.
*
* @param buffer a pointer to the buffer where data will be stored.
*
* @param size the number of bytes that can be safely stored in the buffer.
*
* @return the number of bytes read, or MICROBIT_INVALID_PARAMETER if buffer is
* invalid, or the size given is less than 0.
*/
int read(char *buffer, int size);
/**
* Reads from the current MicroBitFile into a given buffer.
*
* @param size the number of bytes to be read from the file.
*
* @return a ManagedString containing the requested bytes, oran empty ManagedString
* on error.
*/
ManagedString read(int size);
/**
* Removes this MicroBitFile from the MicroBitFileSystem.
*
* @return MICROBIT_OK on success, MICROBIT_INVALID_PARAMETER if the given filename
* does not exist, MICROBIT_CANCELLED if something went wrong.
*/
int remove();
/**
* Seeks to the end of the file, and appends the given ManagedString to this MicroBitFile instance.
*
* @param bytes a pointer to the bytes to write to this file.
*
* @param len the number of bytes to write.
*
* @return The number of bytes written, MICROBIT_NOT_SUPPORTED if the current file
* handle is invalid or this file was not opened in WRITE mode.
*/
int append(const char *bytes, int len);
/**
* Seeks to the end of the file, and appends the given ManagedString to this MicroBitFile instance.
*
* @param s The ManagedString to write to this file.
*
* @return The number of bytes written, MICROBIT_NOT_SUPPORTED if the current file
* handle is invalid or this file was not opened in WRITE mode.
*/
int append(ManagedString s);
/**
* Determines if this MicroBitFile instance refers to a valid, open file.
*
* @return true if this file is valid, false otherwise.
*
*/
bool isValid();
/**
* Returns the handle used by this MicroBitFile instance.
*
* @note This member function will also inform the user of any errors encountered
* during the opening of this MicroBitFile. At open, the handle is set
* to the return value from MicroBitFileSystem.open().
*/
int getHandle();
/**
* Closes this MicroBitFile instance
*
* @return MICROBIT_OK on success, MICROBIT_NOT_SUPPORTED if the file handle
* is invalid.
*
* @note MicroBitFiles are opened at construction and are implicitly closed at
* destruction. They can be closed explicitly using this member function.
*/
int close();
/**
* Writes back all state associated with the given file to FLASH memory,
* leaving the file open.
*
* @return MICROBIT_OK on success, MICROBIT_NOT_SUPPORTED if the file system has not
* been initialised or if this file is invalid.
*/
int flush();
/**
* Destructor for MicroBitFile. Implicitly closes the current file.
*/
~MicroBitFile();
};
#endif

View File

@ -0,0 +1,499 @@
#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, using the following
// structure.
//
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.
};
//
// A directory is a list of DirectoryEntry structures:
//
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;
// Size of the file table (blocks)
uint16_t fileSystemTableSize;
// Cache of the last block allocated. Used to enable round robin use of blocks.
uint16_t lastBlockAllocated;
// Reference to the root directory of the file system.
DirectoryEntry *rootDirectory;
// Chain of open files.
FileDescriptor *openFiles;
/**
* 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.
* 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 existing 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.
* A round robin algorithm is used 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.
* A round robin algorithm is used 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 type One of MBFS_BLOCK_TYPE_FILE, MBFS_BLOCK_TYPE_DIRECTORY, MBFS_BLOCK_TYPE_FILETABLE.
* Erases and regenerates the given block, recycling any data marked for deletion.
* @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 number.
*
* @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 number.
*
* @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 number.
*
* @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);
/**
* Determines if the given filename is a valid filename for use in MicroBitFileSystem.
* valid filenames must be >0 characters in lenght, NULL temrinated and contain
* only printable characters.
*
* @param name The name of the file to test.
* @return true if the filename is valid, false otherwsie.
*/
bool isValidFilename(const char *name);
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.
*
* @param filename name of the file to open, must contain only printable characters.
* @param flags One or more of MB_READ, MB_WRITE or MB_CREAT.
* @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);
/**
* Writes back all state associated with the given file to FLASH memory,
* leaving the file open.
*
* @param fd file descriptor - obtained with open().
* @return MICROBIT_OK 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);
*
* ...
*
* f.flush(fd);
* @endcode
*/
int flush(int fd);
/**
* Close the specified file handle.
* File handle resources are then made available for future open() calls.
*
* close() must be called to ensure all pending data is written back to FLASH memory.
*
* @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.
*
* @param fd File handle
* @param buffer the buffer from which to write data
* @param size 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 size);
/**
* 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 size 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 size);
/**
* 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"))
* printf("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);
};
#endif

View File

@ -0,0 +1,71 @@
#ifndef MICROBIT_FLASH_H_
#define MICROBIT_FLASH_H_
#include <mbed.h>
#define PAGE_SIZE 1024
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);
/**
* 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(void* address, void* buffer, int length,
void* scratch_addr = NULL);
/**
* Erase an entire page.
* @param page_address address of first word of page.
*/
void erase_page(uint32_t* page_address);
};
#endif

View File

@ -41,6 +41,9 @@ set(YOTTA_AUTO_MICROBIT-DAL_CPP_FILES
"drivers/MicroBitStorage.cpp"
"drivers/MicroBitThermometer.cpp"
"drivers/TimedInterruptIn.cpp"
"drivers/MicroBitFlash.cpp"
"drivers/MicroBitFile.cpp"
"drivers/MicroBitFileSystem.cpp"
"bluetooth/MicroBitAccelerometerService.cpp"
"bluetooth/MicroBitBLEManager.cpp"

View File

@ -0,0 +1,279 @@
#include "MicroBitFile.h"
#include "ErrorNo.h"
/**
* Creates an instance of a MicroBitFile and creates a new file if required.
*
* @param fileName the name of the file to create/open.
*
* @param mode One of: READ, WRITE, READ_AND_WRITE. Defaults to READ_AND_WRITE.
*/
MicroBitFile::MicroBitFile(ManagedString fileName, int mode)
{
this->fileName = fileName;
MicroBitFileSystem* fs;
if(MicroBitFileSystem::defaultFileSystem == NULL)
fs = new MicroBitFileSystem();
else
fs = MicroBitFileSystem::defaultFileSystem;
fileHandle = fs->open(fileName.toCharArray(), mode);
}
/**
* Seeks to a position in this MicroBitFile instance from the beginning of the file.
*
* @param offset the number of bytes to seek, relative to the beginning of the file.
*
* @return the new seek position, MICROBIT_NOT_SUPPORTED if the current file handle is invalid,
* MICROBIT_INVALID_PARAMETER if the given offset is negative.
*/
int MicroBitFile::setPosition(int offset)
{
if(fileHandle < 0)
return MICROBIT_NOT_SUPPORTED;
if(offset < 0)
return MICROBIT_INVALID_PARAMETER;
return MicroBitFileSystem::defaultFileSystem->seek(fileHandle, offset, MB_SEEK_SET);
}
/**
* Returns the current position of the seek head for the current file.
*
* @return the new seek position, MICROBIT_NOT_SUPPORTED if the current file handle is invalid.
*/
int MicroBitFile::getPosition()
{
if(fileHandle < 0)
return MICROBIT_NOT_SUPPORTED;
return MicroBitFileSystem::defaultFileSystem->seek(fileHandle, 0, MB_SEEK_CUR);
}
/**
* Writes the given bytes to this MicroBitFile instance at the current position.
*
* @param bytes a pointer to the bytes to write to this file.
*
* @param len the number of bytes to write.
*
* @return The number of bytes written, MICROBIT_NOT_SUPPORTED if the current file
* handle is invalid, MICROBIT_INVALID_PARAMETER if bytes is invalid, or
* len is negative.
*/
int MicroBitFile::write(const char *bytes, int len)
{
if(fileHandle < 0)
return MICROBIT_NOT_SUPPORTED;
return MicroBitFileSystem::defaultFileSystem->write(fileHandle, (uint8_t*)bytes, len);
}
/**
* Writes the given ManagedString to this MicroBitFile instance at the current position.
*
* @param s The ManagedString to write to this file.
*
* @return The number of bytes written, MICROBIT_NOT_SUPPORTED if the current file
* handle is invalid, MICROBIT_INVALID_PARAMETER if bytes is invalid, or
* len is negative.
*/
int MicroBitFile::write(ManagedString s)
{
return write(s.toCharArray(), s.length());
}
/**
* Reads a single character from the file at the current position.
*
* @return the character, or MICROBIT_NOT_SUPPORTED if the current file handle
* is invalid.
*/
int MicroBitFile::read()
{
if(fileHandle < 0)
return MICROBIT_NOT_SUPPORTED;
char c[1];
int ret = read(c,1);
if(ret < 0)
return ret;
return c[0];
}
/**
* Reads from the file into a given buffer.
*
* @param buffer a pointer to the buffer where data will be stored.
*
* @param size the number of bytes that can be safely stored in the buffer.
*
* @return the number of bytes read, or MICROBIT_INVALID_PARAMETER if buffer is
* invalid, or the size given is less than 0.
*/
int MicroBitFile::read(char *buffer, int size)
{
if(fileHandle < 0)
return MICROBIT_NOT_SUPPORTED;
if(size < 0 || buffer == NULL)
return MICROBIT_INVALID_PARAMETER;
return MicroBitFileSystem::defaultFileSystem->read(fileHandle, (uint8_t*)buffer, size);
}
/**
* Reads from the current MicroBitFile into a given buffer.
*
* @param size the number of bytes to be read from the file.
*
* @return a ManagedString containing the requested bytes, oran empty ManagedString
* on error.
*/
ManagedString MicroBitFile::read(int size)
{
char buff[size + 1];
buff[size] = 0;
int ret = read(buff, size);
if(ret < 0)
return ManagedString();
return ManagedString(buff,ret);
}
/**
* Removes this MicroBitFile from the MicroBitFileSystem.
*
* @return MICROBIT_OK on success, MICROBIT_INVALID_PARAMETER if the given filename
* does not exist, MICROBIT_CANCELLED if something went wrong.
*/
int MicroBitFile::remove()
{
if(fileHandle < 0)
return MICROBIT_NOT_SUPPORTED;
int ret = MicroBitFileSystem::defaultFileSystem->remove(fileName.toCharArray());
if(ret < 0)
return ret;
fileHandle = MICROBIT_NOT_SUPPORTED;
return ret;
}
/**
* Seeks to the end of the file, and appends the given ManagedString to this MicroBitFile instance.
*
* @param bytes a pointer to the bytes to write to this file.
*
* @param len the number of bytes to write.
*
* @return The number of bytes written, MICROBIT_NOT_SUPPORTED if the current file
* handle is invalid or this file was not opened in WRITE mode.
*/
int MicroBitFile::append(const char *bytes, int len)
{
if(fileHandle < 0)
return MICROBIT_NOT_SUPPORTED;
int ret = MicroBitFileSystem::defaultFileSystem->seek(fileHandle, 0, MB_SEEK_END);
if(ret < 0)
return ret;
return write(bytes,len);
}
/**
* Seeks to the end of the file, and appends the given ManagedString to this MicroBitFile instance.
*
* @param s The ManagedString to write to this file.
*
* @return The number of bytes written, MICROBIT_NOT_SUPPORTED if the current file
* handle is invalid or this file was not opened in WRITE mode.
*/
int MicroBitFile::append(ManagedString s)
{
return append(s.toCharArray(), s.length());
}
/**
* Determines if this MicroBitFile instance refers to a valid, open file.
*
* @return true if this file is valid, false otherwise.
*
*/
bool MicroBitFile::isValid()
{
return fileHandle >= 0;
}
/**
* Returns the handle used by this MicroBitFile instance.
*
* @note This member function will also inform the user of any errors encountered
* during the opening of this MicroBitFile. At open, the handle is set
* to the return value from MicroBitFileSystem.open().
*/
int MicroBitFile::getHandle()
{
return fileHandle;
}
/**
* Closes this MicroBitFile instance
*
* @return MICROBIT_OK on success, MICROBIT_NOT_SUPPORTED if the file handle
* is invalid.
*
* @note MicroBitFiles are opened at construction and are implicitly closed at
* destruction. They can be closed explicitly using this member function.
*/
int MicroBitFile::close()
{
if(fileHandle < 0)
return MICROBIT_NOT_SUPPORTED;
int ret = MicroBitFileSystem::defaultFileSystem->close(fileHandle);
if(ret < 0)
return ret;
fileHandle = MICROBIT_NO_RESOURCES;
return ret;
}
/**
* Writes back all state associated with the given file to FLASH memory,
* leaving the file open.
*
* @return MICROBIT_OK on success, MICROBIT_NOT_SUPPORTED if the file system has not
* been initialised or if this file is invalid.
*/
int MicroBitFile::flush()
{
if(fileHandle < 0)
return MICROBIT_NOT_SUPPORTED;
return MicroBitFileSystem::defaultFileSystem->flush(fileHandle);
}
/**
* Destructor for MicroBitFile. Implicitly closes the current file.
*/
MicroBitFile::~MicroBitFile()
{
close();
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,162 @@
#include "MicroBitConfig.h"
#include "MicroBitFlash.h"
#include "ErrorNo.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) {};
}
/**
* 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(void* address, void* from_buffer,
int length, void* scratch_addr)
{
// Ensure that scratch_addr is aligned on a page boundary.
if((uint32_t)scratch_addr & 0x3FF)
return MICROBIT_INVALID_PARAMETER;
// Locate the hardware FLASH page used by this operation.
int page = (uint32_t)address / PAGE_SIZE;
uint32_t* pgAddr = (uint32_t*)(page * PAGE_SIZE);
// offset to write from within page.
int offset = (uint32_t)address % PAGE_SIZE;
uint8_t* writeFrom = (uint8_t*)pgAddr;
int start = WORD_ADDR(offset);
int end = WORD_ADDR((offset+length+4));
int erase = need_erase((uint8_t *)from_buffer, (uint8_t *)address, length);
// Preserve the data by writing to the scratch page.
if(erase)
{
if (!scratch_addr)
return MICROBIT_INVALID_PARAMETER;
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))
{
// Write from buffer.
writeWord |= (((uint8_t *)from_buffer)[i-offset] << ((byteOffset)*8));
}
else
{
writeWord |= (writeFrom[i] << ((byteOffset)*8));
}
if( ((i+1)%4) == 0)
{
this->flash_burn(pgAddr + (i/4), &writeWord, 1);
writeWord = 0;
}
}
return MICROBIT_OK;
}