# HG changeset patch # User Michael Spacefalcon # Date 1388982029 0 # Node ID ef7d7da61c56a0fe4832e8c385d0316cf8f801ce # Parent 3ebe6409e8bcfc74cca61df6596dc111dcd2ee77 FFS code integration: remaining C files imported, but not yet integrated diff -r 3ebe6409e8bc -r ef7d7da61c56 gsm-fw/services/ffs/ffs.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/gsm-fw/services/ffs/ffs.c Mon Jan 06 04:20:29 2014 +0000 @@ -0,0 +1,1314 @@ +/****************************************************************************** + * Flash File System (ffs) + * Idea, design and coding by Mads Meisner-Jensen, mmj@ti.com + * + * ffs public API functions + * + * $Id: ffs.c 1.69.1.24.1.40 Thu, 08 Jan 2004 15:05:23 +0100 tsj $ + * +f ******************************************************************************/ + +#ifndef TARGET +#include "ffs.cfg" +#endif + +#if ((TARGET == 1) || (RIV_ENV== 1)) +#include "ffs/board/task.h" +#endif + +#if (TARGET == 0) +#include +#endif + +#include +#include + +#include "ffs/ffs.h" +#include "ffs/board/core.h" +#include "ffs/board/ffstrace.h" + +/****************************************************************************** + * + ******************************************************************************/ + +extern struct fs_s fs; // defined in core.c + +// These dummy defines and struct are only use to simulate FFS on the +// PC. The ones that is used in target are located in task.h +#if (TARGET == 0) +struct ffs_blocking_s {int x; }; +#define FFS_BLOCKING_CALL_BEGIN() + int result; \ + struct ffs_blocking_s fb; +#define FFS_BLOCKING_CALL_END() +#endif + +/****************************************************************************** + * Create, Read and Write + ******************************************************************************/ + +req_id_t ffs_file_write_b(const char *pathname, void *src, int size, + ffs_options_t option, T_RV_RETURN *cp, + struct ffs_blocking_s *fb) +{ + iref_t i, dir; + char *name; + effs_t error; + int chunk_size, size_remaining, bytes_free; + + tw(tr(TR_FUNC, TrApi, "ffs_file_write('%s', 0x%x, %d, %d) ?\n", + pathname, (int) src, size, option)); + + ttw(ttr(TTrApi, "ffs_file_write('%s', 0x%x, %d, %d) ?" NL, + pathname, (int) src, size, option)); + + // TASKBEGIN effs_t FILE_WRITE(path=pathname, src=src, size=size, value16=option) iref_t i, dir; char *name; effs_t error; int chunk_size, size_remaining, bytes_free; + + if (fs.initerror) + return fs.initerror; + + if (size < 0) + return EFFS_INVALID; + + ffs_query(Q_BYTES_FREE, &bytes_free); + if (bytes_free < size) + return EFFS_NOSPACE; + + chunk_size = (size > fs.chunk_size_max ? fs.chunk_size_max : size); + + if ((i = object_lookup(pathname, &name, &dir)) < 0) { + // Object not found, continue like fcreate() + if (i != EFFS_NOTFOUND) + return i; + + if (!is_open_option(option, FFS_O_CREATE)) + return EFFS_NOTFOUND; + + journal_begin(0); + + if ((dir = object_create(name, src, chunk_size, -dir)) < 0) + return dir; + + journal_end(OT_FILE); + } + + else { + // Object found, continue like fupdate() + if (is_open_option(option, (FFS_O_CREATE)) + && is_open_option(option, (FFS_O_EXCL))) + return EFFS_EXISTS; + + if (get_fdi(i) >= 0) + return EFFS_LOCKED; + + // Even though the ffs architecture allows to have data in + // directory objects, we don't want to complicate matters, so we + // return an error + if (is_object(inode_addr(i), OT_DIR) && !(fs.flags & FS_DIR_DATA)) { + return EFFS_NOTAFILE; + } + + if ((i = is_readonly(i, pathname)) < 0) + return i; + + // Save the segment (if any) in the global variable because this + // global variable will be updated if the inode is going to be + // relocated if an inode_reclaim() is triggeret by the object_create() + fs.i_backup = segment_next(i); + + journal_begin(i); + + if ((dir = object_create(name, src, chunk_size, -dir)) < 0) + return dir; + + // Do not link child - we are replacing the complete file! + fs.link_child = 0; + journal_end(0); + + // If any other segments exist then remove them FIXME: If we get a + // power failure here then the remaining segments wil not be removed + // before inode_reclaim() has been executed + if (fs.i_backup > 0) + if ((error = object_remove(fs.i_backup)) < 0) + return error; + + } + // Save dir in fs.i_backup because this will be updated if some of the + // chunks below trigger a inode reclaim! + fs.i_backup = dir; + + size_remaining = size - chunk_size; + + while (size_remaining > 0) { + + chunk_size = (size_remaining > fs.chunk_size_max ? + fs.chunk_size_max : size_remaining); + + journal_begin(0); + + if ((i = segment_create((char*) src + size - size_remaining, + chunk_size, fs.i_backup)) < 0) + return i; + + journal_end(OT_SEGMENT); + + size_remaining -= chunk_size; + } + + tw(tr_bstat()); + + return EFFS_OK; + + // TASKEND +} + +// Note: ffs_fcreate() is deprecated and should not be used. Use +// ffs_file_write(..., FFS_O_CREATE | FFS_O_EXCL) instead. +effs_t ffs_fcreate(const char *pathname, void *src, int size) +{ + FFS_BLOCKING_CALL_BEGIN(); + + result = ffs_file_write_b(pathname, src, size, FFS_O_CREATE | FFS_O_EXCL, + 0, &fb); + + FFS_BLOCKING_CALL_END(); + + return result; +} + +req_id_t ffs_fcreate_nb(const char *pathname, void *src, int size, + T_RV_RETURN *cp) +{ + return ffs_file_write_b(pathname, src, size, FFS_O_CREATE | FFS_O_EXCL, + cp, 0); +} + +// Note: ffs_fupdate() is deprecated and should not be used. Use +// ffs_file_write(...,FFS_O_TRUNC) instead. +effs_t ffs_fupdate(const char *pathname, void *src, int size) +{ + + FFS_BLOCKING_CALL_BEGIN(); + + result = ffs_file_write_b(pathname, src, size, FFS_O_TRUNC, 0, &fb); + + FFS_BLOCKING_CALL_END(); + + return result; +} + +req_id_t ffs_fupdate_nb(const char *pathname, void *src, int size, + T_RV_RETURN *cp) +{ + return ffs_file_write_b(pathname, src, size, FFS_O_TRUNC, cp, 0); +} + +// Note: ffs_fwrite() is deprecated and should not be used. Use +// ffs_file_write(...,FFS_O_CREATE | FFS_O_TRUNC) instead. +effs_t ffs_fwrite(const char *pathname, void *src, int size) +{ + + FFS_BLOCKING_CALL_BEGIN(); + + result = ffs_file_write_b(pathname, src, size, + FFS_O_CREATE | FFS_O_TRUNC, 0, &fb); + + FFS_BLOCKING_CALL_END(); + + return result; +} + +req_id_t ffs_fwrite_nb(const char *pathname, void *src, int size, + T_RV_RETURN *cp) +{ + return ffs_file_write_b(pathname, src, size, + FFS_O_CREATE | FFS_O_TRUNC, cp, 0); +} + +effs_t ffs_file_write(const char *pathname, void *src, int size, + ffs_options_t option) +{ + + FFS_BLOCKING_CALL_BEGIN(); + + result = ffs_file_write_b(pathname, src, size, option, 0, &fb); + + FFS_BLOCKING_CALL_END(); + + return result; +} + +req_id_t ffs_file_write_nb(const char *pathname, void *src, int size, + ffs_options_t option, + T_RV_RETURN *cp) +{ + return ffs_file_write_b(pathname, src, size, option, cp, 0); +} + +// Note important: ffs_fread() is deprecated and should not be used. Use +// ffs_file_read() instead. +int ffs_fread(const char *name, void *addr, int size) +{ + return ffs_file_read(name, addr, size); +} + + +int ffs_file_read(const char *name, void *addr, int size) +{ + int error; + + tw(tr(TR_BEGIN, TrApi, "file_read('%s', 0x%x, %d) {\n", + name, (int) addr, size)); + + if ((error = ffs_begin()) == EFFS_OK) + { + error = file_read(name, addr, size); + } + + tw(tr(TR_END, TrApi, "} %d\n", error)); + + return ffs_end(error); // number of bytes read +} + +/****************************************************************************** + * Stat, Symlink, Remove and Rename + ******************************************************************************/ + +effs_t ffs_stat(const char *name, struct stat_s *stat) +{ + iref_t i; + + tw(tr(TR_FUNC, TrApi, "ffs_stat('%s', ?) ?\n", name)); + ttw(ttr(TTrApi, "ffs_stat('%s', ?) ?" NL, name)); + + if (name == NULL) + return EFFS_BADNAME; + + if ((i = ffs_begin()) == EFFS_OK) + { + if ((i = object_stat(name, (struct xstat_s*) stat, 0, 0, 0)) > 0) + i = EFFS_OK; + } + + return ffs_end(i); +} + +effs_t ffs_lstat(const char *name, struct stat_s *stat) +{ + iref_t i; + + tw(tr(TR_FUNC, TrApi, "ffs_lstat('%s', ?) ?\n", name)); + ttw(ttr(TTrApi, "ffs_lstat('%s', ?) ?" NL, name)); + + if ((i = ffs_begin()) == EFFS_OK) { + if ((i = object_stat(name, (struct xstat_s*)stat, 1, 0, 0)) > 0) + i = EFFS_OK; + } + + return ffs_end(i); +} + +effs_t ffs_xlstat(const char *name, struct xstat_s *stat) +{ + iref_t i; + + tw(tr(TR_FUNC, TrApi, "ffs_xlstat('%s', ?) ?\n", name)); + ttw(ttr(TTrApi, "ffs_xlstat('%s', ?) ?" NL, name)); + + if ((i = ffs_begin()) == EFFS_OK) { + if ((i = object_stat(name, stat, 1, 0, 1)) > 0) + i = EFFS_OK; + } + + return ffs_end(i); +} + +effs_t ffs_fstat(fd_t fdi, struct stat_s *stat) +{ + iref_t i; + + tw(tr(TR_FUNC, TrApi, "ffs_fstat('%d', ?) ?\n", fdi)); + ttw(ttr(TTrApi, "ffs_fstat('%d', ?) ?" NL, fdi)); + + if ((i = ffs_begin()) == EFFS_OK) { + if ((i = object_stat( 0, (struct xstat_s*) stat, 0, fdi, 0)) > 0) + i = EFFS_OK; + } + + return ffs_end(i); +} + +req_id_t ffs_symlink_b(const char *pathname, const char *src, + T_RV_RETURN *cp, struct ffs_blocking_s *fb) +{ + iref_t i, dir; + char *name; + int size; + + tw(tr(TR_FUNC, TrApi, "ffs_symlink('%s', '%s') ?\n", pathname, src)); + ttw(ttr(TTrApi, "ffs_symlink('%s', '%s') ?" NL, pathname, src)); + + // TASKBEGIN effs_t SYMLINK(path=pathname, src=src) iref_t i, dir; int size; char *name; + + if (fs.initerror) + return fs.initerror; + + if (src == NULL) + return EFFS_BADNAME; + + i = object_lookup(pathname, &name, &dir); + if (i > 0) + return EFFS_EXISTS; + if (i != EFFS_NOTFOUND) + return i; + + size = ffs_strlen(src) + 1; // include null-terminator + + journal_begin(0); + + if ((i = object_create(name, src, size, -dir)) < 0) + return i; + + journal_end(OT_LINK); + + tw(tr_bstat()); + + return EFFS_OK; + + // TASKEND +} + +effs_t ffs_symlink(const char *pathname, const char *actualpath) +{ + FFS_BLOCKING_CALL_BEGIN(); + + result = ffs_symlink_b(pathname, actualpath, 0, &fb); + + FFS_BLOCKING_CALL_END(); + + return result; +} + +req_id_t ffs_symlink_nb(const char *pathname, const char *src, + T_RV_RETURN *cp) +{ + return ffs_symlink_b(pathname, src, cp, 0); +} + +int ffs_readlink(const char *name, char *addr, int size) +{ + int error; + + tw(tr(TR_FUNC, TrApi, "ffs_readlink('%s')\n", name)); + + if ((error = ffs_begin()) == EFFS_OK) + { + error = object_read(name, addr, size, 1); + } + return ffs_end(error); +} + +req_id_t ffs_remove_b(const char *pathname, T_RV_RETURN *cp, + struct ffs_blocking_s *fb) +{ + iref_t i; + + tw(tr(TR_FUNC, TrApi, "ffs_remove('%s')\n", pathname)); + ttw(ttr(TTrApi, "ffs_remove('%s') ?" NL, pathname)); + + // TASKBEGIN effs_t REMOVE(path=pathname) iref_t i; + + if (fs.initerror) + return fs.initerror; + + if ((i = object_lookup_once(pathname, 0, 0)) < 0) + return i; + + if (get_fdi(i) >= 0) + return EFFS_LOCKED; + + if ((i = is_readonly(i, pathname)) < 0) + return i; + + if ((i = object_remove(i)) < 0) + return i; + + tw(tr_bstat()); + + return EFFS_OK; + + // TASKEND +} + +effs_t ffs_remove(const char *pathname) +{ + FFS_BLOCKING_CALL_BEGIN(); + + result = ffs_remove_b(pathname, 0, &fb); + + FFS_BLOCKING_CALL_END(); + + return result; +} + +req_id_t ffs_remove_nb(const char *pathname, T_RV_RETURN *cp) +{ + return ffs_remove_b(pathname, cp, 0); +} + +req_id_t ffs_fcontrol_b(const char *pathname, int8 action, int param, + T_RV_RETURN *cp, struct ffs_blocking_s *fb) +{ + iref_t i; + + tw(tr(TR_FUNC, TrApi, "ffs_fcontrol('%s', %d, 0x%x) ?\n", + pathname, action, param)); + ttw(ttr(TTrApi, "ffs_fcontrol('%s', %d, 0x%x) ?" NL, + pathname, action, param)); + + // TASKBEGIN effs_t FCONTROL(path=pathname, value16=action, size=param) iref_t i; + + if (fs.initerror) + return fs.initerror; + + if (pathname == NULL) + return EFFS_BADNAME; + + if ((i = ffs_strcmp(pathname, "/dev/ffs")) != 0) + { + if ((i = object_lookup_once(pathname, 0, 0)) < 0) + return i; + + if ((i = is_readonly(i, pathname)) < 0) + return i; + } + + if ((i = object_control(i, action, param)) < 0) + return i; + + tw(tr_bstat()); + + return EFFS_OK; + + // TASKEND +} + +effs_t ffs_fcontrol(const char *pathname, int8 action, int param) +{ + FFS_BLOCKING_CALL_BEGIN(); + + result = ffs_fcontrol_b(pathname, action, param, 0, &fb); + + FFS_BLOCKING_CALL_END(); + + return result; +} + +req_id_t ffs_fcontrol_nb(const char *pathname, int8 action, int param, + T_RV_RETURN *cp) +{ + return ffs_fcontrol_b(pathname, action, param, cp, 0); +} + +req_id_t ffs_rename_b(const char *pathname, const char *newname, + T_RV_RETURN *cp, struct ffs_blocking_s *fb) +{ + iref_t i, oldi, dir; + char *name; + struct inode_s *ip; + + tw(tr(TR_FUNC, TrApi, "ffs_rename('%s', '%s') ?\n", pathname, newname)); + ttw(ttr(TTrApi, "ffs_rename('%s', '%s') ?" NL, pathname, newname)); + + // TASKBEGIN effs_t RENAME(path=pathname, src=newname) iref_t i, oldi, dir; char *name; struct inode_s *ip; + + if (fs.initerror) + return fs.initerror; + + // pathname MUST exist, not be open and MUST be writable + if ((oldi = object_lookup_once(pathname, 0, 0)) < 0) + return oldi; + if ((oldi = is_readonly(oldi, pathname)) < 0) + return oldi; + if (get_fdi(oldi) >= 0) + return EFFS_LOCKED; + + journal_begin(oldi); + + if ((i = object_lookup_once(newname, &name, &dir)) < 0) { + if (i != EFFS_NOTFOUND) + return i; + } + else { // newname obj exist + ip = inode_addr(oldi); + if (is_object(ip, OT_FILE)) { // is old obj a file? + if ((i = is_readonly(i, newname)) < 0) + return i; + + ip = inode_addr(i); + if (!is_object(ip, OT_FILE)) // newname MUST be a file + return EFFS_NOTAFILE; + + fs.journal.repli = i; + } + else + return EFFS_EXISTS; + } + + if ((i = object_rename(oldi, name, -dir)) < 0) + return i; + + journal_end(0); + + tw(tr_bstat()); + + return EFFS_OK; + + // TASKEND + +} + +effs_t ffs_rename(const char *pathname, const char *newname) +{ + FFS_BLOCKING_CALL_BEGIN(); + + result = ffs_rename_b(pathname, newname, 0, &fb); + + + FFS_BLOCKING_CALL_END(); + + return result; +} + + +req_id_t ffs_rename_nb(const char *pathname, const char *newname, + T_RV_RETURN *cp) +{ + return ffs_rename_b(pathname, newname, cp, 0); +} + +/****************************************************************************** + * Directory Operations + ******************************************************************************/ + +// All directory operations are more or less similar to unix +// semantics. +req_id_t ffs_mkdir_b(const char *pathname, T_RV_RETURN *cp, + struct ffs_blocking_s *fb) +{ + iref_t i, dir; + char *name; + + tw(tr(TR_FUNC, TrApi, "ffs_mkdir('%s')\n", pathname)); + ttw(ttr(TTrApi, "ffs_mkdir('%s') ?" NL, pathname)); + + // TASKBEGIN effs_t MKDIR(path=pathname) iref_t i, dir; char *name; + + if (fs.initerror) + return fs.initerror; + + i = object_lookup(pathname, &name, &dir); + if (i > 0) + return EFFS_EXISTS; + if (i != EFFS_NOTFOUND) + return i; + + journal_begin(0); + + if ((i = object_create(name, 0, 0, -dir)) < 0) + return i; + + journal_end(OT_DIR); + + tw(tr_bstat()); + + return EFFS_OK; + + // TASKEND +} + +effs_t ffs_mkdir(const char *pathname) +{ + FFS_BLOCKING_CALL_BEGIN(); + + result = ffs_mkdir_b(pathname, 0, &fb); + + FFS_BLOCKING_CALL_END(); + + return result; +} + +req_id_t ffs_mkdir_nb(const char *pathname, T_RV_RETURN *cp) +{ + return ffs_mkdir_b(pathname, cp, 0); +} + +int ffs_opendir(const char *name, struct dir_s *dir) +{ + int i; + + tw(tr(TR_FUNC, TrApi, "ffs_opendir('%s', ?)\n", name)); + ttw(ttr(TTrApi, "ffs_opendir('%s', ?) ?" NL, name)); + + if (dir == NULL) + return EFFS_INVALID; + + if ((i = ffs_begin()) == EFFS_OK) + { + if ((i = dir_open(name)) >= 0) + { + dir->this = i; + dir->index = i; + + // Now count the number of entries in the directory + dir_traverse(-i, (iref_t *) &i); + } + } + return ffs_end(i); +} + +int ffs_readdir(struct dir_s *dir, char *name, int size) +{ + iref_t i; + + tw(tr(TR_BEGIN, TrApi, "ffs_readdir(?, ?, ?) {\n")); + ttw(ttr(TTrApi, "ffs_readdir(?, ?, ?) ?" NL)); + + if (dir == NULL || name == NULL || size < 0) { + tw(tr(TR_END, TrApi, "} %d\n", EFFS_INVALID)); + return EFFS_INVALID; + } + + if ((i = ffs_begin()) == EFFS_OK) + { + if ((i = dir_next(dir->this, dir->index, name, size))) + dir->index = i; + } + tw(tr(TR_END, TrApi, "} ('%s') %d\n", name, i)); + + return ffs_end(i); +} + + +/****************************************************************************** + * Preformat and Format + ******************************************************************************/ + +// Note that we do NOT call ffs_begin() because it will just return +// EFFS_NOFORMAT! +req_id_t ffs_format_b(const char *name, uint16 magic, T_RV_RETURN *cp, + struct ffs_blocking_s *fb) +{ + effs_t i; + + tw(tr(TR_BEGIN, TrApi, "ffs_format('%s', 0x%x) {\n", name, magic)); + ttw(ttr(TTrApi, "ffs_format('%s', 0x%x) ?" NL, name, magic)); + + // TASKBEGIN effs_t FORMAT(path=name, size=magic) iref_t i; + + if (magic != 0x2BAD) { + tw(tr(TR_END, TrApi, "} %d\n", EFFS_INVALID)); + return EFFS_INVALID; + } + + if (name == NULL) { + name = "/ffs-5.54"; + } + + if (*name != '/') { + tw(tr(TR_END, TrApi, "} %d\n", EFFS_BADNAME)); + return EFFS_BADNAME; + } + + if ((i = is_formattable(1)) < 0) { + tw(tr(TR_END, TrApi, "} %d\n", i)); + return i; + } + + if ((i = fs_format(name)) < 0) + return i; + + tw(tr(TR_END, TrApi, "} %d\n", i)); + + tw(tr_bstat()); + + return EFFS_OK; + + // TASKEND +} + +effs_t ffs_format(const char *name, uint16 magic) +{ + FFS_BLOCKING_CALL_BEGIN(); + + result = ffs_format_b(name, magic, 0, &fb); + + FFS_BLOCKING_CALL_END(); + + return result; +} + +req_id_t ffs_format_nb(const char *name, uint16 magic, T_RV_RETURN *cp) +{ + return ffs_format_b(name, magic, cp, 0); +} + +req_id_t ffs_preformat_b(uint16 magic, T_RV_RETURN *cp, + struct ffs_blocking_s *fb) +{ + effs_t i; + + tw(tr(TR_BEGIN, TrApi, "ffs_preformat(0x%x) {\n", magic)); + ttw(ttr(TTrApi, "ffs_preformat(0x%x) ?" NL, magic)); + + // TASKBEGIN effs_t PREFORMAT(path="/", size=magic) effs_t i; + + if (magic != 0xDEAD) { + tw(tr(TR_END, TrApi, "} %d\n", EFFS_INVALID)); + return EFFS_INVALID; + } + + if (!ffs_is_modifiable("")) { + tw(tr(TR_END, TrApi, "} %d\n", EFFS_ACCESS)); + return EFFS_ACCESS; + } + + if ((i = is_formattable(0)) < 0) { + tw(tr(TR_END, TrApi, "} %d\n", i)); + return i; + } + + if ((i = fs_preformat()) < 0) + return i; + + tw(tr(TR_END, TrApi, "} %d\n", i)); + + tw(tr_bstat()); + + return EFFS_OK; + + // TASKEND +} + +effs_t ffs_preformat(uint16 magic) +{ + FFS_BLOCKING_CALL_BEGIN(); + + result = ffs_preformat_b(magic, 0, &fb); + + FFS_BLOCKING_CALL_END(); + + return result; +} + +req_id_t ffs_preformat_nb(uint16 magic, T_RV_RETURN *cp) +{ + return ffs_preformat_b(magic, cp, 0); +} + +/****************************************************************************** + * Open, Read, Write, Close + ******************************************************************************/ +req_id_t ffs_open_b(const char *pathname, ffs_options_t option, + T_RV_RETURN *cp, struct ffs_blocking_s *fb) +{ + iref_t i, dir, dummy; + char *name; + fd_t other_fdi, fdi = 0; + int error; + struct inode_s *ip; + + tw(tr(TR_FUNC, TrApi, "ffs_open('%s', 0x%x) ?\n", pathname, option)); + ttw(ttr(TTrApi, "ffs_open('%s', 0x%x) ?" NL, pathname, option)); + + // TASKBEGIN fd_t OPEN(path=pathname, value16=option) iref_t i, dir, dummy; char *name; fd_t other_fdi, fdi = 0; int error; struct inode_s *ip; + + if (fs.initerror) + return fs.initerror; + + // Minimum one of the flags RD or WR must be specifyed + if (!is_open_option(option, FFS_O_RDONLY) && + !is_open_option(option, FFS_O_WRONLY)) + return EFFS_INVALID; + + // RDONLY must not be combined with any other options if not together + // with WR! + if (is_open_option(option, FFS_O_RDONLY) && + !is_open_option(option, FFS_O_WRONLY)) + if (!(option == FFS_O_RDONLY)) + return EFFS_INVALID; + + for (fdi = 0; fdi < fs.fd_max; fdi++) { // Find free fd + if (fs.fd[fdi].options == 0) { + break; + } + } + + if (fdi >= fs.fd_max) + return EFFS_NUMFD; // Too many open files in system + + i = object_lookup(pathname, &name, &dir); + if (i < 0 && i != EFFS_NOTFOUND) + return i; + + // Open one file several times in RD is okay but only one time in WR + if (i != EFFS_NOTFOUND && (other_fdi = get_fdi(i)) >= 0) { + if (is_open_option(fs.fd[other_fdi].options, FFS_O_WRONLY) || + is_open_option(option, FFS_O_WRONLY)) + return EFFS_LOCKED; + } + + // Init default values + fs.fd[fdi].fp = fs.fd[fdi].size = fs.fd[fdi].wfp = fs.fd[fdi].dirty = 0; + + if (i == EFFS_NOTFOUND) { + if (is_open_option(option, (FFS_O_CREATE | FFS_O_WRONLY))) { + if ((error = is_filename(name)) < 0) + return error; + + // Create segmenthead + journal_begin(0); + + if ((i = object_create(name, 0, 0, -dir)) < 0) + return i; + + journal_end(OT_FILE); + tw(tr_bstat()); + fs.fd[fdi].seghead = i; + } + else + return EFFS_NOTFOUND; + } + else { + if (is_open_option(option, FFS_O_WRONLY)) { + if (is_open_option(option, (FFS_O_CREATE | FFS_O_EXCL))) + return EFFS_EXISTS; + if ((i = is_readonly(i, pathname)) < 0) + return i; + } + ip = inode_addr(i); + + if (is_object(ip, OT_DIR)) + return EFFS_NOTAFILE; + + if (is_open_option(option, FFS_O_TRUNC)) { + // Save the segment (if any) in the global variable because this + // global variable will be updated if the inode is relocated by + // an inode_reclaim() triggeret by object_create() + fs.i_backup = segment_next(i); + + // Replace old seghead with a new and remove all old segments + journal_begin(i); + + if ((i = object_create(name, 0, 0, -dir)) < 0) + return i; + + // Do not link child + fs.link_child = 0; + journal_end(0); + + // If any further segments exist then remove them now + if (fs.i_backup > 0) + if ((error = object_remove(fs.i_backup)) < 0) + return error; + + tw(tr_bstat()); + } + + else { + // Get total size of the file. + fs.fd[fdi].size = segfile_seek(i, INT_MAX, &dummy, 0); + } + + if (is_open_option(option, FFS_O_APPEND)) { + fs.fd[fdi].fp = fs.fd[fdi].size; + } + } + + if (is_open_option(option, FFS_O_WRONLY)) { +#if (TARGET == 1) + if ((fs.fd[fdi].buf = (char *) target_malloc(fs.fd_buf_size)) == 0) + return EFFS_MEMORY; +#else + if ((fs.fd[fdi].buf = malloc(fs.fd_buf_size)) == 0) + return EFFS_MEMORY; +#endif + } + + // Save data in file descriptor + fs.fd[fdi].seghead = i; + fs.fd[fdi].options = option; + + return fdi + FFS_FD_OFFSET; + + // TASKEND +} + +fd_t ffs_open(const char *pathname, ffs_options_t option) +{ + FFS_BLOCKING_CALL_BEGIN(); + + result = ffs_open_b(pathname, option, 0, &fb); + + FFS_BLOCKING_CALL_END(); + + return result; +} + + +req_id_t ffs_open_nb(const char *pathname, ffs_options_t option, + T_RV_RETURN *cp) +{ + return ffs_open_b(pathname, option, cp, 0); +} + +req_id_t ffs_close_b(fd_t fdi, T_RV_RETURN *cp, struct ffs_blocking_s *fb) +{ + int error; + + tw(tr(TR_FUNC, TrApi, "ffs_close(%d) ?\n", fdi)); + ttw(ttr(TTrApi, "ffs_close(%d) ?" NL, fdi)); + + // TASKBEGIN effs_t CLOSE(fdi=fdi) iref_t i; int error; + + if (fs.initerror) + return fs.initerror; + + fdi -= FFS_FD_OFFSET; + + if (!is_fd_valid(fdi)) + return EFFS_BADFD; + + if (is_open_option(fs.fd[fdi].options, FFS_O_WRONLY )) { + if ((error = datasync(fdi)) < 0) + return error; + +#if (TARGET == 1) + target_free(fs.fd[fdi].buf); +#else + free(fs.fd[fdi].buf); +#endif + } + + // Clear all data in file descriptor + fs.fd[fdi].seghead = 0; + fs.fd[fdi].options = fs.fd[fdi].fp = 0; + + return EFFS_OK; + + // TASKEND +} + +effs_t ffs_close(fd_t fdi) +{ + FFS_BLOCKING_CALL_BEGIN(); + + result = ffs_close_b(fdi, 0, &fb); + + FFS_BLOCKING_CALL_END(); + + return result; +} + + +req_id_t ffs_close_nb(fd_t fdi, T_RV_RETURN *cp) +{ + return ffs_close_b( fdi, cp, 0); +} + +req_id_t ffs_write_b(fd_t fdi, void *src, int amount, + T_RV_RETURN *cp, struct ffs_blocking_s *fb) +{ + effs_t error; + iref_t i; + int size_remaining, fp_offset; + int size, size_done; + offset_t chunk_offset; + + tw(tr(TR_BEGIN, TrApi, "ffs_write_b(%d, 0x%x, %d) ?{\n", fdi, src, amount)); + ttw(ttr(TTrApi, "ffs_write_b(%d, 0x%x, %d) ?" NL, fdi, src, amount)); + + // TASKBEGIN int WRITE(fdi=fdi, src=src, size=amount) effs_t error; iref_t i; int size_remaining, fp_offset; int size, size_done; offset_t chunk_offset; + + if (fs.initerror) + return fs.initerror; + + if (amount < 0 || src == NULL) + return EFFS_INVALID; + + fdi -= FFS_FD_OFFSET; + + if (!is_fd_valid(fdi)) + return EFFS_BADFD; + + if (!is_open_option(fs.fd[fdi].options, FFS_O_WRONLY )) + return EFFS_INVALID; // not opened with write flag + + // If FFS_O_APPEEND is specified move fp to eof + if (is_open_option(fs.fd[fdi].options, FFS_O_APPEND )) + fs.fd[fdi].fp = fs.fd[fdi].size; + + // If fp has been moved outside the write buf (by a read) then flush the + // write buffer. + if (fs.fd[fdi].fp >= (fs.fd[fdi].wfp + fs.chunk_size_max)) { + if ((error = datasync(fdi)) < 0) + return error; + } + + size_done = 0; + size_remaining = amount; + + do { + if (!fs.fd[fdi].dirty ) { + // Buffer is not dirty so find the chunk that fp points to. + segfile_seek(fs.fd[fdi].seghead, fs.fd[fdi].fp, &i, + &chunk_offset); + + if ((fs.fd[fdi].size == fs.fd[fdi].fp && + chunk_offset == fs.chunk_size_max) || fs.fd[fdi].size == 0 ) { + // End of file and last chunk is full or empty seghead. + fs.fd[fdi].wfp = fs.fd[fdi].size; + fs.fd[fdi].wch = 0; // Create new chunk (not update). + } + else { + // Work on this chunk and update it later by datasyns + segment_read(i, fs.fd[fdi].buf, fs.fd_buf_size, 0); + fs.fd[fdi].wfp = fs.fd[fdi].fp - chunk_offset; + fs.fd[fdi].wch = i; + } + } + + fs.fd[fdi].dirty = 1; + fp_offset = fs.fd[fdi].fp - fs.fd[fdi].wfp; + + // Fill the buffer to max or just add the rest + size = fs.chunk_size_max - fp_offset; + + if (size_remaining <= fs.chunk_size_max - fp_offset) + size = size_remaining; + + tw(tr(TR_FUNC, TrApi, "Copy data to buffer (size: %d)\n", size)); + + memcpy(fs.fd[fdi].buf + fp_offset, (uint8*)src + size_done, + size); + + fs.fd[fdi].fp += size; + if (fs.fd[fdi].fp > fs.fd[fdi].size) + fs.fd[fdi].size = fs.fd[fdi].fp; + + size_done += size; // FIXME: remove size_done or size_remaining + size_remaining -= size; + + // If wrbuf is full (size = chunk_size_max) so create a chunk. + if (fs.fd[fdi].fp >= (fs.fd[fdi].wfp + fs.chunk_size_max)) { + if ((error = datasync(fdi)) < 0) + return error; + } + } while(size_remaining > 0); + + tw(tr(TR_END, TrApi, "} %d\n", amount)); + return amount; + + // TASKEND +} + +int ffs_write(fd_t fdi, void *src, int amount) +{ + FFS_BLOCKING_CALL_BEGIN(); + + result = ffs_write_b(fdi, src, amount, 0, &fb); + + FFS_BLOCKING_CALL_END(); + + return result; +} + +req_id_t ffs_write_nb(fd_t fdi, void *src, int amount, T_RV_RETURN *cp) +{ + + tw(tr(TR_FUNC, TrApi, "ffs_write_nb(%d, 0x%x, %d) ?\n", fdi, src, amount)); + + return ffs_write_b(fdi, src, amount, cp, 0); +} + +int ffs_read(fd_t fdi, void *src, int size) +{ + int error; + + tw(tr(TR_BEGIN, TrApi, "ffs_read(%d, 0x%x, %d) {\n", fdi, src, size)); + ttw(ttr(TTrApi, "ffs_read(%d, 0x%x, %d) ?" NL, fdi, src, size)); + + if ((error = ffs_begin()) == EFFS_OK) + { + error = stream_read(fdi - FFS_FD_OFFSET, src, size); + } + + tw(tr(TR_END, TrApi, "} %d\n", error)); + return ffs_end(error); // number of bytes read +} + +// The seek function will not allow the file offset to be set beyond the end +// of the existing data in the file or the final offset to be negative. +req_id_t ffs_seek_b(fd_t fdi, int offset, int whence, T_RV_RETURN *cp, + struct ffs_blocking_s *fb) +{ + effs_t error; + int fp_new; + + tw(tr(TR_FUNC, TrApi, "ffs_seek(%d, %d, %d) ?\n", fdi, offset, whence)); + ttw(ttr(TTrApi, "ffs_seek(%d, %d, %d) ?" NL, fdi, offset, whence)); + + // TASKBEGIN int SEEK(fdi=fdi, size=offset, value16=whence) effs_t error; iref_t i; int fp_new, foffset; + + if (fs.initerror) + return fs.initerror; + + fdi -= FFS_FD_OFFSET; + + if (!is_fd_valid(fdi)) + return EFFS_BADFD; + + switch(whence) { + case FFS_SEEK_SET: + if (offset < 0 || offset > fs.fd[fdi].size) + return EFFS_INVALID; + fp_new = offset; + break; + case FFS_SEEK_CUR: + if (fs.fd[fdi].fp + offset < 0 || + fs.fd[fdi].fp + offset > fs.fd[fdi].size) + return EFFS_INVALID; + fp_new = fs.fd[fdi].fp + offset; + break; + case FFS_SEEK_END: + if (offset > 0 || fs.fd[fdi].size < -offset) + return EFFS_INVALID; + fp_new = (offset + fs.fd[fdi].size); + break; + default: + return EFFS_INVALID; + } + + if (!is_offset_in_buf(fp_new, fdi)) + if ((error = datasync(fdi)) < 0) + return error; + + return fs.fd[fdi].fp = fp_new; + + // TASKEND +} + +int ffs_seek(fd_t fdi, int offset, int whence) +{ + FFS_BLOCKING_CALL_BEGIN(); + + result = ffs_seek_b(fdi, offset, whence, 0, &fb); + + FFS_BLOCKING_CALL_END(); + + return result; +} + +req_id_t ffs_seek_nb(fd_t fdi, int offset, int whence, T_RV_RETURN *cp) +{ + return ffs_seek_b(fdi, offset, whence, cp, 0); +} + +req_id_t ffs_truncate_b(const char *path, offset_t length, T_RV_RETURN *cp, + struct ffs_blocking_s *fb) +{ + tw(tr(TR_FUNC, TrApi, "ffs_truncate('%s', %d) \n", path, length)); + ttw(ttr(TTrApi, "ffs_ftruncate('%s', %d) ?" NL, path, length)); + + // TASKBEGIN effs_t TRUNC(path=path, size=length) iref_t i; + + if (fs.initerror) + return fs.initerror; + + if (path == NULL) + return EFFS_BADNAME; + + return object_truncate(path, -1, length); + + // TASKEND +} + +effs_t ffs_truncate(const char *path, offset_t length) +{ + FFS_BLOCKING_CALL_BEGIN(); + + result = ffs_truncate_b(path, length, 0, &fb); + + FFS_BLOCKING_CALL_END(); + + return result; +} + +req_id_t ffs_truncate_nb(const char *path, offset_t length, T_RV_RETURN *cp) +{ + return ffs_truncate_b(path, length, cp, 0); +} + +req_id_t ffs_ftruncate_b(fd_t fdi, offset_t length, T_RV_RETURN *cp, + struct ffs_blocking_s *fb) +{ + tw(tr(TR_FUNC, TrApi, "ffs_ftruncate(%d, %d) \n", fdi, length)); + ttw(ttr(TTrApi, "ffs_ftruncate(%d, %d) ?" NL, fdi, length)); + + // TASKBEGIN effs_t FTRUNC(fdi=fdi, size=length) iref_t i; + + if (fs.initerror) + return fs.initerror; + + return object_truncate(0, fdi - FFS_FD_OFFSET, length); + + // TASKEND +} + +effs_t ffs_ftruncate(fd_t fdi, offset_t length) +{ + FFS_BLOCKING_CALL_BEGIN(); + + result = ffs_ftruncate_b(fdi, length, 0, &fb); + + FFS_BLOCKING_CALL_END(); + + return result; +} + +req_id_t ffs_ftruncate_nb(fd_t fdi, offset_t length, T_RV_RETURN *cp) +{ + return ffs_ftruncate_b(fdi, length, cp, 0); +} + +req_id_t ffs_fdatasync_b(fd_t fdi, T_RV_RETURN *cp, struct ffs_blocking_s *fb) +{ + tw(tr(TR_FUNC, TrApi, "ffs_fdatasync(%d) \n", fdi)); + ttw(ttr(TTrApi, "ffs_fdatasync(%d) ?" NL, fdi)); + + // TASKBEGIN effs_t FDATASYNC(fdi=fdi) effs_t error; + + if (fs.initerror) + return fs.initerror; + + return datasync(fdi - FFS_FD_OFFSET); + + // TASKEND +} + +effs_t ffs_fdatasync(fd_t fdi) +{ + FFS_BLOCKING_CALL_BEGIN(); + + result = ffs_fdatasync_b(fdi, 0, &fb); + + FFS_BLOCKING_CALL_END(); + + return result; +} + +req_id_t ffs_fdatasync_nb(fd_t fdi, T_RV_RETURN *cp) +{ + return ffs_fdatasync_b(fdi, cp, 0); +} + diff -r 3ebe6409e8bc -r ef7d7da61c56 gsm-fw/services/ffs/ffs_env.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/gsm-fw/services/ffs/ffs_env.c Mon Jan 06 04:20:29 2014 +0000 @@ -0,0 +1,205 @@ +/****************************************************************************/ +/* */ +/* File Name: ffs_env.c */ +/* */ +/* Purpose: This file contains definitions for RV manager related */ +/* functions used to get info, start and stop the FFS SWE. */ +/* */ +/* Version 0.1 */ +/* */ +/* Date Modification */ +/* ------------------------------------ */ +/* 10/24/2000 Create */ +/* */ +/* Author Pascal Puel */ +/* */ +/* (C) Copyright 2000 by Texas Instruments Incorporated, All Rights Reserved*/ +/****************************************************************************/ + +#include "board.cfg" +#include "ffs.cfg" +#include "ffs/ffs_env.h" +#include "rvm/rvm_gen.h" +#include "rvm/rvm_priorities.h" +#include "rvm/rvm_use_id_list.h" +#include "ffs/board/task.h" +#include + +extern void ffs_task_init(T_RVF_MB_ID mbid, T_RVF_ADDR_ID addr_id); +T_FFS_TASK_INFO ffs_task_info; + +/* global pointer to the error function */ +static T_RVM_RETURN (*ffs_error_ft)(T_RVM_NAME swe_name, T_RVM_RETURN error_cause, + T_RVM_ERROR_TYPE error_type,T_RVM_STRING error_msg); + +/****************************************************************************** +* Function : ffs_get_info +* +* Description : This function is called by the RV manager to learn +* driver requirements in terms of memory, SWEs... +* +* Parameters : T_RVM_INFO_SWE * swe_info: pointer to the structure to fill +* containing infos related to the driver SWE. +* +* Return : T_RVM_RETURN +* +* History : 0.1 (20-August-2000) +* +* +******************************************************************************/ +T_RVM_RETURN ffs_get_info(T_RVM_INFO_SWE * infoSWE) +{ + + /* SWE info */ + infoSWE->swe_type = RVM_SWE_TYPE_4; + + infoSWE->type_info.type4.swe_use_id = FFS_USE_ID; + memcpy ( (UINT8 *) infoSWE->type_info.type4.swe_name, "FFS", sizeof("FFS") ); + infoSWE->type_info.type4.version = 0; + + infoSWE->type_info.type4.stack_size = FFS_STACK_SIZE; + infoSWE->type_info.type4.priority = RVM_FFS_TASK_PRIORITY; + + /* memory bank info */ + infoSWE->type_info.type4.nb_mem_bank = 1; + + memcpy ((UINT8 *) infoSWE->type_info.type4.mem_bank[0].bank_name, "FFS_PRIM", RVM_NAME_MAX_LEN); + infoSWE->type_info.type4.mem_bank[0].initial_params.size = FFS_MB_PRIM_SIZE; + infoSWE->type_info.type4.mem_bank[0].initial_params.watermark = FFS_MB_PRIM_WATERMARK; + + /* linked SWE info */ + infoSWE->type_info.type4.nb_linked_swe = 0; + + /* generic functions */ + infoSWE->type_info.type4.set_info = ffs_set_info; + infoSWE->type_info.type4.init = ffs_init; + infoSWE->type_info.type4.core = ffs_start; + infoSWE->type_info.type4.stop = ffs_stop; + infoSWE->type_info.type4.kill = ffs_kill; + + /* Set return_path */ + infoSWE->type_info.type4.return_path.callback_func = NULL; + infoSWE->type_info.type4.return_path.addr_id = 0; + + return RV_OK; +} + + +/****************************************************************************** +* Function : ffs_set_info +* +* Description : This function is called by the RV manager to inform +* the driver SWE about task_id, mb_id and error function. +* +* Parameters : - T_RVF_ADDR_ID addr_id: unique path to the SWE. +* - T_RV_RETURN ReturnPath[], array of return path for linked SWE +* - T_RVF_MB_ID mbId[]: array of memory bank ids. +* - callback function to call in case of unrecoverable error. +* +* Return : T_RVM_RETURN +* +* History : 0.1 (20-August-2000) +* +* +******************************************************************************/ +T_RVM_RETURN ffs_set_info(T_RVF_ADDR_ID addr_id, + T_RV_RETURN ReturnPath[], + T_RVF_MB_ID mbId[], + T_RVM_RETURN (*callBackFct)(T_RVM_NAME SWEntName, + T_RVM_RETURN errorCause, + T_RVM_ERROR_TYPE errorType, + T_RVM_STRING errorMsg)) +{ + /* store the pointer to the error function */ + ffs_error_ft = callBackFct ; + + ffs_task_init(mbId[0], addr_id); + + ffs_task_info.addr_id = addr_id; + ffs_task_info.mbid = mbId[0]; + + return RV_OK; +} + + +/****************************************************************************** +* Function : ffs_init +* +* Description : This function is called by the RV manager to initialize the +* ffs SWE before creating the task and calling ffs_start. +* +* Parameters : None +* +* Return : T_RVM_RETURN +* +* History : 0.1 (20-August-2000) +* +* +******************************************************************************/ +T_RVM_RETURN ffs_init(void) +{ + return RV_OK; +} + + +/****************************************************************************** +* Function : ffs_start +* +* Description : This function is called by the RV manager to start the ffs +* SWE, it is the body of the task. +* +* Parameters : None +* +* Return : T_RVM_RETURN +* +* History : 0.1 (20-August-2000) +* +* +******************************************************************************/ +T_RVM_RETURN ffs_start(void) +{ + ffs_task(); + return RV_OK; +} + + +/****************************************************************************** +* Function : ffs_stop +* +* Description : This function is called by the RV manager to stop the ffs SWE. +* +* Parameters : None +* +* Return : T_RVM_RETURN +* +* History : 0.1 (20-August-2000) +* +* +******************************************************************************/ +T_RVM_RETURN ffs_stop(void) +{ + /* other SWEs have not been killed yet, ffs can send messages to other SWEs */ + + return RV_OK; +} + + +/****************************************************************************** +* Function : ffs_kill +* +* Description : This function is called by the RV manager to kill the ffs +* SWE, after the ffs_stop function has been called. +* +* Parameters : None +* +* Return : T_RVM_RETURN +* +* History : 0.1 (20-August-2000) +* +* +******************************************************************************/ +T_RVM_RETURN ffs_kill (void) +{ + /* free all memory buffer previously allocated */ + return RV_OK; +} diff -r 3ebe6409e8bc -r ef7d7da61c56 gsm-fw/services/ffs/ffstrace.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/gsm-fw/services/ffs/ffstrace.c Mon Jan 06 04:20:29 2014 +0000 @@ -0,0 +1,206 @@ +/****************************************************************************** + * Flash File System (ffs) + * Idea, design and coding by Mads Meisner-Jensen, mmj@ti.com + * + * ffs deprecated testing + * + * $Id: ffstrace.c 1.32.1.10 Thu, 18 Dec 2003 10:50:52 +0100 tsj $ + * + ******************************************************************************/ + +#ifndef TARGET +#include "ffs.cfg" +#endif + +#include "ffs/ffs.h" +#include "ffs/board/drv.h" +#include "ffs/board/ffstrace.h" + +#include +#include +#include +#include + +/****************************************************************************** + * LED control + *****************************************************************************/ + +#if (TARGET == 1) + +#include "rvf/rvf_api.h" +#include "rv/rv_general.h" +#include "rvm/rvm_use_id_list.h" + +static uint8 led_state = 0; +static uint8 led_countbits = 0; // number of counter bits +static uint8 led_mask = 0x0; // mask containing the counter bits + +// configure the number of counter bits in the leds +void led_config(unsigned char n) +{ + led_countbits = (n <= 8 ? n : 0); + led_mask = (n <= 8 ? (1 << n) - 1 : 0); +} + +// just set the bits, no checking whatsoever +void led_set(unsigned char n) +{ + *(char *) 0x2800000 = led_state = n; +} + +void led_counter(unsigned char n) +{ + *(char *) 0x2800000 = led_state = led_state & ~led_mask | (n & led_mask); +} + +void led_on(unsigned char n) +{ + *(char *) 0x2800000 = led_state = led_state | (1 << (led_countbits + n)); +} + +void led_off(unsigned char n) +{ + *(char *) 0x2800000 = led_state = led_state & ~(1 << (led_countbits + n)); +} +// FIXME +void led_toggle(unsigned char n) +{ + *(char *) 0x2700000 = led_state = led_state ^ (1 << (led_countbits + n)); +} +#endif + + +/****************************************************************************** + * Target Tracing + *****************************************************************************/ + +#if (TARGET == 1) + +static unsigned int ttr_mask = TTrFatal | TTrTest; + +void ttr_init(unsigned int mask) +{ + ttr_mask = mask | TTrFatal | TTrTest; +} + +void ttr(unsigned int mask, char *format, ...) +{ + va_list args; + static char buf[256]; + + if (ttr_mask & mask) + { + // build string ala tr() then call str() + va_start(args, format); + vsprintf(buf, format, args); + str(mask, buf); + va_end(args); + } +} + +void str(unsigned mask, char *string) +{ + if (ttr_mask & mask) { + rvf_send_trace(string, strlen(string), NULL_PARAM, + RV_TRACE_LEVEL_WARNING, FFS_USE_ID); + rvf_delay(5); + } +} + + +/****************************************************************************** + ** PC side Tracing and logging + *****************************************************************************/ + +#else // (TARGET == 0) + +static int tr_mask; // bitmask of which modules to trace + +static int tr_spaces; // number of spaces to indent per level +static FILE *tr_fd; // unused; file descriptor of file to write traces to + + +void tr_init(unsigned int mask, int spaces, char *filename) +{ + tr_mask = mask; + tr_spaces = spaces; + + if (filename == NULL) { + tr_fd = stdout; + } + else { + if ( !(tr_fd = fopen(filename, "a+b")) ) { + fprintf(stderr, "failed to open logfile: %s for append\n", filename); + exit(1); + } + } +} + +// Trace/Log the printf-like string if abs(level) > tr_level. If level is +// negative, the sematics are the same except that no indentation will occur +void tr(int type, unsigned int mask, char *format, ...) +{ + va_list args; + int indent; + static int indent_level = 0; + static char buf[1024]; + const char spaces[] = + " " + " " + " " + " "; + + if ((mask & tr_mask) == 0) + return; + + // If tracing/debugging trace system + if ((tr_mask & TrTrace) && (type & TR_END)) + fprintf(tr_fd, "END(%d)\n", indent_level); + + if (type & TR_END) + indent_level--; + + indent = (type & TR_NULL ? 0 : indent_level); + + if (strlen(format) > 0) + { + va_start(args, format); + vsprintf(buf, format, args); + + indent = tr_spaces * indent; + if (indent < 0) { + fprintf(tr_fd, "WARNING: tr() indenting too left (%d)\n", + indent_level); + indent = 0; + } + if (indent > sizeof(spaces) - 1) { + fprintf(tr_fd, "WARNING: tr() indenting too right (%d)\n", + indent_level); + indent = sizeof(spaces) - 1; + } + fprintf(tr_fd, "%s%s", &spaces[sizeof(spaces) - 1 - indent], buf); + fflush(tr_fd); + } + if (type & TR_BEGIN) + indent_level++; + + // If tracing/debugging trace system + if ((tr_mask & TrTrace) && (type & TR_BEGIN)) + fprintf(tr_fd, "BEGIN(%d)\n", indent_level); +} + +#endif // (TARGET == 0) + + +/****************************************************************************** + ** Common Tracing and logging + *****************************************************************************/ + +int tr_query(int mask) +{ +#if (TARGET == 1) + return (ttr_mask & mask); +#else + return (tr_mask & mask); +#endif +} diff -r 3ebe6409e8bc -r ef7d7da61c56 gsm-fw/services/ffs/fsck.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/gsm-fw/services/ffs/fsck.c Mon Jan 06 04:20:29 2014 +0000 @@ -0,0 +1,1475 @@ +/****************************************************************************** + * Flash File System (ffs) + * Idea, design and coding by Mads Meisner-Jensen, mmj@ti.com + * + * FFS file system integrity checking, journalling, init and exit + * + * $Id: fsck.c 1.3.1.1.1.33 Thu, 08 Jan 2004 15:05:23 +0100 tsj $ + * + ******************************************************************************/ + +#ifndef TARGET +#include "ffs.cfg" +#endif + +#include +#include + +#include "ffs/ffs.h" +#include "ffs/board/core.h" +#include "ffs/board/drv.h" +#include "ffs/board/ffstrace.h" + +/****************************************************************************** + * Functions + ******************************************************************************/ + +bref_t blocks_fsck(void); +iref_t inodes_fsck(void); + +/****************************************************************************** + * Init and Exit + ******************************************************************************/ + +effs_t ffs_initialize(void) +{ + bref_t b; + struct inode_s *ip; + int i; + + tlw(led_set(0)); + tlw(led_on(LED_INIT)); + ttw(str(TTrInit, "initialize {" NL)); + tw(tr(TR_BEGIN, TrFsck, "ffs_initialize() {\n")); + + // default to non-initialized ffs + fs.root = 0; + fs.debug[0] = fs.debug[1] = fs.debug[2] = fs.debug[3] = 0; + fs.testflags = 0; + + tlw(led_on(LED_DRV_INIT)); + fs.initerror = ffsdrv_init(); // read manufacturer and device ID + tlw(led_off(LED_DRV_INIT)); + if (fs.initerror < 0) { + tlw(led_off(0)); + tw(tr(TR_END, TrFsck, "} %d\n", fs.initerror)); + ttw(ttr(TTrInit, "} %d" NL, fs.initerror)); + return fs.initerror; + } + + for (i = 0; i < 2; i++) + { + tlw(led_on(LED_BLOCKS_FSCK)); + fs.initerror = EFFS_INVALID; + fs.initerror = b = blocks_fsck(); + tlw(led_off(LED_BLOCKS_FSCK)); + if (fs.initerror < 0) { + tlw(led_off(0)); + tw(tr(TR_END, TrFsck, "} %d\n", fs.initerror)); + ttw(ttr(TTrInit, "} %d" NL, fs.initerror)); + return fs.initerror; + } + + tlw(led_on(LED_INODES_FSCK)); + fs.initerror = EFFS_INVALID; + fs.initerror = inodes_fsck(); + tlw(led_off(LED_INODES_FSCK)); + if (fs.initerror < 0) { + tlw(led_off(0)); + tw(tr(TR_END, TrFsck, "} %d\n", fs.initerror)); + ttw(ttr(TTrInit, "} %d" NL, fs.initerror)); + return fs.initerror; + } + + // parse the fs options in the root inode's name + ip = inode_addr(fs.root); + fs_params_init(addr2name(offset2addr(location2offset(ip->location)))); + + if ((fs.initerror = journal_init(fs.ijournal)) == 0) + break; + } + + // Init all file_descriptors to zero + memset(fs.fd, 0, sizeof(struct file_descriptor_s) * fs.fd_max); + + // If blocks_fsck() found a block that needs cleaning, we do it, now + // that all the file system has been initialized. + if (b > 0) { + block_clean(b - 1); + block_free(b - 1); + } + + statistics_init(); + + // In target, we do this before entering the task event loop... + // Otherwise we would in some cases impose a long reboot delay if we did + // it here. If we test in target it is nessesary to call + // blocks_reclaim() anyway because we re-init ffs. + +#if (TARGET == 1) //NOTEME: can this be done in another/better way? +#if (WITH_TFFS == 1) + blocks_reclaim(); +#endif +#else + blocks_reclaim(); +#endif + tlw(led_off(LED_INIT)); + tw(tr(TR_END, TrFsck, "} %d\n", EFFS_OK)); + ttw(str(TTrInit, "} 0" NL)); + + return EFFS_OK; +} + +void fs_params_init(const char *p) +{ + uint8 opt, digit; + uint32 n; + int numdatablocks; + + tw(tr(TR_BEGIN, TrFsck, "fsparams_init('%s') {\n", p)); + + // Compiled default values + fs.filename_max = FFS_FILENAME_MAX; + fs.path_depth_max = FFS_PATH_DEPTH_MAX; + fs.fd_max = FFS_FD_MAX; + fs.journal_size = FFS_JOURNAL_SIZE_IN256THS; + fs.flags = 0; + fs.testflags = 0; + + // Flag that it not has been changed by an input arg. + fs.block_files_max = 0; + + // The default lost bytes percentage of a block before it is reclaimed + // is approx. 90%. + fs.lost_threshold = (256 - 256/10); + + // If we only have two blocks, we cannot make any reclaims and thus we + // have a write-once FFS system. + fs.blocks_free_min = (dev.numblocks > 2 ? 1 : 0); + + // Don't count free and inodes blocks + numdatablocks = dev.numblocks - fs.blocks_free_min - 1; + + // Abselute max number of inodes. + fs.inodes_max = dev.blocksize / sizeof(struct inode_s); + if (fs.inodes_max > FFS_INODES_MAX) + fs.inodes_max = FFS_INODES_MAX; + + // MUST be true: objects_max <= inodes_max - block_files_max, this is do + // to the fact that we always need to have block_files_max number of + // inodes left when we run a data reclaim. + fs.objects_max = fs.inodes_max / 2; + + // Find a suitable chunk_size + if (dev.numblocks*dev.blocksize > 1024*1024) + fs.chunk_size_max = 8192; + else + fs.chunk_size_max = (2048 > (dev.blocksize / 8) + ? (dev.blocksize / 8) + : 2048); + fs.fd_buf_size = fs.chunk_size_max; + + fs.journal_size = fs.journal_size * dev.blocksize / 256; + if (fs.journal_size < FFS_JOURNAL_SIZE_MIN) + fs.journal_size = FFS_JOURNAL_SIZE_MIN; + + // Set it just below the same amount as entries in one journal file + fs.block_files_max = (fs.journal_size / sizeof(struct journal_s) + - FFS_JOURNAL_MARGIN - 2); + + // MUST be true: block_files_max < objects_max / 2. But if we want + // to reach objects_max must block_files_max >= objects_max / number + // of datablocks, however a big block_files_max require higher + // reserved_space. + if (fs.block_files_max > fs.objects_max / 2) + fs.block_files_max = fs.objects_max / 2 - 4; + + // Are we able to reach objects_max? If not then lower the number + if (fs.objects_max > numdatablocks * fs.block_files_max) + fs.objects_max = numdatablocks * fs.block_files_max + 10; + + // Absolute minimum is RESERVED_LOW the rest is 'workspace' which is + // needed to have a reasonable performance. + fs.reserved_space = dev.blocksize / 2 + + numdatablocks * dev.blocksize / 16 + RESERVED_LOW; + + // skip to first char following second slash in name + n = 0; + while (*p) { + if (*p++ == '/') { + n++; + if (n == 2) + break; + } + } + if (n == 2) { + // while still options to process... + while (*p) { + opt = *p++; // save option letter for later + // collect option value... + n = 0; + while ((digit = *p)) { + if (digit >= '0' && digit <= '9') { + n = 10 * n + digit - '0'; + p++; + } + else + break; + } + switch (opt) { + case 'b': dev.numblocks = n; break; + case 'm': fs.blocks_free_min = n; break; + case 'i': fs.inodes_max = n; break; + case 'o': fs.objects_max = n; break; + case 'n': fs.filename_max = n; break; + case 'f': fs.block_files_max = n; break; + case 'd': fs.fd_max = n; break; + case 's': fs.fd_buf_size = n; break; + case 't': fs.lost_threshold = n; break; + case 'z': fs.flags = n; break; + case 'j': fs.journal_size = n; break; + case 'c': fs.chunk_size_max = n; break; + case 'r': fs.reserved_space = n; break; + // d = &fs.path_depth_max; // really necessary? + default: + break; + } + } + } + + // Now recompute a few parameters based on adjusted values. + + // No journal file thuse no reserved space. + if (fs.journal_size == 0) { + fs.block_files_max = fs.objects_max / 2; + fs.reserved_space = 0; + fs.block_files_reserved = 0; + } + + else { + // If journal size is less than minimum must it have been changed by an + // input arg, recalculate. + if (fs.journal_size < FFS_JOURNAL_SIZE_MIN) + fs.journal_size = fs.journal_size * dev.blocksize / 256; + + if (fs.reserved_space < RESERVED_LOW) + fs.reserved_space = fs.reserved_space * dev.blocksize / 256; + + // Only one reserved is needed however we want a margin and set it to two + fs.block_files_reserved = 2; + } + + // Don't count free blocks, inode block, reserved space, block headers + // and the size of one filename. + fs.filesize_max = numdatablocks * dev.blocksize - fs.reserved_space - + numdatablocks * BHEADER_SIZE - FFS_FILENAME_MAX; + + // Furthermore don't count the overhead from each chunk (alignment) + fs.filesize_max -= ((fs.filesize_max / fs.chunk_size_max) * dev.atomsize + + dev.atomsize); + + // NOTEME: chunk_size_min is never used + fs.chunk_size_min = numdatablocks / fs.objects_max; + + tw(tr(TR_FUNC, TrFsck, "dev.numblocks = %d\n", dev.numblocks)); + tw(tr(TR_FUNC, TrFsck, "fs.blocks_free_min = %d\n", fs.blocks_free_min)); + tw(tr(TR_FUNC, TrFsck, "fs.inodes_max = %d\n", fs.inodes_max)); + tw(tr(TR_FUNC, TrFsck, "fs.objects_max = %d\n", fs.objects_max)); + tw(tr(TR_FUNC, TrFsck, "fs.block_files_max = %d\n", fs.block_files_max)); + tw(tr(TR_FUNC, TrFsck, "fs.block_files_reserved = %d\n", fs.block_files_reserved)); + tw(tr(TR_FUNC, TrFsck, "fs.chunk_size_max = %d\n", fs.chunk_size_max)); + tw(tr(TR_FUNC, TrFsck, "fs.filename_max = %d\n", fs.filename_max)); + tw(tr(TR_FUNC, TrFsck, "fs.lost_threshold = %d\n", fs.lost_threshold)); + tw(tr(TR_FUNC, TrFsck, "fs.path_depth_max = %d\n", fs.path_depth_max)); + tw(tr(TR_FUNC, TrFsck, "fs.journal_size = %d\n", fs.journal_size)); + tw(tr(TR_FUNC, TrFsck, "fs.reserved_space = %d\n", fs.reserved_space)); + tw(tr(TR_FUNC, TrFsck, "fs.fd_max = %d\n", fs.fd_max)); + tw(tr(TR_FUNC, TrFsck, "fs.fd_buf_size = 0x%02x\n", fs.fd_buf_size)); + tw(tr(TR_FUNC, TrFsck, "fs.flags = 0x%02x\n", fs.flags)); + tw(tr(TR_END, TrFsck, "}\n")); +} + +// TODO: Finish pending commits/writes. +effs_t ffs_exit(void) +{ + tw(tr(TR_FUNC, TrFsck, "exit() 0\n")); + + + return EFFS_OK; +} + +#if 0 // Not used in this version +// Purely for core internal use; Read a file. +effs_t file_read_int(const char *path, void *src, int size) +{ + if (fs.initerror != EFFS_OK) + return fs.initerror; + + return object_read(path, src, size, 0); +} + +// Purely for core internal use; Update a file. +effs_t file_update(const char *path, void *src, int size) +{ + char *name; + iref_t i, dir; + + if (fs.initerror != EFFS_OK) + return fs.initerror; + + if ((i = object_lookup(path, &name, &dir)) < 0) + return i; + + journal_begin(i); + + if ((i = object_create(name, src, size, -dir)) < 0) + return i; + + journal_end(0); + + return EFFS_OK; +} +#endif + +/****************************************************************************** + * blocks_fsck() + ******************************************************************************/ + +blocksize_t block_used(bref_t b) +{ + blocksize_t used; + uint32 *p, *q; + + tlw(led_toggle(LED_BLOCKS_FSCK)); + + // We search backwards through block to find the last used byte and + // thus the total number of used bytes. Note that this code depends + // on the fact that an erased flash location is 0xFF! + p = (uint32 *) offset2addr(dev.binfo[b].offset); + for (q = p + dev.blocksize/4 - 4; q > p; q -= 4) { + if ( ~(q[0] & q[1] & q[2] & q[3]) ) + break; + } + + if ( ~(q[0] & q[1] & q[2] & q[3]) ) + q += 4; + used = atomalign((char *) q - (char *) p); + + tw(tr(TR_FUNC, TrFsckLow, "ffs_block_used(%d) %d\n", b, used)); + + return used; +} + + +age_t age_distance(age_t x, age_t y) +{ + age_t a = x - y; + + if (a > 0x8000) + a = -a; + + tw(tr(TR_FUNC, TrFsckLow, "age_distance(%d, %d) %d\n", x, y, a)); + + return a; +} + +// For each ffs block, we initialise the basic bstat array information, +// namely the number of used bytes. Also, we locate the inodes block and if +// a previous operation was interrupted by a powerfail, we clean it up. +// +// We return EFFS_OK if all is fine. If a positive integer is returned, it +// denotes a block that needs to be cleaned by block_clean() once FFS +// has been properly intialized (we actually return the block number + 1 +// because otherwise it would clash with EFFS_OK return code). If no inodes +// block is found or another error occurs, we return the error code. +bref_t blocks_fsck(void) +{ + bref_t b, b_to_clean, b_inode_lost; + int age_valid; + age_t age_min, age_max, age_dist, age_dist_min, age_dist_max; + struct block_header_s *bhp; + struct block_header_old_s *obhp; + + ttw(str(TTrInitLow, "blocks_fsck {" NL)); + tw(tr(TR_BEGIN, TrFsck, "blocks_fsck() {\n")); + + // initialize ages to the illegal/unset value + age_min = age_max = age_dist = 0; + + fs.format = 0; + fs.inodes = -1; + fs.newinodes = -1; + b_inode_lost = -1; + b_to_clean = EFFS_OK; + + for (b = 0; b < dev.numblocks; b++) + { + tlw(led_toggle(LED_DRV_INIT)); + + // read block flags from flash + bhp = (struct block_header_s *) offset2addr(dev.binfo[b].offset); + obhp = (struct block_header_old_s *) bhp; + + bstat[b].used = dev.blocksize; + bstat[b].lost = bstat[b].used; + bstat[b].flags = bhp->flags; + bstat[b].objects = 0; + + age_valid = 0; + + if (bhp->magic_low != BLOCK_MAGIC_LOW || + bhp->magic_high != BLOCK_MAGIC_HIGH) { + // The block magic as bad! It *could* be because the flash + // memory map is incorrect or because another application has + // spuriously written to the flash or ... who knows what. First + // we check to see if the reason is that we are dealing with a + // (really) old ffs format version. + if (obhp->magic_low == OLD_BLOCK_MAGIC_LOW && + obhp->magic_high == OLD_FFS_FORMAT_VERSION) { + tw(tr(TR_FUNC, TrFsck, "OLD ")); + fs.format = obhp->magic_high; + // We simulate that all the blocks are data blocks, in order + // to have some well-defined state that preformat() can work + // on. Later we will return EFFS_BADFORMAT and otherwise + // leave everything as it is, *without* modifying anything! + bstat[b].flags = BF_IS_DATA; + } + else { + // Quickly test if block is in empty state. We do not make a + // full check with block_used() because that takes too + // long --- we let preformat() do that. + if (bhp->magic_low == FLASH_NULL16 && + bhp->magic_high == FLASH_NULL16 && + bhp->age == FLASH_NULL16 && + bhp->version == FLASH_NULL16 && + bhp->flags == FLASH_NULL16) + { + bstat[b].used = 0; + bstat[b].lost = 0; + bstat[b].flags = BF_IS_EMPTY; + tw(tr(TR_FUNC, TrFsck, "EMPTY ")); + } + else { + // If the block is not free, it is probably corrupted. + // Thus we reset its age and free it. + tw(tr(TR_FUNC, TrFsck, "magic = 0x%08x\n", + bhp->magic_low | (bhp->magic_high << 16))); + ffsdrv.write_halfword(&bhp->age, 0); + block_free(b); + tw(tr(TR_FUNC, TrFsck, "BAD ")); + } + } + } + else { + fs.format = bhp->version; + age_valid = 1; + + if (!is_block(b, BF_IS_FREE)) { + bstat[b].used = block_used(b); + bstat[b].lost = bstat[b].used - BHEADER_SIZE; + } + + if (is_block(b, BF_IS_FREE)) { + // The only case where we do not call block_used() is + // when the block is truly free. + bstat[b].used = 0; + bstat[b].lost = 0; + tw(tr(TR_FUNC, TrFsck, "FREE ")); + ttw(ttr(TTrInitLow, "FREE" NL)); + + } + else if (is_block(b, BF_IS_DATA)) { + tw(tr(TR_FUNC, TrFsck, "DATA ")); + ttw(ttr(TTrInitLow, "DATA" NL)); + } + else if (is_block(b, BF_IS_CLEANING)) { + // Here we schedule a block_clean(). Note that we can + // and do not execute the block cleaning now, as the info + // that block_clean() needs is not at all ready at this + // point in the initialization. So we set a flag and then + // clean the block at the end of ffs_initialize() + tw(tr(TR_FUNC, TrFsck, "CLEANING ")); + ttw(ttr(TTrInitLow, "CLEANING" NL)); + b_to_clean = b + 1; + } + else if (is_block(b, BF_IS_COPYING)) { + tw(tr(TR_FUNC, TrFsck, "COPYING ")); + ttw(ttr(TTrInitLow, "COPYING" NL)); + fs.newinodes = b; + } + else if (is_block(b, BF_IS_INODES)) { + tw(tr(TR_FUNC, TrFsck, "INODES ")); + ttw(ttr(TTrInitLow, "INODES" NL)); + fs.inodes = b; + } + else if (is_block(b, BF_IS_INODES_LOST)) { + tw(tr(TR_FUNC, TrFsck, "INODESLOST")); + ttw(ttr(TTrInitLow, "INODESLOST" NL)); + b_inode_lost = b; + } + else { + block_free(b); + tw(tr(TR_FUNC, TrFsck, "INVALID ")); + ttw(ttr(TTrInitLow, "INVALID" NL)); + } + } + + tw(tr(TR_NULL, TrFsck, " %2d: (0x%05x) %02x, used = %6d\n", + b, dev.binfo[b].offset, bstat[b].flags & 0xFF, bstat[b].used)); + + if (age_valid) { + if (age_min == 0) { + // Initialize minimum and maximum block ages + age_min = age_max = bhp->age; + tw(tr(TR_FUNC, TrFsckLow, "age_min/max = %d\n", age_min)); + } + else { + age_dist_min = age_distance(bhp->age, age_min); + age_dist_max = age_distance(bhp->age, age_max); + if (age_dist_min > age_dist || + age_dist_max > age_dist) { + if (age_dist_max > age_dist_min) { + age_dist = age_dist_max; + age_min = bhp->age; + tw(tr(TR_FUNC, TrFsckLow, "age_min = %d (dist = %d)\n", + age_min, age_dist)); + } + else { + age_dist = age_dist_min; + age_max = bhp->age; + tw(tr(TR_FUNC, TrFsckLow, "age_max = %d (dist = %d)\n", + age_max, age_dist)); + } + } + } + } + } + tlw(led_off(LED_DRV_INIT)); + tw(tr(TR_FUNC, TrFsck, "age min, max, max-min = %d, %d, %d\n", + age_min, age_max, (uint16) (age_max-age_min))); + // If age_max is untouched is is because all blocks were in the 'Empty' + // state. In this case we let the age be as it is (0xFFFF). + if (age_max == 0) + age_max = age_min = BLOCK_AGE_MAX; + + // Handle age wrap around thus ensuring fs.age_max is set correctly. We + // have to type-cast the whole computation, otherwise it will be + // incorrect. + if ((age_t) (age_max - age_min) > 0x8000) { + age_dist = age_max; + age_max = age_min; + age_min = age_dist; + } + + // save maximum age found for the case of a bad block that is going to + // be reclaimed later on by blocks_reclaim() + fs.age_max = age_max; + + tw(tr(TR_FUNC, TrFsck, "fs.format = 0x%04x\n", fs.format)); + tw(tr(TR_FUNC, TrFsck, "fs.inodes, newinodes = %d, %d\n", + fs.inodes, fs.newinodes)); + ttw(ttr(TTrInit, "fs.inodes, newinodes = %d, %d" NL, + fs.inodes, fs.newinodes)); + tw(tr(TR_FUNC, TrFsck, "age min, max = %d, %d\n", age_min, age_max)); + + // If any blocks were in the EMPTY state, now is the time to bring them + // into the FREE state. Note that we must only do this *after* + // fs.age_max has been initialized. + for (b = 0; b < dev.numblocks; b++) { + if (is_block(b, BF_IS_EMPTY)) { + if ((bstat[b].used = block_used(b)) == 0) + block_preformat(b, 0); + else + block_free(b); + } + } + + if (fs.inodes >= 0) { + // The 'old' inode block is still valid thus we keep it. + if (fs.newinodes >= 0) + // The copying of inodes to the new block was not finished thus + // we free the block + block_free(fs.newinodes); + inodes_set(fs.inodes); + } + else { + // Copying must have been finished + if (fs.newinodes >= 0 && b_inode_lost >= 0) { + // The inode reclaim did finish but currently there is no valid + // inode block thus the operation must be finished by committing + // the new block as the valid inode block. + fs.inodes = b_inode_lost; + block_commit(); + + } + else { + // No old or new Inode block! + tw(tr(TR_END, TrFsck, "} %d\n", EFFS_NOFORMAT)); + ttw(ttr(TTrInitLow, "} %d" NL, EFFS_NOFORMAT)); + return EFFS_NOFORMAT; + } + } + + if ((fs.format >> 8) != (FFS_FORMAT_VERSION >> 8)) { + tw(tr(TR_END, TrFsck, "} %d\n", EFFS_BADFORMAT)); + ttw(ttr(TTrInitLow, "} %d" NL, EFFS_BADFORMAT)); + return EFFS_BADFORMAT; + } + + // FIXME: Insert age sanity check; age distance must not be too big (> 2 + // * FFS_AGE_DISTANCE)? + + tw(tr(TR_END, TrFsck, "} %d\n", b_to_clean)); + ttw(ttr(TTrInitLow, "} %d" NL, b_to_clean)); + + return b_to_clean; +} + +// Set fs.inodes and fs.inodes_addr +void inodes_set(iref_t i) +{ + fs.inodes = i; + fs.inodes_addr = (struct inode_s *) + (offset2addr(dev.binfo[fs.inodes].offset) + + dev.atomsize - sizeof(struct inode_s)); +} + + +/****************************************************************************** + * inodes_fsck() + ******************************************************************************/ + +// Now for each inode in the inodes block, update the bstat array +// information: free, used, objects. Also, locate the root inode. We could +// optimize this a little, because bstat[binodes].used gives an inidication +// of how many inodes are actually present in the system. +iref_t inodes_fsck(void) +{ + iref_t i; + struct inode_s *ip; + char *addr; + bref_t block; + + ttw(str(TTrInitLow, "inodes_fsck {" NL)); + tw(tr(TR_BEGIN, TrFsck, "inodes_fsck() {\n")); + tw(tr(TR_FUNC, TrFsck, "inodes in block %d:\n", fs.inodes)); + + // the fields of the bstat entry for the inodes have the meaning: + // used = total number of used inodes (valid, erased, invalid) + // lost = total number of lost inodes (erased, invalid) + // objects = index of first free inode (used by inode_alloc()) + + fs.root = 0; // default to root inode not found + fs.ijournal = 0; // default to journal file inode not found + bstat[fs.inodes].objects = 1; + bstat[fs.inodes].used = 0; + bstat[fs.inodes].lost = 0; + fs.sequence = 0; // just for debug (fun) + + // we must set some default value for this, so we set it to max possible! + fs.inodes_max = dev.blocksize / sizeof(struct inode_s); + + ip = inode_addr(1); + tw(tr(TR_FUNC, TrFsck, " i addr cld sib seq upd flag size name\n")); + for (i = 1; i < fs.inodes_max; i++, ip++) + { + // just for debug (fun) + if (ip->sequence > fs.sequence) + fs.sequence = ip->sequence; + + // compute block index and total data space occupied + block = offset2block(location2offset(ip->location)); + + // Only scan used inodes. blocks_fsck() accounted all used space as + // also being lost space, so now we subtract from the lost space, + // the space used by valid objects + if (ip->location != FLASH_NULL32) + { + bstat[fs.inodes].used++; + + tw(tr(TR_FUNC, TrFsck, "%3d 0x%05X %3d %3d %4d %3d %s%s%s%s%s%s %6d %s\n", + i, + location2offset(ip->location), + ip->child, ip->sibling, + ip->sequence, ip->updates, + is_object(ip, OT_DIR) ? "d" : "", + is_object(ip, OT_LINK) ? "l" : "", + is_object(ip, OT_FILE) ? "f" : "", + is_object(ip, OT_SEGMENT) ? "s" : "", + is_object(ip, OT_ERASED) ? " " : "", + IS_BIT_SET(ip->flags, OF_READONLY) && !is_object(ip, OT_ERASED) ? + "r" : " ", + ip->size, + // Erased chunks do not have any name so we can not trace erased objects! + (ip->size && !is_object(ip, OT_SEGMENT) && !is_object(ip, OT_ERASED) ? + addr2name(offset2addr(location2offset(ip->location))) : "") + )); + + if (is_object_valid(ip)) { + // This inode is valid, so we account the data space as used + // and the inode as used too. + bstat[block].lost -= ip->size; + bstat[block].objects++; + // test if this is the root inode. store index if it is. + if (!is_object(ip, OT_SEGMENT)) { + addr = addr2name(offset2addr(location2offset(ip->location))); + if (*addr == '/') + fs.root = i; + else if (*addr == '.' && + ffs_strcmp(addr, FFS_JOURNAL_NAME) == 0) { + fs.ijournal = i; + } + } + } + else if (is_object(ip, OT_ERASED)) { + // this inode's data is deleted, so we account the data + // space as used and lost and the inode as lost too. + bstat[fs.inodes].lost++; + } + else { + // This is an invalid object, so we account the data space + // as used and lost and the inode as lost too. NOTEME: error + // what should we do? Perhaps we should record semi-lost + // inodes? Can we safely account for it here if this is an + // object to be recovered because another inode.copied is + // referring to this? Will used/lost etc. be updated + // correctly then? + bstat[fs.inodes].lost++; + tw(tr(TR_NULL, TrFsck, "(invalid = %d)\n", ip->flags & OT_MASK)); + } + } + } + ttw(ttr(TTrInit, "fs.root=%d, journal=%d" NL, fs.root, fs.ijournal)); + tw(tr(TR_END, TrFsck, "} used: %d, lost: %d, root: %d, journal: %d\n", + bstat[fs.inodes].used, bstat[fs.inodes].lost, fs.root, fs.ijournal)); + + fs.sequence++; + + tw(tr_bstat()); + + if (fs.root == 0) { + ttw(ttr(TTrInitLow, "} %d" NL, EFFS_NOFORMAT)); + return EFFS_NOFORMAT; + } + + ttw(str(TTrInitLow, "} 0" NL)); + + return EFFS_OK; +} + + +/****************************************************************************** + * Preformat and format + ******************************************************************************/ + +// Prepare all blocks for fs_format(). Because ffs_is_formattable() has +// already been called prior to this function, we know that no sector erase +// is in progress! The blocks are prepared by putting them into the 'Free' +// state. +effs_t fs_preformat(void) +{ + bref_t b; + + ttw(str(TTrFormat, "preformat {" NL)); + tw(tr(TR_BEGIN, TrFormat, "fs_preformat() {\n")); + + // Mark ffs as being non-formatted from now on. + fs.root = 0; + + // We must initialize bstat[fs.inodes].used and inodes_high, such that + // inodes_reclaim() isn't triggered in reclaim() on the following + // fs_format(). + inodes_set(0); + bstat[fs.inodes].used = 0; + bstat[fs.inodes].lost = 0; + bstat[fs.inodes].objects = 0; + + // While format is in progress, we make FFS inaccessible to other + // functions... + fs.initerror = EFFS_NOFORMAT; + + if (dev.manufact == 0) { + b = EFFS_NODEVICE; + } + else { + for (b = 0; b < dev.numblocks; b++) { + if (is_block(b, BF_IS_EMPTY)) { + if ((bstat[b].used = block_used(b)) == 0) + block_preformat(b, 0); + else + block_free(b); + } + else if (!is_block(b, BF_IS_FREE)) { + block_free(b); + } + } + b = EFFS_OK; + } + + tw(tr(TR_END, TrFormat, "} %d\n", b)); + ttw(ttr(TTrFormat, "} %d" NL, b)); + + return b; +} + +// Preformat a single block thus taking it from the 'Empty' state into +// 'Free' state. +void block_preformat(bref_t b, age_t age) +{ + int set_age_max; + struct block_header_s *bhp = + (struct block_header_s *) offset2addr(dev.binfo[b].offset); + + tw(tr(TR_BEGIN, TrFormat, "fs_block_preformat(%d, %d)\n", b, age)); + + if (age == 0) { + age = fs.age_max; + } + else { + // We schedule an update of fs.age_max. Due to proper handling of + // age wrap-around, we can not actually set it now. + set_age_max = (age == fs.age_max); + age++; + if (age == 0) + age++; + if (set_age_max) { + fs.age_max = age; + tw(tr(TR_FUNC, TrFormat, "new fs.age_max = %d\n", fs.age_max)); + } + } + + ffsdrv.write_halfword(&bhp->age, age); + ffsdrv.write_halfword(&bhp->version, FFS_FORMAT_VERSION); + ffsdrv.write_halfword(&bhp->magic_low, BLOCK_MAGIC_LOW); + ffsdrv.write_halfword(&bhp->magic_high, BLOCK_MAGIC_HIGH); + + bstat[b].flags = BF_IS_EMPTY; + bstat[b].used = 0; + bstat[b].lost = 0; + bstat[b].objects = 0; + + block_flags_write(b, BF_FREE); + + tw(tr(TR_END, TrFormat, "")); +} + +// After preformat() has erased two blocks, this function can be called to +// initialize ffs by writing fs data and metadata. Note that ffs_begin() is +// *not* called before this function in ffs.c. Otherwise we would never +// enter this function because fs.root is zero. NOTEME: this is also a bug +// as this means we risk that this operation is started while an erase (or a +// write) is in progress! How the flash device reacts to this is currently +// unknown. +effs_t fs_format(const char *name) +{ + bref_t i, b; + + ttw(str(TTrFormat, "format {" NL)); + tw(tr(TR_BEGIN, TrFormat, "fs_format('%s') {\n", name)); + + // Initialize file system parameters. It should be safe to change these + // now, as the format cannot fail at this point onwards. + fs_params_init(name); + + // Make the first block be the inodes block + if ((fs.inodes = block_alloc(1, BF_COPYING)) < 0) + return EFFS_AGAIN; + block_flags_write(fs.inodes, BF_INODES); + inodes_set(fs.inodes); + + // Make all block as data blocks except from the free_min and inode block + for (i = 0; i < dev.numblocks - fs.blocks_free_min - 1; i++) + if ((b = block_alloc(0, BF_DATA)) < 0) + return EFFS_AGAIN; + + // Restart object sequencing (debug feature only) + fs.sequence = 0; + + // Create root directory + journal_begin(0); + if ((fs.root = object_create(name, 0, 0, 0)) < 0) { + tw(tr(TR_END, TrFormat, "} %d\n", fs.root)); + return fs.root; + } + journal_commit(OT_DIR); + + if ((fs.ijournal = journal_create(0)) < 0) { + tw(tr(TR_END, TrFormat, "} %d\n", fs.ijournal)); + return fs.ijournal; + } + + fs.initerror = ffs_initialize(); + + ttw(ttr(TTrFormat, "} %d" NL, fs.initerror)); + tw(tr(TR_END, TrFormat, "} %d\n", fs.initerror)); + + return fs.initerror; +} + +// Check if we are ready to preformat (flag = 0) or format (flag = 1) +// +// For a format, we must first ensure no blocks are valid e.g. a preformat +// has already been run. Next, we must ensure we have preformatted all +// blocks e.g. all blocks are in the 'Free' state. This is actually the same +// thing but it sure helps the user because it yields a more precise error +// code when the format fails. In future we might be able to start a format +// when only two blocks have been preformatted, but this is harder because +// we have to make sure not to read from the physical sector that we are +// erasing, and this is exactly what ffs_ffs_initialize() currently does +// (when it is called at the end of format()). +// +// For a preformat, we must ensure an erase is not in progress (because we +// don't know how the device will react to a new erase when an erase is +// currently suspended). +effs_t is_formattable(int8 flag) +{ + bref_t i, free, valid; + effs_t error = EFFS_OK; + + tw(tr(TR_FUNC, TrFormat, "is_formattable() ")); + + // Count the number of valid and free blocks. These numbers will later + // be checked to see if we are really ready for a (pre)format(). Note + // that we *only* read block flags from the bstat[] array. We must not + // read directly from the flash sectors because an erase might be in + // progress! + for (i = 0, free = 0, valid = 0; i < dev.numblocks; i++) { + if (is_block(i, BF_IS_DATA) || is_block(i, BF_IS_INODES)) + valid++; + if (is_block(i, BF_IS_FREE)) + free++; + } + if (flag == 0) { + // In the case of a preformat, ensure an erase is not in + // progress (because we don't know how the device will react to a new + // erase when an erase is currently suspended). + if (dev.state == DEV_ERASE || dev.state == DEV_ERASE_SUSPEND) { + tw(tr(TR_NULL, TrFormat, "(%d)\n", EFFS_AGAIN)); + return EFFS_AGAIN; + } + } + else { + if (valid > 0) + // Ensure we have preformatted prior to a format. + error = EFFS_NOPREFORMAT; + else if (free < dev.numblocks) + // Ensure all blocks are free before a format(). If not, a + // preformat() is currently in progress. + error = EFFS_AGAIN; + } + + tw(tr(TR_NULL, TrFormat, "(%d)\n", error)); + return error; +} + + +/****************************************************************************** + * Journalling + ******************************************************************************/ + +// The following matrix illustrates how the members of an inode change for +// the various (journalled) operations: +// +// | flags | size | loc | child | siblg | dir | oldi | updates +// ---------+-------+------+-----+-------+-------+-----+------+-------- +// create | new | new | new | - | - | ins | n/a | 0 +// fupdate | o | new | new | o | - | ins | del | old+1 +// relocate | o | o | new | o | - | ins | del | old+1 +// fctrl | new | o | o | o | - | ins | del | old+1 +// remove | n/a | n/a | n/a | n/a | n/a | n/a | del | n/a +// +// - = leave empty (0xFFFF) +// ins = insert/append into directory +// o = old value +// +// We don't have to store child member in the journal entry because either +// it is EMPTY (fs.journal.oldi = 0) or it is retrieved from oldip->child. + +// NOTEME: With journalling implemented, object_relocate might be able just +// to make a simple data copy! + +// block_clean() is safe (without journalling), now that only ip->size is +// set to zero. + +// Begin a new journal. Either a fresh object create (oldi == 0) or an +// update of an existing object (oldi == iref of old object) +void journal_begin(iref_t oldi) +{ + tw(tr(TR_FUNC, TrJournal, "journal_begin(%d)\n", oldi)); + + fs.journal.i = 0; + fs.journal.state = JOURNAL_IS_EMPTY; + fs.journal.repli = 0; + fs.link_child = 1; //Default link child in journal_commit() + + if (oldi == 0) { + fs.journal.flags = 0xFF; + fs.journal.diri = 0; + fs.journal.oldi = 0; + fs.journal.location = 0; + fs.journal.size = 0; + } + else { + struct inode_s *oldip = inode_addr(oldi); + fs.journal.flags = oldip->flags; + fs.journal.diri = oldi; + fs.journal.oldi = oldi; + fs.journal.location = oldip->location; + fs.journal.size = oldip->size; + } +} + +// NOTEME: We have compressed the macro code because it will NOT compile on +// Unix otherwise. So until we find out why, we use this as a work-around. +#if (FFS_TEST == 1) +#define JOURNAL_TEST(testcase, text) if (fs.testflags == testcase) { tw(tr(TR_END, TrJournal, "} (" text ")\n")); return; } +#else +#define JOURNAL_TEST(testcase, text) +#endif + +// NOTEME: Should we empty journal file when we are anyway relocating it in +// data_reclaim()? +void journal_end(uint8 type) +{ + struct inode_s *ip = inode_addr(fs.ijournal); + struct journal_s *addr = (struct journal_s *) + offset2addr(location2offset(ip->location) + fs.journal_pos); + + tw(tr(TR_BEGIN, TrJournal, "journal_end(0x%x) {\n", type)); + tw(tr(TR_FUNC, TrJournal, "journal_pos = 0x%04x (%d)\n", fs.journal_pos, + (fs.journal_pos - JOURNAL_POS_INITIAL) / sizeof(struct journal_s))); + + // If this is a create, set the object type + if (type != 0 && fs.journal.oldi == 0) + fs.journal.flags = (fs.journal.flags & OF_MASK) | type; + + // If there is no journal file, we can do without it, although we + // certainly don't like it! + if (fs.ijournal == 0) { + journal_commit(0); + tw(tr(TR_END, TrJournal, "} No jounal file\n")); + return; + } + + JOURNAL_TEST(JOURNAL_TEST_EMPTY, "Oops in JOURNAL_IS_EMPTY"); + + // Write RAM journal to journal file. + if (fs.journal.state == (uint8) JOURNAL_IS_EMPTY) { + fs.journal.state = JOURNAL_IS_WRITING; + ffsdrv.write(addr, &fs.journal, sizeof(fs.journal)); + } + + JOURNAL_TEST(JOURNAL_TEST_WRITING, "Oops in JOURNAL_IS_WRITING"); + + // Advance journal file's state + if (fs.journal.state == (uint8) JOURNAL_IS_WRITING) { + fs.journal.state = JOURNAL_IS_READY; + ffsdrv_write_byte(&addr->state, fs.journal.state); + } + + JOURNAL_TEST(JOURNAL_TEST_READY, "Oops in JOURNAL_IS_READY"); + + journal_commit(0); + + JOURNAL_TEST(JOURNAL_TEST_COMMITTING, "Oops in JOURNAL_TEST_COMMITTING"); + JOURNAL_TEST(JOURNAL_TEST_COMMITTED, "Oops in JOURNAL_COMMITTED"); + + // Advance journal file's state + ffsdrv_write_byte(&addr->state, JOURNAL_IS_DONE); + + JOURNAL_TEST(JOURNAL_TEST_DONE, "Oops in JOURNAL_IS_DONE"); + + // Advance journal + fs.journal_pos += sizeof(struct journal_s); + + // Unless we are currently relocating the journal file itself, check if + // journal file is near full and relocate it if it is. + if (fs.journal_pos >= fs.journal_size - FFS_JOURNAL_MARGIN * + sizeof(struct journal_s) && fs.journal.oldi != fs.ijournal) { + tw(tr(TR_FUNC, TrJournal, "Journal file (near) full!\n")); + journal_create(fs.ijournal); + } + + // Check if we have just committed the journal file itself + if (fs.journal.oldi == fs.ijournal) { + fs.journal_pos = JOURNAL_POS_INITIAL; + fs.ijournal = fs.journal.i; + tw(tr(TR_FUNC, TrJournal, "Journal file re-created, fs.ijournal = %d\n", + fs.ijournal)); + } + tw(tr(TR_END, TrJournal, "}\n")); +} + +// Write contents of fs.journal to FFS meta data (inodes). Note that we do +// NOT traverse ip->copied as we used to do in the old +// object_update_commit(). Also, we do not check if object has been +// erased after traversing ip->copied. All this code has been removed +// because we will very soon have full callback functionality and thus the +// code is redundant. +void journal_commit(uint8 type) +{ + struct inode_s *ip = inode_addr(fs.journal.i); + struct inode_s *oldip = inode_addr(fs.journal.oldi); + struct inode_s *dp; + bref_t b; + + tw(tr(TR_BEGIN, TrJournal, "journal_commit(%d) {\n", type)); + tw(tr(TR_FUNC, TrJournal, "i = %d\n", fs.journal.i)); + ttw(ttr(TTrObj, "jc(){" NL)); + + if (fs.journal.i) + { + // If this is a create, set the object type + if (type != 0 && fs.journal.oldi == 0) + fs.journal.flags = (fs.journal.flags & OF_MASK) | type; + + tw(tr(TR_FUNC, TrJournal, "loc = 0x%04x, size = %d\n", + fs.journal.location, fs.journal.size)); + ffsdrv.write((uint32 *) &ip->location, (uint32 *) &fs.journal.location, sizeof(location_t)); + ffsdrv.write_halfword((uint16 *) &ip->size, fs.journal.size); + + if (fs.journal.oldi != 0 && fs.link_child != 0) + // If this is an update, we copy the child member from old + // inode. We must do this before we validate the new object, + // otherwise an intermediate readdir() will detect an empty + // directory! + ffsdrv.write_halfword((uint16*) &ip->child, oldip->child); + + tw(tr(TR_FUNC, TrJournal, "seq = %d\n", fs.sequence)); + // We must check if sequence is already written because if this + // commit was inititiated by journal_init(), we don't know exactly + // what was written + if (ip->sequence == FLASH_NULL16) + ffsdrv.write_halfword(&ip->sequence, fs.sequence++); + if (fs.journal.oldi == 0) + ffsdrv.write_halfword(&ip->updates, 0); + else + ffsdrv.write_halfword(&ip->updates, oldip->updates + 1); + + JOURNAL_TEST(JOURNAL_TEST_COMMITTING, "Oops in JOURNAL_TEST_COMMITTING") + + // Insert object into directory structure. We must do this before + // deleting old object, otherwise an intermediate readdir() will + // fail with EFFS_NOTFOUND. Note that when the root directory is + // created, fs.journal.diri is zero --- thus the test! + if (fs.journal.diri != 0) { + tw(tr(TR_FUNC, TrJournal, "diri = %d ", fs.journal.diri)); + if (fs.journal.diri < 0) { + tw(tr(TR_NULL, TrJournal, "child\n")); + dp = inode_addr(-fs.journal.diri); + ffsdrv.write_halfword((uint16 *) &dp->child, fs.journal.i); + } + else { + tw(tr(TR_NULL, TrJournal, "sibling\n")); + dp = inode_addr(fs.journal.diri); + ffsdrv.write_halfword((uint16 *) &dp->sibling, fs.journal.i); + } + } + + // The new object is validated before the old object is deleted. + // This is in order to avoid an interrupting stat or read operation + // to fail with EFFS_NOTFOUND + tw(tr(TR_FUNC, TrJournal, "flags = 0x%02x\n", fs.journal.flags)); + ffsdrv_write_byte(&ip->flags, fs.journal.flags); + + // Update bstat[] appropriately + b = offset2block(location2offset(ip->location)); + bstat[b].objects++; + tw(tr(TR_FUNC, TrJournal, "bstat[%d].objects = %d\n", b, bstat[b].objects)); + } + + tw(tr(TR_FUNC, TrJournal, "oldi = %d\n", fs.journal.oldi)); + if (fs.journal.oldi != 0) + { + // If this is an update or an erase, we erase the old object + ffsdrv_write_byte(&oldip->flags, OT_ERASED); + + // Update bstat according to deletion of the old object. + b = offset2block(location2offset(oldip->location)); + bstat[b].objects--; + tw(tr(TR_FUNC, TrJournal, "bstat[%d].objects = %d\n", b, bstat[b].objects)); + + // If we moved the data (all cases, except fcontrol), update lost + if (fs.journal.location != oldip->location) + bstat[b].lost += oldip->size; + + bstat[fs.inodes].lost++; + + // If we renamed a file to an existing filename, remove the replaced file. + if (fs.journal.repli > 0) + object_remove(fs.journal.repli); // Ignore error! + } + + tw(tr(TR_END, TrJournal, "}\n")); + ttw(ttr(TTrObj, "}" NL)); +} + +// Save the current journal into "old" journal. We need this because an +// object_create() can call data_reclaim() which can call object_relocate() +// which uses the journal system. +int journal_push(void) +{ + memcpy(&fs.ojournal, &fs.journal, sizeof(struct journal_s)); + fs.journal_depth++; + if (fs.journal_depth > 1) { + tw(tr(TR_FUNC, TrAll, "FATAL: journal_push() to depth %d\n", + fs.journal_depth)); + return -1; + } + + tw(tr(TR_FUNC, TrJournal, "journal_push() to depth %d\n", + fs.journal_depth)); + + return EFFS_OK; +} + +// Recall "old" journal into current journal +int journal_pop(void) +{ + tw(tr(TR_FUNC, TrJournal, "journal_pop() from depth %d\n", + fs.journal_depth)); + + fs.journal_depth--; + if (fs.journal_depth < 0) { + tw(tr(TR_FUNC, TrAll, "FATAL: journal_pop() to depth %d\n", + fs.journal_depth)); + return -1; + } + memcpy(&fs.journal, &fs.ojournal, sizeof(struct journal_s)); + + return EFFS_OK; +} + +// Initialize the journalling system. Create journal file if it not already +// exist. Commit/write pending journal if such exists --- return 1 in that +// case. Otherwise, if journal file is clean (no journals pending) and all +// is fine, return EFFS_OK. +effs_t journal_init(iref_t i) +{ + int j; + struct inode_s *ip = inode_addr(i); + struct journal_s *addr; + + if (i == 0) { + // Journal file does not exist, so create it + if ((i = journal_create(0)) <= 0) { + fs.ijournal = 0; + return i; + } + } + + fs.journal_depth = 0; + fs.journal_pos = JOURNAL_POS_INITIAL; + + addr = (struct journal_s *) + offset2addr(location2offset(ip->location) + fs.journal_pos); + + tw(tr(TR_BEGIN, TrJournal, "journal_init(%d) {\n", i)); + + fs.ijournal = i; + + // Search for first non-completed journal entry. + for (j = 0; /* FIXME: limit to end of journal */; j++, addr++) { + if (addr->state != (uint8) JOURNAL_IS_DONE) + break; + } + tw(tr(TR_FUNC, TrJournal, "entry %d is in state 0x%x\n", j, addr->state)); + + fs.journal_pos += j * sizeof(fs.journal); + i = EFFS_OK; + + if (addr->state == (uint8) JOURNAL_IS_EMPTY) { + tw(tr(TR_FUNC, TrJournal, "Last journal is in EMPTY state\n")); + // Journal file is proper, so just record position + } + else if (addr->state == (uint8) JOURNAL_IS_READY) { + // Copy the entry into fs.journal. + tw(tr(TR_FUNC, TrJournal, "Last journal is in READY state\n")); + memcpy(&fs.journal, addr, sizeof(fs.journal)); + journal_end(0); + i = 1; + } + else { + // Journal entry wasn't finished, so just ignore it after updating + // its state to JOURNAL_IS_DONE. + tw(tr(TR_FUNC, TrJournal, "Last journal is between EMPTY and READY\n")); + ffsdrv_write_byte(&addr->state, JOURNAL_IS_DONE); + fs.journal_pos += sizeof(fs.journal); + } + + if (ip->size != fs.journal_size + atomalign(sizeof(FFS_JOURNAL_NAME) + 1)) { + tw(tr(TR_FUNC, TrJournal, "Wrong journal size, create new\n")); + // Journal size do not match default size, so create new. This + // should only happen if we use an old FFS image with a newer FFS + // version. + if ((i = journal_create(fs.ijournal)) <= 0) { + fs.ijournal = 0; + return i; + } + } + + tw(tr(TR_FUNC, TrJournal, "journal_pos = 0x%04x\n", fs.journal_pos)); + tw(tr(TR_END, TrJournal, "} %d\n", i)); + + return i; +} + +// Create the journal file from scratch or relocate an existing one. It is +// marked read-only just for clarity --- it cannot be deleted anyway! +// fs_format() calls this function. Note that no data are written in +// object_create() because the journal file is handled specially in that +// function. +iref_t journal_create(iref_t oldi) +{ + iref_t i; + + tw(tr(TR_BEGIN, TrJournal, "journal_create(%d) {\n", oldi)); + tw(tr(TR_FUNC, TrJournal, "journal file size = %d\n", fs.journal_size)); + + if (fs.journal_size == 0) { + tw(tr(TR_FUNC, TrJournal, "Journal file creation aborted because fs.journal_size = 0 (No journal file wanted)\n")); + tw(tr(TR_END, TrJournal, "} %d\n", 0)); + return 0; + } + + // If we are working on a write-once file system, we do not need a + // journal. + if (fs.blocks_free_min == 0) { + tw(tr(TR_FUNC, TrJournal, "Journal file creation aborted because fs.blocks_free_min = 0 (write-once system)\n")); + tw(tr(TR_END, TrJournal, "} %d\n", 0)); + return 0; + } + + journal_begin(oldi); + + i = object_create(FFS_JOURNAL_NAME, 0, fs.journal_size, -fs.root); + if (i < 0) { + tw(tr(TR_END, TrJournal, "} %d\n", i)); + return i; + } + fs.journal.flags = BIT_SET(fs.journal.flags, OF_READONLY); + + // commit the creation or relocation + if (oldi != 0) + journal_end(0); + else { + journal_commit(OT_FILE); + fs.journal_pos = JOURNAL_POS_INITIAL; + } + + tw(tr(TR_END, TrJournal, "} %d\n", i)); + + return i; +} + +/****************************************************************************** + * FFS Begin and End + ******************************************************************************/ + +// The following two functions should surround the code of every API +// function in ffs.c (except preformat and format). The functions +// ensures that the operation about to be executed can be made without +// race-conditions or other problems. +#if (TARGET == 0) +int debug_suspend = 0; +#endif + + +// Check if ffs has been initialized. Suspend an erase operation. +effs_t ffs_begin(void) +{ +#if (TARGET == 0) + if (debug_suspend > 0) { + tw(tr(TR_FUNC, TrAll, "FATAL: Previous erase_suspend was not resumed\n")); + return EFFS_CORRUPTED; + } +// tw(tr(TR_FUNC, TrHelper, "Set debug_suspend\n")); + debug_suspend = 1; +#endif + + if (fs.initerror != EFFS_OK) + return fs.initerror; + + // Suspend an erase in progress (only applicable if we are using a + // multi-bank device driver) + if (dev.state == DEV_ERASE) { + ffsdrv.erase_suspend(); + } + else if (dev.state == DEV_WRITE) { + ffsdrv.write_end(); + } + + return EFFS_OK; +} + +// Resume an erase operation that was in progress. +int ffs_end(int error) +{ +#if (TARGET == 1) + // Resume an erase in progress (only applicable if we are using a + // multi-bank device driver) + if (dev.state == DEV_ERASE_SUSPEND) { + ffsdrv.erase_resume(); + } +#else + debug_suspend = 0; +#endif + + return error; +} + +/****************************************************************************** + * FFS Statistics functions + ******************************************************************************/ + +// Not implemented: +int statistics_file_create(void) +{ + return 0; +} + +// Not implemented: +// Rewrite the statistics file if it exists. Otherwise return error +// code. The function is called after each data and inodes reclaim (after +// writing the file that provoked the reclaim). +int statistics_write(void) +{ + return 0; +} + +// Read the statistics file if it exists. Otherwise reset all statistics to +// zero and set the magic. This function is called from ffs_init(). +void statistics_init(void) +{ + memset(&stats, 0, sizeof(struct ffs_stats_s)); +} + +void statistics_update_drec(int valid, int lost, int candidate) +{ + unsigned int old; + + switch (candidate) { + case MOST_LOST: stats.drec.most_lost++; break; + case MOST_UNUSED: stats.drec.most_unused++; break; + case YOUNGEST: stats.drec.youngest++; break; + } + + // Increment Most Significant Word if overflow is detected + old = stats.drec.valid[0]; + stats.drec.valid[0] += valid; + if (old > stats.drec.valid[0]) + stats.drec.valid[1]++; + + old = stats.drec.lost[0]; + stats.drec.lost[0] += lost; + if (old > stats.drec.lost[0]) + stats.drec.lost[1]++; +} + +void statistics_update_irec(int valid, int lost) +{ + stats.irec.num++; + stats.irec.valid += valid; + stats.irec.lost += lost; +} + diff -r 3ebe6409e8bc -r ef7d7da61c56 gsm-fw/services/ffs/reclaim.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/gsm-fw/services/ffs/reclaim.c Mon Jan 06 04:20:29 2014 +0000 @@ -0,0 +1,883 @@ +/****************************************************************************** + * Flash File System (ffs) + * Idea, design and coding by Mads Meisner-Jensen, mmj@ti.com + * + * FFS core reclaim functionality + * + * $Id: reclaim.c 1.4.1.28 Thu, 08 Jan 2004 15:05:23 +0100 tsj $ + * + ******************************************************************************/ + +#ifndef TARGET +#include "ffs.cfg" +#endif + +#include "ffs/ffs.h" +#include "ffs/board/core.h" +#include "ffs/board/drv.h" +#include "ffs/board/ffstrace.h" + +#include // rand() + +/****************************************************************************** + * Inodes Reclaim + ******************************************************************************/ + +void inodes_recurse(iref_t i) +{ + iref_t pi; + struct inode_s *ip, *newip; + + tw(tr(TR_BEGIN, TrReclaimLow, "inodes_recurse(%d) {\n", i)); + + ip = inode_addr(i); + newip = (struct inode_s *) offset2addr(dev.binfo[fs.newinodes].offset) + i; + + // copy inode dir to new block, except child, sibling and copied + ffsdrv.write((uint32*) &newip->location, (uint32*) &ip->location, sizeof(location_t)); + ffsdrv.write_halfword((uint16*) &newip->size, ip->size); + ffsdrv_write_byte (&newip->flags, ip->flags); + ffsdrv.write_halfword((uint16*) &newip->sequence, ip->sequence); + ffsdrv.write_halfword((uint16*) &newip->updates, ip->updates); + bstat[fs.newinodes].used++; + + // if no children of this dir, we have no more work to do + if (ip->child == (iref_t) IREF_NULL) { + tw(tr(TR_END, TrReclaimLow, "}\n")); + return; + } + + pi = -i; + i = ip->child; + ip = inode_addr(i); + + do { + tw(tr(TR_FUNC, TrReclaimLow, "pi = %d, i = %d", pi, i)); + + tw(tr(TR_NULL, TrReclaimLow, ", size = %d, location = 0x%x", ip->size, + ip->location)); + + tw(tr(TR_NULL, TrReclaimLow, ", name_addr = 0x%x", + addr2name(offset2addr(location2offset(ip->location))))); + + if (is_object(ip, OT_SEGMENT)) + tw(tr(TR_NULL, TrReclaimLow, ", (segment)\n")); + + else + tw(tr(TR_NULL, TrReclaimLow, ", '%s'\n", + (ip->size ? addr2name(offset2addr(location2offset(ip->location))) + : "(cleaned)"))); + + if (is_object_valid(ip)) + { + if (is_object(ip, OT_DIR)) { + tw(tr(TR_NULL, TrReclaimLow, "recursing...\n", i)); + inodes_recurse(i); + } + else { + tw(tr(TR_NULL, TrReclaimLow, "copying...\n")); + // copy inode to new block, except child, sibling and copied + newip = (struct inode_s *) + offset2addr(dev.binfo[fs.newinodes].offset) + i; + ffsdrv.write((uint32*) &newip->location, (uint32*) &ip->location, sizeof(location_t)); + ffsdrv.write_halfword((uint16*) &newip->size, ip->size); + ffsdrv_write_byte (&newip->flags, ip->flags); + ffsdrv.write_halfword((uint16*) &newip->sequence, ip->sequence); + ffsdrv.write_halfword((uint16*) &newip->updates, ip->updates); + bstat[fs.newinodes].used++; + } + + tw(tr(TR_FUNC, TrReclaimLow, "Linking: %d->%d\n",pi, i)); + // now write the child or sibling link of previous inode + newip = (struct inode_s *) + offset2addr(dev.binfo[fs.newinodes].offset); + if (pi > 0) + ffsdrv.write_halfword((uint16*) &(newip + pi)->sibling, i); + else + ffsdrv.write_halfword((uint16*) &(newip + (-pi))->child, i); + + pi = i; // save index of previous inode + + if (ip->child != (iref_t) IREF_NULL && is_object(ip, OT_FILE)) { + iref_t pis, is; + struct inode_s *ips; + pis = i; + ips = ip; + + tw(tr(TR_FUNC, TrReclaimLow, "Follow segment head\n")); + // While child is valid + while ((is = ips->child) != (iref_t) IREF_NULL) { + + // Get child + is = ips->child; + ips = inode_addr(is); + tw(tr(TR_FUNC, TrReclaimLow, "Child ok, got new child i = %d\n", is)); + // While object not is valid + while (!is_object_valid(ips)) { + tw(tr(TR_FUNC, TrReclaimLow, "pi = %d, i = %d c(cleaned)\n", pis, is)); + // If sibling are valid + if (ips->sibling != (iref_t) IREF_NULL) { + // Get sibling + is = ips->sibling; + ips = inode_addr(is); + tw(tr(TR_FUNC, TrReclaimLow, "Sibling ok, got new sibling i = %d\n", is)); + } + else { + tw(tr(TR_FUNC, TrReclaimLow, "Sibling = FF (%d)\n", ips->sibling)); + break; // Nothing more todo, child and sibling = FF + } + } + // If object is valid + if (is_object_valid(ips)) { + tw(tr(TR_NULL, TrReclaimLow, "copying...\n")); + // copy inode to new block, except child, sibling and copied + newip = (struct inode_s *) + offset2addr(dev.binfo[fs.newinodes].offset) + is; + ffsdrv.write((uint32*) &newip->location, (uint32*) &ips->location, sizeof(location_t)); + ffsdrv.write_halfword((uint16*) &newip->size, ips->size); + ffsdrv_write_byte (&newip->flags, ips->flags); + ffsdrv.write_halfword((uint16*) &newip->sequence, ips->sequence); + ffsdrv.write_halfword((uint16*) &newip->updates, ips->updates); + bstat[fs.newinodes].used++; + + tw(tr(TR_FUNC, TrReclaimLow, "Linking child: %d->%d\n",pis, is)); + // now write the child link of previous inode + newip = (struct inode_s *) + offset2addr(dev.binfo[fs.newinodes].offset); + ffsdrv.write_halfword((uint16*) &(newip + (pis))->child, is); + + pis = is; // save index of previous inode + + } + else { + tw(tr(TR_FUNC, TrReclaimLow, "Sibling = FF (%d, %d)\n", + ips->sibling, ips->child)); + } + + } + } + } + else { + tw(tr(TR_NULL, TrReclaimLow, "(ignoring)\n")); + } + i = ip->sibling; + ip = inode_addr(i); + + } while (i != (iref_t) IREF_NULL); + + tw(tr(TR_END, TrReclaimLow, "}\n")); +} + +// Reclaim inodes, eg. move inodes to another block and erase old one. +effs_t inodes_reclaim(void) +{ + tw(tr(TR_BEGIN, TrIReclaim, "inodes_reclaim() {\n")); + ttw(str(TTrRec, "irec{")); + + if (fs.initerror != EFFS_OK) { + tw(tr(TR_END, TrIReclaim, "} %d\n", fs.initerror)); + ttw(ttr(TTrRec, "} %d" NL, fs.initerror)); + return fs.initerror; + } + + if ((fs.newinodes = block_alloc(1, BF_COPYING)) < 0) { + tw(tr(TR_END, TrIReclaim, "} %d\n", EFFS_NOBLOCKS)); + ttw(ttr(TTrRec, "} %d" NL, EFFS_NOBLOCKS)); + return EFFS_NOBLOCKS; + } + + statistics_update_irec(bstat[fs.inodes].used - bstat[fs.inodes].lost, + bstat[fs.inodes].lost); + + // copy all inodes... + bstat[fs.newinodes].used = 0; + inodes_recurse(fs.root); + + block_commit(); + + tw(tr(TR_END, TrIReclaim, "} 0\n")); + ttw(str(TTrRec, "} 0" NL)); + + return EFFS_OK; +} + +#if (FFS_TEST == 0) +#define BLOCK_COMMIT_TEST(testcase, text) +#else +#if (TARGET == 0) +// NOTEME: We have compressed the macro code because it will NOT compile on +// Unix otherwise. So until we find out why, we use this as a work-around. +#define BLOCK_COMMIT_TEST(testcase, text) if (fs.testflags == testcase) { tw(tr(TR_FUNC, TrData, "} (" text ")\n")); return; } +#else +#define BLOCK_COMMIT_TEST(testcase, text) if (fs.testflags == testcase) { ttw(ttr(TTrData, "} (" text ")\n")); return; } +#endif +#endif + +// Inode -> Lost, Copying -> Inode, Lost -> Free +void block_commit(void) +{ + int oldinodes = fs.inodes; + + tw(tr(TR_BEGIN, TrIReclaim, "block_commit(%d -> %d) {\n", + oldinodes, fs.newinodes)); + ttw(ttr(TTrRec, "block_commit(%d -> %d) {\n" NL, + oldinodes, fs.newinodes)); + + BLOCK_COMMIT_TEST(BLOCK_COMMIT_BEFORE, "Oops before commit"); + + block_flags_write(oldinodes, BF_LOST); + + BLOCK_COMMIT_TEST(BLOCK_COMMIT_NO_VALID, "Oops no valid inode block"); + + // Validate new block as an inodes block + block_flags_write(fs.newinodes, BF_INODES); + + bstat[fs.newinodes].lost = 0; + bstat[fs.newinodes].objects = 1; + inodes_set(fs.newinodes); + + // Free old inodes block + block_free(oldinodes); + + BLOCK_COMMIT_TEST(BLOCK_COMMIT_OLD_FREE, "Oops after freeing old block"); + + BLOCK_COMMIT_TEST(BLOCK_COMMIT_AFTER, "Oops after commit"); + + ttw(str(TTrRec, "} 0" NL)); + tw(tr(TR_END, TrIReclaim, "}\n")); +} + + +/****************************************************************************** + * Data Reclaim + ******************************************************************************/ + +// Important note: We must NOT perform a data reclaim when we are in the +// process of creating the journal file! + +// Reclaim a data block, eg. move files to other blocks and erase old one. +// When the reclaim is done, we must completely delete the old inodes which +// are pointing into the old data sector which is going to be erased now. +iref_t data_reclaim(int space) +{ + iref_t error; + + tw(tr(TR_BEGIN, TrDReclaim, "data_reclaim(%d) {\n", space)); + + if (fs.initerror != EFFS_OK) { + tw(tr(TR_END, TrDReclaim, "} %d\n", fs.initerror)); + return fs.initerror; + } + + error = data_reclaim_try(space); + + tw(tr(TR_END, TrDReclaim, "} (data_reclaim) %d\n", error)); + + return error; +} + +int dage_max_reached(int dage_blk, int agegain) +{ + int reclaim, early, log2, mask; + + tw(tr(TR_BEGIN, TrDReclaim, "young(%d, %d) {\n", dage_blk, agegain)); + + // Simple algorithm + reclaim = (dage_blk + agegain - 2 * FFS_DAGE_MAX >= 0); + + // Early exponential probability based reclaim + early = FFS_DAGE_MAX - dage_blk; + if (agegain > dage_blk - 4 && 0 < early && early <= FFS_DAGE_EARLY_WIDTH) { + if (early < 4) + early = 2; + if (early < FFS_DAGE_EARLY_WIDTH) { + // Now make an exponential probability distributon by + // generating a bitmask of a size relative to (dage_blk + // - DAGE_EARLY_WIDTH) + log2 = -1; + while (early > 0) { + early >>= 1; + log2++; + } + reclaim = log2; + + mask = (1 << (log2 + 1)) - 1; + reclaim = ((rand() & mask) == 0); + } + } + + // Do not perform a reclaim unless we gain a certain minimum + if (agegain < FFS_DAGE_GAIN_MIN) + reclaim = 0; + + tw(tr(TR_END, TrDReclaim, "} (%d)\n", reclaim)); + return reclaim; +} + + +// Try to reclaim at least bytes of data space. On success, return +// the number of bytes actually reclaimed. Otherwise, on failure, return a +// (negative) error. +int data_reclaim_try(int space) +{ + // 1. Find a suitable block to reclaim. + // + // 2. Relocate each valid object from old block (to another block). An + // object relocation is similar to a normal file update, e.g. similar to + // fupdate(). + // + // 3. If there is not enough space to relocate a file, we must alloc a + // new block then data_format() it. + // + // 4. set BF_CLEANING flag of old block. + // + // 5. ALL inodes (also invalid an erased ones) referring into reclaimed + // block must now be totally wiped out. + // + // 6. Free (invalidate) old block. + + int result = 0, reserved_ok = 0; + bref_t b, blocks_free; + bref_t brc_young_b, brc_lost_b, brc_unused_b; + + blocksize_t brc_lost_lost, brc_lost_unused; + blocksize_t brc_unused_unused; + blocksize_t unused, unused_total, lost, lost_total, free; + + age_t brc_young_dage, free_dage, dage; + struct block_header_s *bhp; + // Note gain can be negative if the free block is younger than the youngest data block + int age_gain; + + tw(tr(TR_BEGIN, TrDReclaim, "data_reclaim_try(%d) {\n", space)); + ttw(str(TTrRec, "drec{" NL)); + + // While searching for a block to reclaim, we maintain three block + // reclaim candidates (brc): One with the maximum number of lost bytes, + // one with the maximum number of unused bytes and another for the + // youngest block, e.g. the one with the largest age distance to + // fs.age_max. The candidates are tried in the order mentioned. + + // This counts free blocks, so we initialize to number of blocks minus + // one for inodes. + blocks_free = dev.numblocks - 1; + + // Initialize Block Reclaim Candidate (brc) variables + brc_lost_b = -1; brc_lost_unused = 0; brc_lost_lost = 0; + brc_unused_b = -1; brc_unused_unused = 0; + + brc_young_b = -1; brc_young_dage = 0; free_dage = 0; + + lost_total = 0; + unused_total = 0; + + tw(tr(TR_FUNC, TrDReclaim, + "blk unused lost w/age age dist objs\n")); + for (b = 0; b < dev.numblocks; b++) + { + bhp = (struct block_header_s *) offset2addr(dev.binfo[b].offset); + + if (is_block(b, BF_IS_DATA)) + { + // Record number of lost bytes and number of unused bytes, + // eg. total space that would be freed if this block was + // reclaimed + lost = bstat[b].lost; + unused = dev.blocksize - (bstat[b].used - bstat[b].lost); + free = dev.blocksize - bstat[b].used; + + lost_total += lost; + unused_total += unused; + + if (free >= RESERVED_LOW) + reserved_ok = 1; + if (lost > brc_lost_lost) { + brc_lost_b = b; + brc_lost_lost = lost; + brc_lost_unused = unused; + } + if (unused > brc_unused_unused) { + brc_unused_b = b; + brc_unused_unused = unused; + } + + tw(tr(TR_FUNC, TrDReclaim, "%3d %7d %7d ", b, unused, lost)); + + dage = saturate_dage(fs.age_max - bhp->age); + + tw(tr(TR_NULL, TrDReclaim, "%6d %5d %4d %3d\n", + lost, bhp->age, dage, bstat[b].objects)); + + if (dage >= brc_young_dage) { + brc_young_b = b; + brc_young_dage = dage; + } + blocks_free--; + } + else if (is_block(b, BF_IS_FREE)) { + unused_total += dev.blocksize; + + // Find youngest free block (in must cases we will only have one free b) + dage = saturate_dage(fs.age_max - bhp->age); + + if (dage >= free_dage) + free_dage = dage; // Delta age of youngest free block + } + } + tw(tr(TR_FUNC, TrDReclaim, "sum %7d %7d\n", unused_total, lost_total)); + tw(tr(TR_FUNC, TrDReclaim, "blocks_free = %d, fs.age_max = %d\n", blocks_free, fs.age_max)); + + age_gain = brc_young_dage - free_dage; // Same as free - block age + + if (space > unused_total) { + // We will never be able to reclaim this amount... + result = 0; + } + else { + // No additional blocks (apart from spare block) are free... + tw(tr(TR_FUNC, TrDReclaim, + "brc_young_dage = %d, brc_lost_unused = %d, brc_unused_unused = %d\n", + brc_young_dage, brc_lost_unused, brc_unused_unused)); + + if (reserved_ok == 0) { + tw(tr(TR_FUNC, TrDReclaim, + "No reserved, reclaim most-lost block (%d)\n", brc_unused_b)); + result = data_block_reclaim(brc_lost_b, MOST_LOST); + } + else if (dage_max_reached(brc_young_dage, age_gain) > 0 ) { + tw(tr(TR_FUNC, TrDReclaim, "Reclaiming youngest block (%d)\n", + brc_young_b)); + result = data_block_reclaim(brc_young_b, YOUNGEST); + } + else if (brc_lost_unused >= space) { + tw(tr(TR_FUNC, TrDReclaim, "Reclaiming most-lost block (%d)\n", + brc_lost_b)); + result = data_block_reclaim(brc_lost_b, MOST_LOST); + } + else if (brc_unused_unused >= space) { + tw(tr(TR_FUNC, TrDReclaim, "Reclaiming most-unused block (%d)\n", + brc_unused_b)); + result = data_block_reclaim(brc_unused_b, MOST_UNUSED); + } + else { + tw(tr(TR_FUNC, TrDReclaim, "Reclaiming most-lost blockx (%d)\n", + brc_lost_b)); + result = data_block_reclaim(brc_lost_b, MOST_LOST); + if (result >= 0) + result = 0; // We reclaimed a block but we still need more space + } + + } + tw(tr(TR_END, TrDReclaim, "} (data_reclaim_try) %d\n", result)); + + return result; +} + + +#if (FFS_TEST == 0) +#define BLOCK_RECLAIM_TEST(testcase, text) +#else +#if (TARGET == 0) +// NOTEME: We have compressed the macro code because it will NOT compile on +// Unix otherwise. So until we find out why, we use this as a work-around. +#define BLOCK_RECLAIM_TEST(testcase, text) if (fs.testflags == testcase) { tw(tr(TR_FUNC, TrTestHigh, "(" text ")\n")); tw(tr(TR_END, TrDReclaim, "} (Test) -100\n", result));return -100; } +#else +#define BLOCK_RECLAIM_TEST(testcase, text) if (fs.testflags == testcase) { ttw(ttr(TTrData, "} (" text ")"NL)); ttw(ttr(TTrRec, "} (Test) -100" NL));return -100; } +#endif +#endif + +#if (FFS_TEST == 0) +#define BLOCK_RECOVER_TEST_INIT(testcase, text) +#define BLOCK_RECOVER_TEST(testcase, text) +#else +#if (TARGET == 0) +#define BLOCK_RECOVER_TEST_INIT(testcase, text) int rand_object; if (fs.testflags == testcase) { rand_object = rand() % bstat[b].objects; tw(tr(TR_FUNC, TrTestHigh, "Fail when object nr %d is relocated\n", rand_object)); } + +#define BLOCK_RECOVER_TEST(testcase, text) if (fs.testflags == testcase) {if (rand_object == n) { tw(tr(TR_FUNC, TrTestHigh, "(" text ")\n")); tw(tr(TR_END, TrDReclaim, "} (Test) -101\n", result)); return -101; } } + +#else +#define BLOCK_RECOVER_TEST_INIT(testcase, text) int rand_object; if (fs.testflags == testcase) { rand_object = rand() % bstat[b].objects; ttw(ttr(TTrData, "Fail when object nr %d is relocated" NL, rand_object)); } +#define BLOCK_RECOVER_TEST(testcase, text) if (fs.testflags == testcase) {if (rand_object == n) { ttw(ttr(TTrData, "(" text ")" NL)); ttw(ttr(TTrRec, "} (Test) -101" NL, result)); return -101; } } +#endif +#endif + +iref_t data_block_reclaim(bref_t b, int candidate) +{ + iref_t i, n, j; + blocksize_t used_old, lost_old; + int org_res_space, result = 0; + iref_t org_block_files_reserved; + offset_t lower, upper; + struct inode_s *ip; + static int is_reclaim_running = 0; + + tw(tr(TR_BEGIN, TrDReclaim, "data_block_reclaim(%d) {\n", b)); + + // In case of no free blocks (after sudden power off) or if the file + // system is near full we risk to be reentered (infinity recursively + // loop) and we can not allow that, so just return. + if (is_reclaim_running == 1) { + tw(tr(TR_END, TrDReclaim, "} (reenteret skip reclaim) 0\n")); + return EFFS_RECLAIMLOOP; + } + + is_reclaim_running = 1; + + // If there are more objects in this block than there are remaining + // free inodes, we have to make an inodes_reclaim() first. + tw(tr(TR_FUNC, TrDReclaim, + "block_objects, fs.inodes_max, inodes: used, free\n")); + tw(tr(TR_FUNC, TrDReclaim, + "%10d, %13d, %15d, %4d\n", + bstat[b].objects, + fs.inodes_max, bstat[fs.inodes].used, + fs.inodes_max - (bstat[fs.inodes].used + bstat[fs.inodes].lost))); + + if (bstat[b].objects >= (fs.inodes_max - (bstat[fs.inodes].used + + bstat[fs.inodes].lost + + FFS_INODES_MARGIN))) { + tw(tr(TR_FUNC, TrInode, "NOTE: Will run out of free inodes...\n")); + inodes_reclaim(); + } + + // Allocate a new block. NOTE: we don't return an error because if we + // get in the situation where we don't have any free blocks this is the + // only way to recover. + if ((result = block_alloc(1, BF_DATA)) < 0) { + tw(tr(TR_FUNC, TrAll, "WARNING: block_alloc failed\n")); + } + + BLOCK_RECLAIM_TEST(BLOCK_RECLAIM_ALLOC, "Oops after ffs_block_alloc()"); + + // If there are any objects at all to reclaim... + if (bstat[b].objects > 0) + { + BLOCK_RECOVER_TEST_INIT(BLOCK_RECOVER_OBJECTS, "Dummy") + // Save the current journal state + if (journal_push() != EFFS_OK) { + is_reclaim_running = 0; // NOTEME: change to goto? + return EFFS_CORRUPTED; + } + + // We simulate that this block is completely full, such that we + // don't relocate files to the end of the block + used_old = bstat[b].used; + lost_old = bstat[b].lost; // For statistics + bstat[b].used = dev.blocksize - 1; + + + // Compute lower (inclusive) and upper (exclusive) bounds of the + // location of files in this block + lower = offset2location(dev.binfo[b].offset); + upper = offset2location(dev.binfo[b].offset + dev.blocksize); + + tw(tr(TR_FUNC, TrDReclaim, "Block addr range = 0x%X..0x%X\n", + location2offset(lower), location2offset(upper))); + + // This is the only time we are allowed to use the reserved + org_block_files_reserved= fs.block_files_reserved; + fs.block_files_reserved = 0; + + org_res_space = fs.reserved_space; + fs.reserved_space = RESERVED_NONE; + + ip = inode_addr(1); + for (i = 1, n = 0; i < fs.inodes_max; i++, ip++) + { + BLOCK_RECOVER_TEST(BLOCK_RECOVER_OBJECTS, "Oops before relocate all objects"); + // Ensure object is valid and within the block to be reclaimed + if (is_object_valid(ip) && + lower <= ip->location && ip->location < upper) + { + if ((result = object_relocate(i)) < 0) { + tw(tr(TR_FUNC, TrAll, "FATAL object_relocate failed\n")); + break; + } + + // If we reclaim a segment head or wch that is in use we must + // update the file descriptor as well + for (j = 0; j < fs.fd_max; j++) { + if (i == fs.fd[j].seghead) { + tw(tr(TR_FUNC, TrDReclaim, + "Updated seghead %d -> %d \n", + fs.fd[j].seghead, result)); + fs.fd[j].seghead = result; + } + if (i == fs.fd[j].wch) { + tw(tr(TR_FUNC, TrDReclaim, + "Updated wch %d -> %d \n", + fs.fd[j].wch, result)); + fs.fd[j].wch = result; + } + } + + // If we have just reclaimed an object which we started on + // updating we must also update ojournal + if (i == fs.ojournal.oldi) { + struct inode_s *ip = inode_addr(result); + tw(tr(TR_FUNC, TrDReclaim, + "Updated ojournal oldi %d -> %d \n", + fs.ojournal.oldi, result)); + fs.ojournal.oldi = result; + fs.ojournal.location = ip->location; + } + + if (i == fs.ojournal.diri || i == -fs.ojournal.diri) { + fs.ojournal.diri = (fs.ojournal.diri < 0 ? -result : result); + tw(tr(TR_FUNC, TrDReclaim, + "Updated ojournal: diri %d -> %d \n", + i, fs.ojournal.diri)); + } + + if (i == fs.ojournal.repli || i == -fs.ojournal.repli) { + fs.ojournal.repli = (fs.ojournal.repli < 0 ? -result : result); + tw(tr(TR_FUNC, TrDReclaim, + "Updated ojournal: repli %d -> %d \n", + i, fs.ojournal.repli)); + } + + if (i == fs.i_backup || i == -fs.i_backup) { + fs.i_backup = (fs.i_backup < 0 ? -result : result); + tw(tr(TR_FUNC, TrDReclaim, + "Updated i_backup: %d -> %d \n", i, fs.i_backup)); + } + + n++; + } + } + + fs.block_files_reserved = org_block_files_reserved; // Restore + fs.reserved_space = org_res_space; + + tw(tr(TR_FUNC, TrDReclaim, "Reclaimed %d objects\n", n)); + if (result >= 0) + result = n; // We return number of objects relocated + + if (i < fs.inodes_max) { + // We did not finish, so restore the old bstat[].used of the block. + bstat[b].used = used_old; + tw(tr(TR_FUNC, TrAll, + "WARNING: data_block_reclaim() not completed\n")); + result = EFFS_DBR; + } + + // Restore the saved journal state + if (journal_pop() != EFFS_OK) { + is_reclaim_running = 0; // NOTEME: change to goto? + return EFFS_CORRUPTED; + } + } + BLOCK_RECLAIM_TEST(BLOCK_RECLAIM_NO_CLEAN, "Oops before clean old data block"); + + if (result >= 0) { + // Clean the block (remove all inodes that refer to this block) + block_flags_write(b, BF_CLEANING); + block_clean(b); + + statistics_update_drec(used_old - lost_old, lost_old, candidate); + + BLOCK_RECLAIM_TEST(BLOCK_RECLAIM_CLEANING, "Oops before free old data block"); + + // Free the old block + block_free(b); + } + + is_reclaim_running = 0; + + tw(tr(TR_END, TrDReclaim, "} (data_block_reclaim) %d\n", result)); + ttw(ttr(TTrRec, "} %d" NL, result)); + + return result; +} + +// Relocate object represented by inode reference . +iref_t object_relocate(iref_t oldi) +{ + iref_t newi; + struct inode_s *oldip; + char *olddata, *oldname; + int oldsize; + + tw(tr(TR_BEGIN, TrReclaimLow, "object_relocate(%d) {\n", oldi)); + + journal_begin(oldi); + + oldip = inode_addr(oldi); + + oldsize = segment_datasize(oldip); + olddata = offset2addr(location2offset(oldip->location)); + oldname = addr2name(olddata); + olddata = addr2data(olddata, oldip); + + if (is_object(oldip, OT_SEGMENT)) + newi = segment_create(olddata, oldsize, -oldi); + else { + // root inode is a special case + if (*oldname == '/') + newi = object_create(oldname, olddata, oldsize, 0); + else + newi = object_create(oldname, olddata, oldsize, oldi); + } + + if (newi < 0) { + tw(tr(TR_END, TrReclaimLow, "} %d\n", newi)); + return newi; + } + + // root inode is a special case + if ((*oldname == '/') && !is_object(oldip, OT_SEGMENT)) { + tw(tr(TR_FUNC, TrDReclaim, "Relocating fs.root: %d->%d\n", oldi, newi)); + fs.root = newi; + } + + journal_end(0); + + tw(tr(TR_END, TrReclaimLow, "} %d\n", newi)); + + return newi; +} + +// Clean a block, eg. erase all inodes that refer to this block. +iref_t block_clean(bref_t b) +{ + iref_t i, n; + struct inode_s *ip; + offset_t lower, upper; + + tw(tr(TR_FUNC, TrDReclaim, "block_clean(%d) { ", b)); + + // Compute lower (inclusive) and upper (exclusive) bounds of the + // location of files in this block + lower = offset2location(dev.binfo[b].offset); + upper = offset2location(dev.binfo[b].offset + dev.blocksize); + + tw(tr(TR_FUNC, TrDReclaim, "offset range = 0x%X..0x%X: ", lower, upper)); + + ip = inode_addr(1); + for (i = 1, n = 0; i < fs.inodes_max; i++, ip++) + { + // Ensure object is within the block to be reclaimed. Note: if ffs + // is conf. with 1MB or above will all not used inodes default have + // the location to FFFF which will trigger a clean and make a error! + if (lower <= ip->location && upper > ip->location) + { + tw(tr(TR_NULL, TrReclaimLow, "%d ", i)); + // Set the size to zero so it won't be counted in ffs_initialize() + ffsdrv.write_halfword((uint16 *) &ip->size, 0); + n++; + } + } + tw(tr(TR_NULL, TrDReclaim, "} %d\n", n)); + + return n; +} + + +/****************************************************************************** + * Main and block reclaim + ******************************************************************************/ + +// Reclaim (erase) all blocks that are marked as invalid/reclaimable. Each +// time a block is erased, its age is incremented so as to support wear +// levelling. Also, the global age limits are updated. FIXME: Should we +// avoid having ffs_initialize() do a block_reclaim() because it delays reboot?. +int blocks_reclaim(void) +{ + bref_t b, n, b_lost_space; + int blocks_free = 0, lost_space; + + int free_space, b_free_space; + + tw(tr(TR_BEGIN, TrBlock, "blocks_reclaim() {\n")); + ttw(str(TTrRec, "blocks_reclaim() {" NL)); + + // Testing of fs.testflags is for the sake of testing block_commit() + if ((fs.testflags & BLOCK_COMMIT_BASE) != 0) { + tw(tr(TR_FUNC, TrBlock, "Bailing out because fs.testflags = 0x%X\n", + fs.testflags)); + } + else { + for (b = 0, n = 0; b < dev.numblocks; b++) { + if (is_block_flag(b, BF_LOST)) { + block_reclaim(b); + n++; + } + if (is_block(b, BF_IS_FREE)) { + blocks_free++; + } + } + } + + // If the number of free blocks is less than fs.blocks_free_min we + // call data_block_reclaim(). We will reclaim the block with most lost + // space. This should only happend if we got a sudden power off/reset + // while we reclaimed a block. + if (blocks_free < fs.blocks_free_min) { + lost_space = 0; + free_space = 0; + + // We most never reclaim the block with most free space because this + // is the only block we can relocate the objects to. + for (b = 0; b < dev.numblocks; b++) { + if (is_block_flag(b, BF_DATA)) { + if ((dev.blocksize - bstat[b].used) > free_space) { + free_space = dev.blocksize - bstat[b].used; + b_free_space = b; + } + } + } + tw(tr(TR_FUNC, TrBlock, "most free space: %d in block: %d \n", + free_space, b_free_space)); + + for (b = 0; b < dev.numblocks; b++) { + if (is_block_flag(b, BF_DATA) && b != b_free_space) { + if (bstat[b].lost > lost_space) { + lost_space = bstat[b].lost; + b_lost_space = b; + } + } + } + tw(tr(TR_FUNC, TrBlock, "most lost space: %d in block: %d \n", + lost_space, b_lost_space)); + + data_block_reclaim(b_lost_space, MOST_LOST); + } + tw(tr(TR_END, TrBlock, "} %d\n", n)); + ttw(ttr(TTrRec, "} %d" NL, n)); + + return n; +} + +int block_reclaim(bref_t b) +{ + age_t age; + struct block_header_s *bhp; + + tw(tr(TR_BEGIN, TrBlock, "block_reclaim(%d) {\n", b)); + + // In ffs_initialize() we set fs.initerror = EFFS_INVALID while we call + // blocks_fsck(). We test for that condition now, in order to avoid + // doing sector erases that will delay the whole target boot process. + if (fs.initerror == EFFS_INVALID) { + tw(tr(TR_END, TrBlock, "} %d\n", fs.initerror)); + return fs.initerror; + } + + // Testing of fs.testflags is for the sake of testing block_commit() + if ((fs.testflags & BLOCK_COMMIT_BASE) != 0 && + fs.testflags != BLOCK_COMMIT_OLD_FREE) { + tw(tr(TR_FUNC, TrBlock, "Bailing out because fs.testflags = 0x%X\n", + fs.testflags)); + } + else { + // We must read block's age before we erase it. + bhp = (struct block_header_s *) offset2addr(dev.binfo[b].offset); + age = bhp->age; + ffsdrv.erase(b); + block_preformat(b, age); + } + + tw(tr(TR_END, TrBlock, "} %d\n", 0)); + + return 0; +}