/* * Copyright (C) 2010 by egnite GmbH * * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. Neither the name of the copyright holders nor the names of * contributors may be used to endorse or promote products derived * from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF * THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. * * For additional information see http://www.ethernut.de/ */ #include #include #include #include #include #include #include #include #include #include #include #if 0 /* Use for local debugging. */ #define NUTDEBUG #include #endif /*! * \file fs/uflashfs.c * \brief UFLASH File System. * * The UFLASH file system has been specifically designed for serial * flash memory chips used on tiny embedded systems. * * Such memory chips do not allow to program single bytes. Instead, * only full pages can be programmed. On the other hand, these chips * provide one or two integrated RAM buffers. They allow to modify * single bytes without transfering full pages between the memory * chip and the microcontroller's RAM. * * Although no mechanism has been implemented to replace worn out pages, * the file system maintains a very well balanced wear levelling. This * includes regular movement of static files. * * Furthermore, previously unfinished write accesses are automatically * detected and reverted when mounting a volume. However, this features * still needs to be tested more thoroughly. * * Note, that directories do not really exist in the UFLASH file system. * They are mimicked in a limited way. When scanning a directory, all * subdirectories will appear again for each file they contain. * * \verbatim * $Id$ * \endverbatim */ /*! * \addtogroup xgUFlash */ /*@{*/ /* Wheter to use time stamps. Requires, that the system time had been set. */ //#define UFLASH_USE_TIMESTAMP /* Wheter to use IDs instead of file names. Not implemented. */ //#define UFLASH_ID_NAMES /* * File system configuration. */ #ifndef UFLASH_MAX_BLOCKS /*! \brief Max. number of blocks per file system. */ #define UFLASH_MAX_BLOCKS 8192 #endif #ifndef UFLASH_BLOCK_UNITS /*! \brief Number of units per block. */ #define UFLASH_BLOCK_UNITS 4 #endif #ifndef UFLASH_ENTRIES /*! \brief Number of file system entries. */ #define UFLASH_ENTRIES 128 #endif #ifndef UFLASH_USAGE_CACHE /*! \brief Size of the least used buffer. */ #define UFLASH_USAGE_CACHE 32 #endif #if UFLASH_MAX_BLOCKS <= 255 typedef uint8_t blknum_t; typedef uint_fast8_t blknum_fast_t; #elif UFLASH_MAX_BLOCKS <= 65535 typedef uint16_t blknum_t; typedef uint_fast16_t blknum_fast_t; #else typedef uint32_t blknum_t; typedef uint_fast32_t blknum_fast_t; #endif #ifndef UFLASH_MAX_PATH /*! \brief Maximum lenght of a full absolute path. * * If not defined, a variable path name length is used. Note, that * the total size of the name is still limited, because the block * header and the entry header must fit into the first unit of the * file system block. */ #define UFLASH_MAX_PATH 0 #endif #define UFLASH_BLOCK_INVALID ((blknum_t)-1) /*! * \brief Internal block usage cache structure. */ typedef struct _BLOCK_USAGE { uint8_t use_cnt; blknum_t use_phyblk; } BLOCK_USAGE; /*! * \brief Internal block usage cache. */ static BLOCK_USAGE min_used[UFLASH_USAGE_CACHE]; /*! * \brief UFLASH block header. * * Each used block starts with a BLOCKHEAD structure. */ typedef struct NUT_PACKED_TYPE _BLOCKHEAD { blknum_t bh_logblk; /*!< \brief Logical block number. */ blknum_t bh_version; /*!< \brief Logical block version. */ blknum_t bh_entblk; /*!< \brief Logical entry block number. */ blknum_t bh_entseq; /*!< \brief Block sequence. */ } BLOCKHEAD; /*! * \brief UFLASH block footer. * * Each block ends with a BLOCKFOOT structure. */ typedef struct NUT_PACKED_TYPE _BLOCKFOOT { #ifdef UFLASH_USE_TIMESTAMP time_t bf_time; /*!< \brief Last update time. */ #endif uint8_t bf_wear; /*!< \brief Erase counter. */ } BLOCKFOOT; /* * UFLASH entry header. * * On each entry block an ENTRYHEAD structure follows the BLOCKHEAD. * The ENTRYHEAD is followed by the variable length name of this entry. * After the name comes the data, immediatly followed by an optional * timestamp. */ typedef struct NUT_PACKED_TYPE _ENTRYHEAD { /* Entry attributes. */ uint8_t eh_attr; /* Name length. */ uint8_t eh_nlen; } ENTRYHEAD; /*! * \brief File system entry descriptor. */ typedef struct _UFLASHENTRY { /*! \brief Logical block number of the entry. */ blknum_t ent_id; /*! \brief Current data pointer position. */ uint32_t ent_pos; /*! \brief Current logical block number. */ blknum_t ent_bidx; /*! \brief Current sequence. */ uint16_t ent_sidx; /*! \brief Current unit index within the current block. */ uint16_t ent_uidx; /*! \brief Current position within the current unit. */ uint16_t ent_upos; /*! \brief File open mode flags. */ uint32_t ent_mode; /*! \brief Entry attributes. */ uint8_t ent_attr; /*! \brief Name length. */ uint8_t ent_namlen; /*! \brief Total data size. */ uint32_t ent_size; /*! \brief Total file data offset of the entry block. */ uint16_t ent_eoff; } UFLASHENTRY; #define UFLASH_VOLF_FIXED 0x80 /*! \brief Volume info structure type. */ typedef struct _UFLASHVOLUME UFLASHVOLUME; /*! * \brief Volume info structure. */ struct _UFLASHVOLUME { /*! \brief I/O interface. */ NUTSERIALFLASH *vol_ifc; /*! \brief Exclusive access queue. */ HANDLE vol_mutex; /*! \brief Attribute flags. */ uint8_t vol_attrib; /*! \brief Number of blocks in this volume. */ blknum_t vol_blocks; /*! \brief Block translation table. */ blknum_t *vol_l2p; }; /*! * \brief Entry search structure. */ typedef struct _UFLASHFIND { blknum_t uff_lbe; char *uff_path; } UFLASHFIND; /*! * \brief Copy a given unit from one block to another. */ static int FlashUnitCopy(NUTSERIALFLASH * ifc, blknum_t b_src, blknum_t b_dst, sf_unit_t unit) { unit += ifc->sf_rsvbot; return (*ifc->sf_copy) (ifc, b_src * UFLASH_BLOCK_UNITS + unit, b_dst * UFLASH_BLOCK_UNITS + unit); } /*! * \brief Commit changes of a given unit. */ static void FlashUnitCommit(NUTSERIALFLASH * ifc, blknum_t b, sf_unit_t unit) { (*ifc->sf_commit) (ifc, b * UFLASH_BLOCK_UNITS + unit + ifc->sf_rsvbot); } /*! * \brief Read unit. */ static int FlashUnitRead(NUTSERIALFLASH * ifc, blknum_t b, sf_unit_t unit, int upos, void *data, int len) { int rc = ifc->sf_unit_size - upos; if (unit == UFLASH_BLOCK_UNITS - 1) { rc -= sizeof(BLOCKFOOT); } if (rc > len) { rc = len; } (*ifc->sf_read) (ifc, b * UFLASH_BLOCK_UNITS + unit + ifc->sf_rsvbot, upos, data, rc); return rc; } /*! * \brief Write unit. */ static int FlashUnitWrite(NUTSERIALFLASH * ifc, blknum_t b, sf_unit_t unit, int upos, const void *data, int len) { int rc = ifc->sf_unit_size - upos; if (unit == UFLASH_BLOCK_UNITS - 1) { rc -= sizeof(BLOCKFOOT); } if (rc > len) { rc = len; } (*ifc->sf_write) (ifc, b * UFLASH_BLOCK_UNITS + unit + ifc->sf_rsvbot, upos, data, rc); return rc; } /*! * \brief Verify CRC of a given block. */ static int FlashCheckBlock(NUTSERIALFLASH * ifc, blknum_t b) { return (*ifc->sf_check) (ifc, b * UFLASH_BLOCK_UNITS + ifc->sf_rsvbot, UFLASH_BLOCK_UNITS); } /*! * \brief Read header of a given block. */ static int FlashReadBlockHead(NUTSERIALFLASH * ifc, blknum_t b, BLOCKHEAD * bh) { return (*ifc->sf_read) (ifc, b * UFLASH_BLOCK_UNITS + ifc->sf_rsvbot, 0, bh, sizeof(BLOCKHEAD)); } /*! * \brief Write header of a given block. */ static int FlashWriteBlockHead(NUTSERIALFLASH * ifc, blknum_t b, BLOCKHEAD * bh) { /* Update the block's version number. */ bh->bh_version++; return (*ifc->sf_write) (ifc, b * UFLASH_BLOCK_UNITS + ifc->sf_rsvbot, 0, bh, sizeof(BLOCKHEAD)); } /*! * \brief Read entry of a given block. */ static int FlashReadEntry(NUTSERIALFLASH * ifc, blknum_t b, ENTRYHEAD * eh, char **name) { int rc; /* Read the entry header, which contains the name length. */ rc = (*ifc->sf_read) (ifc, b * UFLASH_BLOCK_UNITS + ifc->sf_rsvbot, sizeof(BLOCKHEAD), eh, sizeof(ENTRYHEAD)); if (rc == 0 && name) { *name = malloc(eh->eh_nlen + 1); if (*name == NULL) { rc = -1; } else { *(*name + eh->eh_nlen) = '\0'; rc = (*ifc->sf_read) (ifc, b * UFLASH_BLOCK_UNITS + ifc->sf_rsvbot, sizeof(BLOCKHEAD) + sizeof(ENTRYHEAD), *name, eh->eh_nlen); } } return rc; } /*! * \brief Compare entry name of a given block. */ static int FlashBlockCmpEntryName(NUTSERIALFLASH * ifc, blknum_t b, const char *name, uint8_t len) { return (*ifc->sf_compare) (ifc, b * UFLASH_BLOCK_UNITS + ifc->sf_rsvbot, sizeof(BLOCKHEAD) + sizeof(ENTRYHEAD), name, len); } /*! * \brief Write entry of a given block. */ static int FlashWriteEntry(NUTSERIALFLASH * ifc, blknum_t b, const ENTRYHEAD * eh, const char *name) { int rc; rc = (*ifc->sf_write) (ifc, b * UFLASH_BLOCK_UNITS + ifc->sf_rsvbot, sizeof(BLOCKHEAD), eh, sizeof(ENTRYHEAD)); if (rc == 0) { rc = (*ifc->sf_write) (ifc, b * UFLASH_BLOCK_UNITS + ifc->sf_rsvbot, sizeof(BLOCKHEAD) + sizeof(ENTRYHEAD), name, eh->eh_nlen); } return rc; } /*! * \brief Read footer of a given block. */ static int FlashReadBlockFoot(NUTSERIALFLASH * ifc, blknum_t b, BLOCKFOOT * bf) { return (*ifc->sf_read) (ifc, (b + 1) * UFLASH_BLOCK_UNITS - 1 + ifc->sf_rsvbot, -(int) sizeof(BLOCKFOOT), bf, sizeof(BLOCKFOOT)); } /*! * \brief Write footer of a given block. */ static int FlashWriteBlockFoot(NUTSERIALFLASH * ifc, blknum_t b, BLOCKFOOT * bf) { bf->bf_wear++; #ifdef UFLASH_USE_TIMESTAMP bf->bf_time = time(NULL); #endif return (*ifc->sf_write) (ifc, (b + 1) * UFLASH_BLOCK_UNITS - 1 + ifc->sf_rsvbot, -(int) sizeof(BLOCKFOOT), bf, sizeof(BLOCKFOOT)); } /*! * \brief Erase first unit of a given block. */ static int FlashEraseEntry(NUTSERIALFLASH * ifc, blknum_t b) { return (*ifc->sf_erase) (ifc, b * UFLASH_BLOCK_UNITS + ifc->sf_rsvbot, 1); } /*! * \brief Erase data units of a given block. */ static int FlashEraseBlockData(NUTSERIALFLASH * ifc, blknum_t b) { return (*ifc->sf_erase) (ifc, b * UFLASH_BLOCK_UNITS + 1 + ifc->sf_rsvbot, UFLASH_BLOCK_UNITS - 1); } /*! * \brief Erase a given number of blocks. */ static int FlashEraseBlocks(NUTSERIALFLASH * ifc, int n) { return (*ifc->sf_erase) (ifc, ifc->sf_rsvbot, n * UFLASH_BLOCK_UNITS); } /*! * \brief Collect least used blocks. * * \param vol Pointer to the information structure of a mounted volume. * * TODO: We may optionally exclude static files from recycling. */ static void CollectLeastUsed(UFLASHVOLUME * vol, uint8_t * usage) { uint8_t rmin; uint8_t rmax; blknum_t b; /* Determine the range of values. */ rmin = usage[0]; rmax = rmin; for (b = 1; b < vol->vol_blocks; b++) { if (usage[b] < rmin) { rmin = usage[b]; if (rmax - rmin >= 128) { break; } } else if (usage[b] > rmax) { rmax = usage[b]; if (rmax - rmin >= 128) { break; } } } /* ** If the range of values is equal or greater than half of the full ** range, then some values overflowed. In this case we shift all ** values by half of the full range to get a consecutive sequence. */ if (b < vol->vol_blocks) { rmax = 0; for (b = 0; b < vol->vol_blocks; b++) { usage[b] += 128; if (usage[b] > rmax) { rmax = usage[b]; } } } /* ** Collect a sorted table of minimums. */ memset(min_used, 0xff, sizeof(min_used)); for (b = 0; b < vol->vol_blocks; b++) { int j = UFLASH_USAGE_CACHE - 1; if (min_used[j].use_phyblk > vol->vol_blocks || min_used[j].use_cnt > usage[b]) { while (--j >= 0) { if (usage[b] >= min_used[j].use_cnt && min_used[j].use_phyblk < vol->vol_blocks) { break; } min_used[j + 1] = min_used[j]; } min_used[j + 1].use_cnt = usage[b]; min_used[j + 1].use_phyblk = b; } } } static blknum_t PhysBlkAllocate(UFLASHVOLUME * vol, int level); /*! * \brief Move a physical block. * * \param vol Pointer to the information structure of a mounted volume. * \param src Physical source block number. On return, this block will * be available again. * \param dst Physical destination block number. */ static int PhysBlkMove(UFLASHVOLUME * vol, blknum_t src, blknum_t dst) { int rc = 0; BLOCKHEAD bh; BLOCKFOOT bf; uint_fast8_t u; /* Read the header of the source block. */ FlashReadBlockHead(vol->vol_ifc, src, &bh); /* Read the footer of the destination block. */ FlashReadBlockFoot(vol->vol_ifc, dst, &bf); /* Erase the tail of the destination. */ FlashEraseBlockData(vol->vol_ifc, dst); /* Copy unit by unit. */ for (u = 0; rc == 0 && u < UFLASH_BLOCK_UNITS; u++) { FlashUnitCopy(vol->vol_ifc, src, dst, u); /* If this is the first unit, write a new version of the header. */ if (u == 0) { FlashWriteBlockHead(vol->vol_ifc, dst, &bh); } /* If this is the last unit, write back an updated footer. */ else if (u == UFLASH_BLOCK_UNITS - 1) { FlashWriteBlockFoot(vol->vol_ifc, dst, &bf); } /* Commit the unit. */ FlashUnitCommit(vol->vol_ifc, dst, u); } /* If all done, erase the first unit of the source block. This will ** make the block available again. */ FlashEraseEntry(vol->vol_ifc, src); return 0; } /*! * \brief Get the first unused block. * * \param vol Pointer to the information structure of a mounted volume. */ static blknum_t PhysBlkUnused(UFLASHVOLUME * vol) { BLOCKHEAD bh; blknum_t b; for (b = 0; b < vol->vol_blocks; b++) { FlashReadBlockHead(vol->vol_ifc, b, &bh); if (bh.bh_logblk >= vol->vol_blocks) { break; } } return b; } /*! * \brief Reallocate an unused physical block. * * \param vol Pointer to the information structure of a mounted volume. */ static int PhysBlkRequest(UFLASHVOLUME * vol, int level, blknum_t n) { blknum_t b; /* Check if this block is in use. */ for (b = 0; b < vol->vol_blocks; b++) { if (vol->vol_l2p[b] == n) { blknum_t nn; nn = PhysBlkAllocate(vol, level + 1); if (nn >= vol->vol_blocks) { return -1; } vol->vol_l2p[b] = nn; PhysBlkMove(vol, n, nn); break; } } return 0; } /*! * \brief Allocate an unused physical block. * * \param vol Pointer to the information structure of a mounted volume. */ static blknum_t PhysBlkAllocate(UFLASHVOLUME * vol, int level) { uint8_t *phy_use; int i; for (;;) { if (level || min_used[UFLASH_USAGE_CACHE - (UFLASH_USAGE_CACHE / 4)].use_phyblk < vol->vol_blocks) { for (i = 0; i < UFLASH_USAGE_CACHE; i++) { blknum_t n = min_used[i].use_phyblk; if (n < vol->vol_blocks) { min_used[i].use_phyblk = UFLASH_BLOCK_INVALID; if (PhysBlkRequest(vol, level, n)) { return UFLASH_BLOCK_INVALID; } return n; } } } /* Cache exhausted. */ phy_use = calloc(vol->vol_blocks, 1); if (phy_use == NULL) { break; } for (i = 0; i < vol->vol_blocks; i++) { BLOCKFOOT bf; FlashReadBlockFoot(vol->vol_ifc, i, &bf); phy_use[i] = bf.bf_wear; } for (i = 0; i < vol->vol_blocks; i++) { if (vol->vol_l2p[i] < vol->vol_blocks) { phy_use[vol->vol_l2p[i]] += UFLASH_USAGE_CACHE - 1; } } CollectLeastUsed(vol, phy_use); free(phy_use); if (level) { break; } } return PhysBlkUnused(vol); } /*! * \brief Allocate a new logical block. * * \param vol Pointer to the information structure of a mounted volume. */ static blknum_t LogBlkAllocate(UFLASHVOLUME * vol, blknum_t lb) { int lb_last = lb ? vol->vol_blocks : UFLASH_ENTRIES; /* Find an unused logical block. */ for (; lb < lb_last; lb++) { if (vol->vol_l2p[lb] >= vol->vol_blocks) { /* Found, assign a physical block. */ vol->vol_l2p[lb] = PhysBlkAllocate(vol, 0); break; } } return lb; } /*! * \brief Reallocate a logical block. * * \param vol Pointer to the information structure of a mounted volume. */ static blknum_t LogBlkReallocate(UFLASHVOLUME * vol, blknum_t lb) { blknum_t b_old; blknum_t b_new; /* Allocate a new physical block. Note, that the currently assigned ** physical block may move during this allocation. */ b_new = PhysBlkAllocate(vol, 0); if (b_new >= vol->vol_blocks) { b_old = b_new; } else { b_old = vol->vol_l2p[lb]; vol->vol_l2p[lb] = b_new; } return b_old; } /*! * \brief Find existing data block by sequence. * * \param vol Pointer to the information structure of a mounted volume. */ static int LogBlkRelease(UFLASHVOLUME * vol, blknum_t lb) { blknum_t b; b = vol->vol_l2p[lb]; vol->vol_l2p[lb] = UFLASH_BLOCK_INVALID; return FlashEraseEntry(vol->vol_ifc, b); } /*! * \brief Find existing data block by sequence. * * \param vol Pointer to the information structure of a mounted volume. * \param lbe Logical block of the entry. * \param seq Sequence number to search. * * \return Logical block number of the requested data block. If this is * equal to the number of blocks available, then the sequence * doesn't exist. */ static blknum_t EntrySeek(UFLASHVOLUME * vol, blknum_t lbe, blknum_t seq) { blknum_t lb = lbe; /* Data block sequence 0 is the same as the entry block. No need to ** search for it. */ if (seq) { /* Walking through the translation table requires exclusive access. */ NutEventWait(&vol->vol_mutex, 0); /* No need to search the entry blocks. */ for (lb = UFLASH_ENTRIES; lb < vol->vol_blocks; lb++) { blknum_t b = vol->vol_l2p[lb]; /* If this block is allocated, read its header. */ if (b < vol->vol_blocks) { BLOCKHEAD bh; FlashReadBlockHead(vol->vol_ifc, b, &bh); if (bh.bh_entblk == lbe && bh.bh_entseq == seq) { /* Block found. */ break; } } } /* Release the exclusive access. */ NutEventPost(&vol->vol_mutex); } return lb; } /*! * \brief Update the position indices. */ static void EntryPosSet(NUTFILE * nfp, long pos) { uint16_t seq; uint32_t off; UFLASHENTRY *ent; UFLASHVOLUME *vol; ent = (UFLASHENTRY *) nfp->nf_fcb; vol = (UFLASHVOLUME *) nfp->nf_dev->dev_dcb; /* Current position within this entry. */ ent->ent_pos = pos; off = ent->ent_eoff + ent->ent_pos; /* Current block sequence within this entry. */ seq = (uint16_t) (off / (vol->vol_ifc->sf_unit_size * UFLASH_BLOCK_UNITS - (sizeof(BLOCKHEAD) + sizeof(BLOCKFOOT)))); if (seq != ent->ent_sidx) { ent->ent_sidx = seq; ent->ent_bidx = UFLASH_BLOCK_INVALID; } off -= ent->ent_sidx * (vol->vol_ifc->sf_unit_size * UFLASH_BLOCK_UNITS - (sizeof(BLOCKHEAD) + sizeof(BLOCKFOOT))); /* Page number within the current block. */ ent->ent_uidx = (uint16_t) ((off + sizeof(BLOCKHEAD)) / vol->vol_ifc->sf_unit_size); off -= ent->ent_uidx * vol->vol_ifc->sf_unit_size; /* Position with the current page. */ ent->ent_upos = (uint16_t) off + sizeof(BLOCKHEAD); } static void EntryPosInc(NUTFILE * nfp, int step) { UFLASHENTRY *ent; UFLASHVOLUME *vol; ent = (UFLASHENTRY *) nfp->nf_fcb; vol = (UFLASHVOLUME *) nfp->nf_dev->dev_dcb; ent->ent_pos += step; ent->ent_upos += step; if (ent->ent_uidx < UFLASH_BLOCK_UNITS - 1) { step = vol->vol_ifc->sf_unit_size - ent->ent_upos; } else { step = vol->vol_ifc->sf_unit_size - sizeof(BLOCKFOOT) - ent->ent_upos; } if (step == 0) { ent->ent_uidx++; if (ent->ent_uidx >= UFLASH_BLOCK_UNITS) { ent->ent_upos = sizeof(BLOCKHEAD); ent->ent_uidx = 0; ent->ent_sidx++; ent->ent_bidx = UFLASH_BLOCK_INVALID; } else { ent->ent_upos = 0; } } } /*! * \brief Scan for all blocks associated to a given entry. * * \param vol Pointer to the information structure of a mounted volume. */ static long EntryScan(UFLASHVOLUME * vol, blknum_t lbe, time_t * mod) { long siz; int p; int i; int off; BLOCKHEAD bh; blknum_t lb; blknum_t lbs = lbe; blknum_t seq = 0; #ifdef UFLASH_USE_TIMESTAMP if (mod) { BLOCKFOOT bf; FlashReadBlockFoot(vol->vol_ifc, vol->vol_l2p[lbe] / UFLASH_BLOCK_UNITS, &bf); *mod = bf.bf_time; } #endif /* Find the highest sequence block. */ for (lb = UFLASH_ENTRIES; lb < vol->vol_blocks; lb++) { p = vol->vol_l2p[lb]; if (p < vol->vol_blocks) { p *= UFLASH_BLOCK_UNITS; FlashReadBlockHead(vol->vol_ifc, p / UFLASH_BLOCK_UNITS, &bh); if (bh.bh_entblk == lbe) { if (seq < bh.bh_entseq) { seq = bh.bh_entseq; lbs = lb; } #ifdef UFLASH_USE_TIMESTAMP if (mod) { BLOCKFOOT bf; FlashReadBlockFoot(vol->vol_ifc, p / UFLASH_BLOCK_UNITS, &bf); if (*mod < bf.bf_time) { *mod = bf.bf_time; } } #endif } } } /* Size of full blocks. */ siz = seq * (vol->vol_ifc->sf_unit_size * UFLASH_BLOCK_UNITS - (sizeof(BLOCKHEAD) + sizeof(BLOCKFOOT))); /* Number of bytes in the highest sequence block. */ p = vol->vol_l2p[lbs] * UFLASH_BLOCK_UNITS; off = -(int) sizeof(BLOCKFOOT); for (i = UFLASH_BLOCK_UNITS; --i >= 0;) { int s = (*vol->vol_ifc->sf_used) (vol->vol_ifc, p + i + vol->vol_ifc->sf_rsvbot, off); off = 0; if (s) { siz += s + i * vol->vol_ifc->sf_unit_size - sizeof(BLOCKHEAD); break; } } return siz; } /*! * \brief Find entry by name. * * This routine can be either used to find an exact match or to find * the next entry that starts with the given name. * * \param vol Pointer to the volume information structure. * \param name Full path name of the requested entry. A leading slash * will be ignored. * \param eh Pointer to the entry header. * \param lbs Logical block to start searching. If set to UFLASH_ENTRIES, * all entry blocks will be searched for an exact match. * Otherwise the search starts at the specified block for the * next entry that starts with the given name. */ static int EntrySearchNext(UFLASHVOLUME * vol, const char *name, ENTRYHEAD * eh, blknum_t lbs) { blknum_t lbe; blknum_t b; int nlen; nlen = strlen(name); /* Walking through the translation table requires exclusive access. */ NutEventWait(&vol->vol_mutex, 0); for (lbe = lbs == UFLASH_ENTRIES ? 0 : lbs; lbe < UFLASH_ENTRIES; lbe++) { b = vol->vol_l2p[lbe]; if (b < vol->vol_blocks) { /* Read the entry header, which contains the path length. */ if (FlashReadEntry(vol->vol_ifc, b, eh, NULL) == 0) { /* If the path lengths are equal, then compare the names. */ if (eh->eh_nlen == nlen || (lbs != UFLASH_ENTRIES && eh->eh_nlen >= nlen)) { if (FlashBlockCmpEntryName(vol->vol_ifc, b, name, nlen) == 0) { NutEventPost(&vol->vol_mutex); return lbe; } } } } } NutEventPost(&vol->vol_mutex); return UFLASH_ENTRIES; } /*! * \brief Find existing entry by name. * * \param vol Pointer to the information structure of a mounted volume. */ static int EntrySearch(UFLASHVOLUME * vol, const char *name, ENTRYHEAD * eh) { return EntrySearchNext(vol, name, eh, UFLASH_ENTRIES); } /*! * \brief Erase all data blocks of a given entry. * * \param vol Pointer to the information structure of a mounted volume. */ static int EntryTruncateSeq(UFLASHVOLUME * vol, int lbe) { blknum_t lb; BLOCKHEAD bh; for (lb = UFLASH_ENTRIES; lb < vol->vol_blocks; lb++) { if (vol->vol_l2p[lb] < vol->vol_blocks) { FlashReadBlockHead(vol->vol_ifc, vol->vol_l2p[lb], &bh); if (bh.bh_entblk == lbe) { LogBlkRelease(vol, lb); } } } return 0; } /*! * \brief Truncate an entry. * * \param vol Pointer to the information structure of a mounted volume. */ static int EntryTruncate(UFLASHVOLUME * vol, blknum_t lbe) { BLOCKHEAD bh; ENTRYHEAD eh; BLOCKFOOT bf; blknum_t b; blknum_t b_old; char *name = NULL; EntryTruncateSeq(vol, lbe); /* Allocate a new entry block. */ b = PhysBlkAllocate(vol, 0); if (b >= vol->vol_blocks) { return -1; } b_old = vol->vol_l2p[lbe]; vol->vol_l2p[lbe] = b; /* Read the block footer and erase the remaining units. */ FlashReadBlockFoot(vol->vol_ifc, b, &bf); FlashEraseBlockData(vol->vol_ifc, b); /* Copy block and entry headers, incrementing the version. * Note, that we cannot use a unit copy, because we also must * get rid of the data in the first block. */ FlashReadBlockHead(vol->vol_ifc, b_old, &bh); FlashReadEntry(vol->vol_ifc, b_old, &eh, &name); FlashWriteBlockHead(vol->vol_ifc, b, &bh); FlashWriteEntry(vol->vol_ifc, b, &eh, name); free(name); FlashUnitCommit(vol->vol_ifc, b, 0); /* Update block footer in the last unit. */ FlashWriteBlockFoot(vol->vol_ifc, b, &bf); FlashUnitCommit(vol->vol_ifc, b, UFLASH_BLOCK_UNITS - 1); /* Remove the old entry. */ FlashEraseEntry(vol->vol_ifc, b_old); return 0; } /*! * \brief Create a new entry. * * \param vol Pointer to the information structure of a mounted volume. */ static int EntryCreate(UFLASHVOLUME * vol, blknum_t lbe, ENTRYHEAD * eh, const char *name) { BLOCKHEAD bh; BLOCKFOOT bf; blknum_t b; b = vol->vol_l2p[lbe]; /* Read the footer before erasing the block. */ FlashReadBlockFoot(vol->vol_ifc, b, &bf); FlashEraseBlockData(vol->vol_ifc, b); /* Write block header. */ bh.bh_logblk = lbe; bh.bh_version = UFLASH_BLOCK_INVALID; bh.bh_entblk = lbe; bh.bh_entseq = 0; FlashWriteBlockHead(vol->vol_ifc, b, &bh); /* Write the entry header and name. */ FlashWriteEntry(vol->vol_ifc, b, eh, name); FlashUnitCommit(vol->vol_ifc, b, 0); /* Update block footer. */ FlashWriteBlockFoot(vol->vol_ifc, b, &bf); FlashUnitCommit(vol->vol_ifc, b, UFLASH_BLOCK_UNITS - 1); return 0; } /*! * \brief Remove an entry. */ static int UFlashFileRemove(NUTDEVICE * dev, const char *name) { blknum_t lbe; ENTRYHEAD eh; UFLASHVOLUME *vol; vol = (UFLASHVOLUME *) dev->dev_dcb; while (*name == '/') { name++; } lbe = EntrySearch(vol, name, &eh); if (lbe >= UFLASH_ENTRIES || vol->vol_l2p[lbe] > vol->vol_blocks) { return -1; } NutEventWait(&vol->vol_mutex, 0); EntryTruncateSeq(vol, lbe); LogBlkRelease(vol, lbe); NutEventPost(&vol->vol_mutex); return 0; } /*! * \brief Retrieve the status of an entry. */ static int UFlashFileStatus(NUTDEVICE * dev, const char *name, struct stat *st) { blknum_t lbe; ENTRYHEAD eh; UFLASHVOLUME *vol; uint_fast8_t partial = 0; vol = (UFLASHVOLUME *) dev->dev_dcb; while (*name == '/') { name++; } lbe = EntrySearch(vol, name, &eh); if (lbe >= UFLASH_ENTRIES || vol->vol_l2p[lbe] > vol->vol_blocks) { lbe = EntrySearchNext(vol, name, &eh, 0); if (lbe >= UFLASH_ENTRIES || vol->vol_l2p[lbe] > vol->vol_blocks) { return -1; } partial = 1; } memset(st, 0, sizeof(struct stat)); if (partial) { st->st_mode = 1; #ifdef UFLASH_USE_TIMESTAMP EntryScan(vol, lbe, &st->st_mtime); #endif } else { NutEventWait(&vol->vol_mutex, 0); #ifdef UFLASH_USE_TIMESTAMP st->st_size = EntryScan(vol, lbe, &st->st_mtime); #else st->st_size = EntryScan(vol, lbe, NULL); #endif NutEventPost(&vol->vol_mutex); #if UFLASH_MAX_PATH st->st_size -= sizeof(ENTRYHEAD) + UFLASH_MAX_PATH; #else st->st_size -= sizeof(ENTRYHEAD) + eh.eh_nlen; #endif } return 0; } /*! * \brief Open a directory. * * \param dev Specifies the file system device. * \param dpath Full absolute pathname of the directory to open. * * \return Pointer to a NUTFILE structure if successful or NUTFILE_EOF otherwise. */ static NUTFILE *UFlashDirOpen(NUTDEVICE * dev, const char *dpath) { NUTFILE *ndp; UFLASHFIND *uff; ndp = malloc(sizeof(NUTFILE)); if (ndp) { uff = malloc(sizeof(UFLASHFIND)); if (uff) { uff->uff_lbe = 0; uff->uff_path = strdup(dpath + (*dpath == '/')); if (uff->uff_path) { ndp->nf_dev = dev; ndp->nf_fcb = uff; return ndp; } free(uff); } free(ndp); } return NUTFILE_EOF; } /*! * \brief Close a directory. */ static int UFlashDirClose(NUTFILE * ndp) { UFLASHFIND *uff; uff = (UFLASHFIND *) ndp->nf_fcb; free(uff->uff_path); free(uff); free(ndp); return 0; } /*! * \brief Read next entry of a directory. */ static int UFlashDirRead(DIR * dir) { struct dirent *ent; NUTFILE *ndp; NUTDEVICE *dev; UFLASHFIND *uff; UFLASHVOLUME *vol; ENTRYHEAD eh; char *name = NULL; char *cp; ndp = dir->dd_fd; uff = (UFLASHFIND *) ndp->nf_fcb; dev = ndp->nf_dev; vol = (UFLASHVOLUME *) dev->dev_dcb; uff->uff_lbe = EntrySearchNext(vol, uff->uff_path, &eh, uff->uff_lbe); if (uff->uff_lbe >= UFLASH_ENTRIES) { return -1; } ent = (struct dirent *) dir->dd_buf; memset(dir->dd_buf, 0, sizeof(struct dirent)); FlashReadEntry(vol->vol_ifc, vol->vol_l2p[uff->uff_lbe], &eh, &name); cp = strchr(name + strlen(uff->uff_path), '/'); if (cp) { *cp = '\0'; ent->d_type = 1; } else { ent->d_type = 0; } strcpy(ent->d_name, name + strlen(uff->uff_path)); ent->d_namlen = strlen(name + strlen(uff->uff_path)); free(name); uff->uff_lbe++; return 0; } /*! * \brief Rename an entry. * * \param dev Specifies the file system device. * \param old_path Old name and path of the file. * \param new_path New name and path of the file. * * \return 0 on success, -1 otherwise. */ static int UFlashFileRename(NUTDEVICE * dev, const char *old_path, const char *new_path) { blknum_t lbe; blknum_t b; blknum_t b_old; uint_fast8_t u; ENTRYHEAD eh; UFLASHVOLUME *vol; BLOCKHEAD bh; BLOCKFOOT bf; NUTASSERT(dev != NULL); NUTASSERT(old_path != NULL); #if UFLASH_MAX_PATH NUTASSERT(strlen(old_path) <= UFLASH_MAX_PATH); #endif NUTASSERT(new_path != NULL); #if UFLASH_MAX_PATH NUTASSERT(strlen(new_path) <= UFLASH_MAX_PATH); #endif vol = (UFLASHVOLUME *) dev->dev_dcb; NUTASSERT(vol != NULL); vol = (UFLASHVOLUME *) dev->dev_dcb; while (*old_path == '/') { old_path++; } while (*new_path == '/') { new_path++; } lbe = EntrySearch(vol, old_path, &eh); if (lbe >= UFLASH_ENTRIES || vol->vol_l2p[lbe] > vol->vol_blocks) { return -1; } #if UFLASH_MAX_PATH == 0 if (eh.eh_nlen < strlen(new_path)) { return -1; } #endif eh.eh_nlen = strlen(new_path); /* Exclusive access from here. */ NutEventWait(&vol->vol_mutex, 0); /* Allocate a new entry block. */ b = PhysBlkAllocate(vol, 0); if (b >= vol->vol_blocks) { /* No more blocks available. */ NutEventPost(&vol->vol_mutex); return -1; } /* Replace the physical block in the translation table. */ b_old = vol->vol_l2p[lbe]; vol->vol_l2p[lbe] = b; /* Keep the footer and erase the pages of the new block, except the first. The first page of unallocated blocks is already erased. */ FlashReadBlockFoot(vol->vol_ifc, b, &bf); FlashEraseBlockData(vol->vol_ifc, b); /* Copy all pages of the entry block. */ for (u = 0; u < UFLASH_BLOCK_UNITS; u++) { FlashUnitCopy(vol->vol_ifc, b_old, b, u); /* If this is the first unit, write a new version of the header. */ if (u == 0) { /* Separately copy the block header, because this will increment the block's version number. */ FlashReadBlockHead(vol->vol_ifc, b, &bh); FlashWriteBlockHead(vol->vol_ifc, b, &bh); /* Fill in the new path name. */ FlashWriteEntry(vol->vol_ifc, b, &eh, new_path); } /* If this is the last unit, write back an updated footer. */ else if (u == UFLASH_BLOCK_UNITS - 1) { FlashWriteBlockFoot(vol->vol_ifc, b, &bf); } /* Commit the unit. */ FlashUnitCommit(vol->vol_ifc, b, u); } /* Finally remove the old entry. */ FlashEraseEntry(vol->vol_ifc, b_old); /* Release exclusive access. */ NutEventPost(&vol->vol_mutex); return 0; } /*! * \brief Retrieve the size of a previously opened UFLASH file. * * This function is called by the low level size routine of the C runtime * library, using the _NUTDEVICE::dev_size entry. * * \param nfp Pointer to a \ref _NUTFILE structure, obtained by a * previous call to UFlashFileOpen(). * * \return Size of the file. */ static long UFlashFileSize(NUTFILE * nfp) { NUTASSERT(nfp != NULL); NUTASSERT(nfp->nf_fcb != NULL); return ((UFLASHENTRY *) nfp->nf_fcb)->ent_size; } /*! * \brief Open a UFLASH file. * * This function is called by the low level open routine of the C runtime * library, using the _NUTDEVICE::dev_open entry. * * \param dev Specifies the file system device. * \param path Pathname of the file to open. * \param mode Operation mode. * \param acc File attribute, currently ignored. * * \return Pointer to a NUTFILE structure if successful or NUTFILE_EOF otherwise. */ static NUTFILE *UFlashFileOpen(NUTDEVICE * dev, const char *path, int mode, int acc) { int rc = -1; NUTFILE *nfp = NUTFILE_EOF; UFLASHENTRY *ent; UFLASHVOLUME *vol; blknum_t lbe; ENTRYHEAD eh; long datsiz = 0; NUTASSERT(dev != NULL); NUTASSERT(path != NULL); vol = (UFLASHVOLUME *) dev->dev_dcb; NUTASSERT(vol != NULL); while (*path == '/') { path++; } /* Allocate a private entry information structure. */ ent = calloc(1, sizeof(UFLASHENTRY)); if (ent == NULL) { return NUTFILE_EOF; } ent->ent_mode = mode; lbe = EntrySearch(vol, path, &eh); NutEventWait(&vol->vol_mutex, 0); /* ** Entry exists. */ if (lbe < UFLASH_ENTRIES) { if ((mode & (_O_CREAT | _O_EXCL)) == (_O_CREAT | _O_EXCL)) { errno = EEXIST; } else { /* Truncate. */ if ((mode & _O_TRUNC) == _O_TRUNC) { rc = EntryTruncate(vol, lbe); } /* Do not truncate. */ else { rc = 0; datsiz = EntryScan(vol, lbe, NULL); #if UFLASH_MAX_PATH datsiz -= sizeof(ENTRYHEAD) + UFLASH_MAX_PATH; #else datsiz -= sizeof(ENTRYHEAD) + eh.eh_nlen; #endif } } } /* ** Entry doesn't exist. */ else { if ((mode & _O_CREAT) == _O_CREAT) { lbe = LogBlkAllocate(vol, 0); if (lbe < UFLASH_ENTRIES) { eh.eh_nlen = strlen(path); eh.eh_attr = 0; rc = EntryCreate(vol, lbe, &eh, path); } } else { errno = ENOENT; } } if (rc) { free(ent); } else { ent->ent_attr = eh.eh_attr; ent->ent_namlen = eh.eh_nlen; #if UFLASH_MAX_PATH ent->ent_eoff = sizeof(ENTRYHEAD) + UFLASH_MAX_PATH; #else ent->ent_eoff = sizeof(ENTRYHEAD) + eh.eh_nlen; #endif ent->ent_id = lbe; ent->ent_size = datsiz; /* Allocate a file information structure. */ nfp = malloc(sizeof(NUTFILE)); if (nfp == NULL) { free(ent); NutEventPost(&vol->vol_mutex); return NUTFILE_EOF; } nfp->nf_dev = dev; nfp->nf_fcb = ent; if ((mode & _O_APPEND) == _O_APPEND) { ent->ent_bidx = UFLASH_BLOCK_INVALID; EntryPosSet(nfp, datsiz); } else { ent->ent_bidx = lbe; EntryPosSet(nfp, 0); } } NutEventPost(&vol->vol_mutex); return nfp; } /*! * \brief Close a UFLASH file. * * \param nfp Pointer to a \ref NUTFILE structure, obtained by a previous * call to UFlashFileOpen(). * * \return Always 0. */ static int UFlashFileClose(NUTFILE * nfp) { NUTASSERT(nfp != NULL); NUTASSERT(nfp->nf_dev != NULL); NUTASSERT(nfp->nf_fcb != NULL); free(nfp->nf_fcb); free(nfp); return 0; } /*! * \brief Read data from a UFLASH file. * * \param nfp Pointer to a \ref NUTFILE structure, obtained by a previous * call to UFlashFileOpen(). * \param data Pointer to the data buffer to fill. * \param size Maximum number of bytes to read. * * \return The number of bytes actually read. A return value of -1 indicates * an error. */ static int UFlashFileRead(NUTFILE * nfp, void *data, int size) { int u; int step; int rc; uint8_t *dptr; UFLASHENTRY *ent; UFLASHVOLUME *vol; /* Ignore input flush. */ if (data == NULL || size == 0) { return 0; } dptr = (uint8_t *) data; NUTASSERT(nfp != NULL); NUTASSERT(nfp->nf_dev != NULL); ent = (UFLASHENTRY *) nfp->nf_fcb; NUTASSERT(ent != NULL); vol = (UFLASHVOLUME *) nfp->nf_dev->dev_dcb; NUTASSERT(vol != NULL); /* Do not read beyond the file end. */ if (size > (int) (ent->ent_size - ent->ent_pos)) { size = (int) (ent->ent_size - ent->ent_pos); } /* ** Loop until we read the requested size. */ for (rc = 0; rc < size;) { /* Is the current logical block number still valid? */ if (ent->ent_bidx >= vol->vol_blocks) { /* If not, seek for the right block. */ ent->ent_bidx = EntrySeek(vol, ent->ent_id, ent->ent_sidx); if (ent->ent_bidx >= vol->vol_blocks) { /* Seek failed. */ errno = EIO; return -1; } } /* Read unit by unit. */ for (u = ent->ent_uidx; rc < size && u < UFLASH_BLOCK_UNITS; u++) { NutEventWait(&vol->vol_mutex, 0); step = FlashUnitRead(vol->vol_ifc, vol->vol_l2p[ent->ent_bidx], u, ent->ent_upos, dptr, size - rc); NutEventPost(&vol->vol_mutex); if (step < 0) { /* Read failed. */ errno = EIO; return -1; } /* Increment pointers and indices. */ rc += step; dptr += step; EntryPosInc(nfp, step); } } return rc; } /*! * \brief Write data to a UFLASH file. * * \param nfp Pointer to a \ref NUTFILE structure, obtained by a previous * call to UFlashFileOpen(). * \param buffer Pointer to the data to be written. If zero, then the * output buffer will be flushed. * \param len Number of bytes to write. * * \return The number of bytes written. A return value of -1 indicates an * error. */ static int UFlashFileWrite(NUTFILE * nfp, const void *data, int len) { int u; blknum_t b; blknum_t b_old; int step; int siz = len; uint8_t *dptr; BLOCKHEAD bh; BLOCKFOOT bf; UFLASHENTRY *ent; UFLASHVOLUME *vol; /* Ignore output flush. */ if (data == NULL || len == 0) { return 0; } dptr = (uint8_t *) data; /* Sanity check. */ NUTASSERT(nfp != NULL); NUTASSERT(nfp->nf_fcb != NULL); NUTASSERT(nfp->nf_dev != NULL); NUTASSERT(nfp->nf_dev->dev_dcb != NULL); ent = (UFLASHENTRY *) nfp->nf_fcb; vol = (UFLASHVOLUME *) nfp->nf_dev->dev_dcb; /* ** Loop until all data has been written. */ while (len) { /* Is the current logical block number still valid? */ if (ent->ent_bidx >= vol->vol_blocks) { /* If not, seek for the right block. The routine will return an ** invalid block number, if we need a new block. */ ent->ent_bidx = EntrySeek(vol, ent->ent_id, ent->ent_sidx); } /* ** If the current logical block exists, then we reallocate a ** physical block. */ NutEventWait(&vol->vol_mutex, 0); if (ent->ent_bidx < vol->vol_blocks) { b_old = LogBlkReallocate(vol, ent->ent_bidx); if (b_old >= vol->vol_blocks) { /* No more blocks available. */ NutEventPost(&vol->vol_mutex); return -1; } /* Read header of old block and increment the version number. */ FlashReadBlockHead(vol->vol_ifc, b_old, &bh); } /* ** If the current logical block doesn't exists, then we allocate ** a new one. */ else { ent->ent_bidx = LogBlkAllocate(vol, UFLASH_ENTRIES); if (ent->ent_bidx >= vol->vol_blocks) { /* No more blocks available. */ NutEventPost(&vol->vol_mutex); return -1; } /* No reallocation, old and new are the same block. */ b_old = vol->vol_l2p[ent->ent_bidx]; /* Initial block header. */ bh.bh_logblk = ent->ent_bidx; bh.bh_entblk = ent->ent_id; bh.bh_entseq = ent->ent_sidx; bh.bh_version = UFLASH_BLOCK_INVALID; } /* Current physical block. */ b = vol->vol_l2p[ent->ent_bidx]; /* Read the block footer. */ FlashReadBlockFoot(vol->vol_ifc, b, &bf); /* The first unit had been erased when the block was released. ** Now erase the remaining units. */ FlashEraseBlockData(vol->vol_ifc, b); /* ** Write unit by unit. */ for (u = 0; u < UFLASH_BLOCK_UNITS; u++) { /* Make a copy of the old unit. If old and new are ** the same, this won't hurt. */ FlashUnitCopy(vol->vol_ifc, b_old, b, u); /* Write the block header to the first unit. */ if (u == 0) { FlashWriteBlockHead(vol->vol_ifc, b, &bh); } /* If this is the current unit to write to, fill it with data. */ if (len && u == ent->ent_uidx) { step = FlashUnitWrite(vol->vol_ifc, b, u, ent->ent_upos, dptr, len); /* Increment pointers and indices. */ len -= step; dptr += step; EntryPosInc(nfp, step); if (ent->ent_size < ent->ent_pos) { ent->ent_size = ent->ent_pos; } } /* Write the block footer to the last unit. */ if (u == UFLASH_BLOCK_UNITS - 1) { FlashWriteBlockFoot(vol->vol_ifc, b, &bf); } FlashUnitCommit(vol->vol_ifc, b, u); } /* If we reallocated the block, then release the old one now. ** It is essential to do this after having successfully written ** the new block. If the new block with a higher version number ** remains incomplete, then the old version still exists and the ** incomplete block will be removed during mounting. */ if (b != b_old) { FlashEraseEntry(vol->vol_ifc, b_old); } NutEventPost(&vol->vol_mutex); } return siz; } #ifdef __HARVARD_ARCH__ /*! * \brief Write data from program space to a file. * * This function is not yet implemented and will always return -1. * * Similar to UFlashFileWrite() except that the data is located in * program memory. * * \param nfp Pointer to a \ref NUTFILE structure, obtained by a previous * call to RawFsFileOpen(). * \param buffer Pointer to the data in program space. If zero, then the * output buffer will be flushed. * \param len Number of bytes to write. * * \return The number of bytes written. A return value of -1 indicates an * error. */ static int UFlashFileWrite_P(NUTFILE * nfp, PGM_P buffer, int len) { return -1; } #endif /*! * \brief Move file pointer to a specified position. * * Moving beyond the current file size is not supported. * * \param nfp File descriptor. * \param pos Requested file position. * \param whence Positioning directive. * * \return 0 on success, -1 otherwise. In the latter case the position * is unspecified. */ static int UFlashFileSeek(NUTFILE * nfp, long *pos, int whence) { int rc = 0; long npos; UFLASHENTRY *ent; NUTASSERT(nfp != NULL); NUTASSERT(nfp->nf_fcb != NULL); ent = (UFLASHENTRY *) nfp->nf_fcb; NUTASSERT(pos != NULL); npos = *pos; switch (whence) { case SEEK_CUR: /* Relative to current position. */ npos += ent->ent_pos; break; case SEEK_END: /* Relative to file end. */ npos += ent->ent_size; break; } /* Make sure that we are within limits. */ if (npos < 0 || npos > (long) ent->ent_size) { errno = EINVAL; rc = -1; } else { EntryPosSet(nfp, npos); *pos = npos; } return rc; } /*! * \brief Mount a UFLASH volume. * * \param dev Specifies the file system device. * * \return 0 on success or -1 in case of an error. */ static int UFlashMount(NUTDEVICE * dev) { int b; BLOCKHEAD bh; UFLASHVOLUME *vol; NUTASSERT(dev != NULL); NUTASSERT(dev->dev_icb != NULL); NUTASSERT(dev->dev_dcb == NULL); /* Allocate the volume information structure */ vol = calloc(1, sizeof(UFLASHVOLUME)); if (vol == NULL) { return -1; } dev->dev_dcb = vol; vol->vol_ifc = (NUTSERIALFLASH *) dev->dev_icb; /* Determine the total number of blocks. Honor any reserved units. */ vol->vol_blocks = (blknum_t) ((vol->vol_ifc->sf_units - vol->vol_ifc->sf_rsvbot - vol->vol_ifc->sf_rsvtop) / UFLASH_BLOCK_UNITS); if (vol->vol_blocks > UFLASH_MAX_BLOCKS) { vol->vol_blocks = UFLASH_MAX_BLOCKS; } /* Allocate the block translation table. */ vol->vol_l2p = malloc(vol->vol_blocks * sizeof(blknum_t)); if (vol->vol_l2p == NULL) { free(vol); return -1; } memset(vol->vol_l2p, 0xff, vol->vol_blocks * sizeof(blknum_t)); memset(min_used, 0xff, sizeof(min_used)); /* ** Fill the block translation table. */ for (b = 0; b < vol->vol_blocks; b++) { FlashReadBlockHead(vol->vol_ifc, b, &bh); /* Check if this is a logical block. */ if (bh.bh_logblk < vol->vol_blocks) { /* Verify the CRC. */ if (FlashCheckBlock(vol->vol_ifc, b)) { /* Bad block, remove it. */ FlashEraseEntry(vol->vol_ifc, b); /* Mark the volume fixed. */ vol->vol_attrib |= UFLASH_VOLF_FIXED; } else { /* Check if we already found another version. */ if (vol->vol_l2p[bh.bh_logblk] < vol->vol_blocks) { BLOCKHEAD bho; /* Read the previous entry. */ FlashReadBlockHead(vol->vol_ifc, vol->vol_l2p[bh.bh_logblk], &bho); /* Compare versions. */ if (bho.bh_version > bh.bh_version) { if (bho.bh_version - bh.bh_version > vol->vol_blocks) { /* The current version overflowed. */ FlashEraseEntry(vol->vol_ifc, b); } else { FlashEraseEntry(vol->vol_ifc, vol->vol_l2p[bh.bh_logblk]); vol->vol_l2p[bh.bh_logblk] = b; } } else if (bh.bh_version - bho.bh_version < vol->vol_blocks) { /* The current version is higher. */ FlashEraseEntry(vol->vol_ifc, b); } else { FlashEraseEntry(vol->vol_ifc, vol->vol_l2p[bh.bh_logblk]); vol->vol_l2p[bh.bh_logblk] = b; } /* Mark the volume fixed. */ vol->vol_attrib |= UFLASH_VOLF_FIXED; } else { /* First version of this block. */ vol->vol_l2p[bh.bh_logblk] = b; } } } } /* Initialize the mutex. */ NutEventPost(&vol->vol_mutex); return 0; } /*! * \brief Unmount a UFLASH volume. * * \param dev Specifies the file system device. * * \return Always 0. */ static int UFlashUnmount(NUTDEVICE * dev) { UFLASHVOLUME *vol; /* Sanity check. */ NUTASSERT(dev != NULL); /* Get the volume information structure. */ NUTASSERT(dev->dev_dcb != NULL); vol = (UFLASHVOLUME *) dev->dev_dcb; /* Release allocated heap space. */ NUTASSERT(vol->vol_l2p != NULL); free(vol->vol_l2p); free(vol); /* Mark the file system driver unmounted. */ dev->dev_dcb = NULL; return 0; } /*! * \brief Attach an UFLASH file system to a serial flash device. * * This function will initialize the hardware and mount the * volume. * * \param dev Specifies the file system device. * \param sfi Specifies the serial flash interface. * \param bus Specifies the SPI bus driver. * * \return 0 on success, -1 on errors. */ int UFlashAttach(NUTDEVICE * dev, NUTSERIALFLASH * sfi, NUTSPIBUS * bus) { /* Sanity checks. */ NUTASSERT(dev != NULL); NUTASSERT(sfi != NULL); NUTASSERT(sfi->sf_node != NULL); NUTASSERT(sfi->sf_init != NULL); NUTASSERT(bus != NULL); NUTASSERT(bus->bus_initnode != NULL); sfi->sf_node->node_bus = bus; if ((*bus->bus_initnode) (sfi->sf_node)) { return -1; } NutEventPost(&sfi->sf_node->node_bus->bus_mutex); if ((*sfi->sf_init) (sfi) == 0) { dev->dev_icb = (void *) sfi; if (UFlashMount(dev) == 0) { return 0; } dev->dev_icb = NULL; } return -1; } /*! * \brief Detach an UFLASH file system from a serial flash device. * * This function will unmount the volume and release all occupied resources. * * \param dev Specifies the file system device. * * \return 0 on success, -1 on errors. */ void UFlashDetach(NUTDEVICE * dev) { NUTSERIALFLASH *sfi; UFlashUnmount(dev); /* Release and detach the serial flash interface. */ sfi = (NUTSERIALFLASH *) dev->dev_icb; NUTASSERT(sfi != NULL); NUTASSERT(sfi->sf_exit != NULL); (*sfi->sf_exit) (sfi); dev->dev_icb = NULL; } /*! * \brief Format an UFLASH volume. * * Formatting an UFLASH volume is easy: All blocks are simply erased. * Therefore, formatting is required only to remove existing contents, * which is incompatible with the current format options. The caller * must make sure, that the volume is not mounted. * * \param dev Specifies the file system device. * \param sfi Specifies the serial flash interface. * \param bus Specifies the SPI bus driver. * * \return 0 on success, -1 on errors. */ int UFlashFormat(NUTDEVICE * dev, NUTSERIALFLASH * sfi, NUTSPIBUS * bus) { int rc; blknum_t n; /* Sanity checks. */ NUTASSERT(dev != NULL); NUTASSERT(sfi != NULL); NUTASSERT(dev->dev_dcb == NULL); sfi->sf_node->node_bus = bus; if ((*sfi->sf_node->node_bus->bus_initnode) (sfi->sf_node)) { return -1; } NutEventPost(&sfi->sf_node->node_bus->bus_mutex); rc = (*sfi->sf_init) (sfi); if (rc == 0) { n = (blknum_t) ((sfi->sf_units - sfi->sf_rsvbot - sfi->sf_rsvtop) / UFLASH_BLOCK_UNITS); if (n > UFLASH_MAX_BLOCKS) { n = UFLASH_MAX_BLOCKS; } FlashEraseBlocks(sfi, n); (*sfi->sf_exit) (sfi); } return 0; } /*! * \brief UFLASH specific functions. * * \param dev Identifies the file system device that receives the * control function. * \param req Requested control function. May be set to one of the * following constants: * - FS_STATUS * - FS_DIR_CREATE, ignored * - FS_DIR_REMOVE, ignored * - FS_DIR_OPEN * - FS_DIR_CLOSE * - FS_DIR_READ * - FS_FILE_DELETE * - FS_FILE_SEEK * - FS_RENAME * - FS_VOL_MOUNT * - FS_VOL_UNMOUNT * \param conf Points to a buffer that contains any data required for * the given control function or receives data from that * function. * * \return 0 on success, -1 otherwise. */ static int UFlashIOCtl(NUTDEVICE * dev, int req, void *conf) { int rc = -1; switch (req) { case FS_STATUS: NUTASSERT(conf != NULL); { FSCP_STATUS *par = (FSCP_STATUS *) conf; rc = UFlashFileStatus(dev, par->par_path, par->par_stp); } break; case FS_DIR_CREATE: rc = 0; break; case FS_DIR_REMOVE: rc = 0; break; case FS_DIR_OPEN: /* Open a directory for reading entries. */ NUTASSERT(conf != NULL); { DIR *dir = (DIR *) conf; if ((dir->dd_fd = UFlashDirOpen(dev, dir->dd_buf)) != NUTFILE_EOF) { rc = 0; } } break; case FS_DIR_CLOSE: NUTASSERT(conf != NULL); rc = UFlashDirClose(((DIR *) conf)->dd_fd); break; case FS_DIR_READ: rc = UFlashDirRead((DIR *) conf); break; case FS_FILE_DELETE: rc = UFlashFileRemove(dev, (char *) conf); break; case FS_FILE_SEEK: NUTASSERT(conf != NULL); UFlashFileSeek((NUTFILE *) ((IOCTL_ARG3 *) conf)->arg1, /* */ (long *) ((IOCTL_ARG3 *) conf)->arg2, /* */ (int) ((IOCTL_ARG3 *) conf)->arg3); break; case FS_RENAME: /* Rename an existing file. */ NUTASSERT(conf != NULL); { FSCP_RENAME *par = (FSCP_RENAME *) conf; rc = UFlashFileRename(dev, par->par_old, par->par_new); } break; case FS_VOL_MOUNT: /* Mount a volume. */ rc = UFlashMount(dev); break; case FS_VOL_UNMOUNT: /* Unmount a volume. */ rc = UFlashUnmount(dev); break; } return rc; } /*! * \brief Initialize the UFLASH device file system driver. * * This routine is called during device registration. * * \param dev Specifies the file system device. * * \return Zero on success. Otherwise an error code is returned. */ static int UFlashInit(NUTDEVICE * dev) { return 0; } /*! * \brief UFLASH0 file system device. */ NUTDEVICE devUFlash0 = { 0, /*!< \brief Pointer to next device, dev_next. */ {'U', 'F', 'L', 'A', 'S', 'H', '0', 0, 0} , /*!< \brief Unique device name, dev_name. */ IFTYP_FS, /*!< \brief Type of device, dev_type. Obsolete. */ 0, /*!< \brief Chip select, dev_base. */ 0, /*!< \brief First interrupt number, dev_irq. Unused. */ NULL, /*!< \brief Attached serial flash interface, dev_icb. */ NULL, /*!< \brief Mounted volume information, dev_dcb. */ UFlashInit, /*!< \brief Driver initialization routine, dev_init. */ UFlashIOCtl, /*!< \brief Driver specific control function, dev_ioctl. */ UFlashFileRead, /*!< \brief Read data from a file, dev_read. */ UFlashFileWrite, /*!< \brief Write data to a file, dev_write. */ #ifdef __HARVARD_ARCH__ UFlashFileWrite_P, /*!< \brief Write data from program space to a file, dev_write_P. */ #endif UFlashFileOpen, /*!< \brief Open a file, dev_open. */ UFlashFileClose, /*!< \brief Close a file, dev_close. */ UFlashFileSize, /*!< \brief Return file size, dev_size. */ NULL, /*!< \brief Select function, optional, not yet implemented */ }; /*! * \brief UFLASH1 file system device. */ NUTDEVICE devUFlash1 = { 0, /*!< \brief Pointer to next device, dev_next. */ {'U', 'F', 'L', 'A', 'S', 'H', '1', 0, 0} , /*!< \brief Unique device name, dev_name. */ IFTYP_FS, /*!< \brief Type of device, dev_type. Obsolete. */ 1, /*!< \brief Chip select, dev_base. */ 0, /*!< \brief First interrupt number, dev_irq. Unused. */ NULL, /*!< \brief Attached serial flash interface, dev_icb. */ NULL, /*!< \brief Mounted volume information, dev_dcb. */ UFlashInit, /*!< \brief Driver initialization routine, dev_init. */ UFlashIOCtl, /*!< \brief Driver specific control function, dev_ioctl. */ UFlashFileRead, /*!< \brief Read data from a file, dev_read. */ UFlashFileWrite, /*!< \brief Write data to a file, dev_write. */ #ifdef __HARVARD_ARCH__ UFlashFileWrite_P, /*!< \brief Write data from program space to a file, dev_write_P. */ #endif UFlashFileOpen, /*!< \brief Open a file, dev_open. */ UFlashFileClose, /*!< \brief Close a file, dev_close. */ UFlashFileSize, /*!< \brief Return file size, dev_size. */ NULL, /*!< \brief Select function, optional, not yet implemented */ }; /*@}*/