|
@@ -0,0 +1,1591 @@
|
|
|
+/*
|
|
|
+ This file is part of UFFS, the Ultra-low-cost Flash File System.
|
|
|
+
|
|
|
+ Copyright (C) 2005-2009 Ricky Zheng <ricky_gz_zheng@yahoo.co.nz>
|
|
|
+
|
|
|
+ UFFS is free software; you can redistribute it and/or modify it under
|
|
|
+ the GNU Library General Public License as published by the Free Software
|
|
|
+ Foundation; either version 2 of the License, or (at your option) any
|
|
|
+ later version.
|
|
|
+
|
|
|
+ UFFS is distributed in the hope that it will be useful, but WITHOUT
|
|
|
+ ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
|
|
+ FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
|
|
+ or GNU Library General Public License, as applicable, for more details.
|
|
|
+
|
|
|
+ You should have received a copy of the GNU General Public License
|
|
|
+ and GNU Library General Public License along with UFFS; if not, write
|
|
|
+ to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
|
|
|
+ Boston, MA 02110-1301, USA.
|
|
|
+
|
|
|
+ As a special exception, if other files instantiate templates or use
|
|
|
+ macros or inline functions from this file, or you compile this file
|
|
|
+ and link it with other works to produce a work based on this file,
|
|
|
+ this file does not by itself cause the resulting work to be covered
|
|
|
+ by the GNU General Public License. However the source code for this
|
|
|
+ file must still be made available in accordance with section (3) of
|
|
|
+ the GNU General Public License v2.
|
|
|
+
|
|
|
+ This exception does not invalidate any other reasons why a work based
|
|
|
+ on this file might be covered by the GNU General Public License.
|
|
|
+*/
|
|
|
+/**
|
|
|
+ * \file uffs_buf.c
|
|
|
+ * \brief uffs page buffers manipulations
|
|
|
+ * \author Ricky Zheng
|
|
|
+ * \note Created in 11th May, 2005
|
|
|
+ */
|
|
|
+
|
|
|
+#include "uffs/uffs_types.h"
|
|
|
+#include "uffs/uffs_buf.h"
|
|
|
+#include "uffs/uffs_device.h"
|
|
|
+#include "uffs/uffs_os.h"
|
|
|
+#include "uffs/uffs_public.h"
|
|
|
+#include "uffs/uffs_pool.h"
|
|
|
+#include "uffs/uffs_ecc.h"
|
|
|
+#include "uffs/uffs_badblock.h"
|
|
|
+#include <string.h>
|
|
|
+
|
|
|
+#define PFX "pbuf: "
|
|
|
+
|
|
|
+
|
|
|
+URET _BufFlush(struct uffs_DeviceSt *dev, UBOOL force_block_recover, int slot);
|
|
|
+
|
|
|
+
|
|
|
+/**
|
|
|
+ * \brief inspect (print) uffs page buffers.
|
|
|
+ * \param[in] dev uffs device to be inspected.
|
|
|
+ */
|
|
|
+void uffs_BufInspect(uffs_Device *dev)
|
|
|
+{
|
|
|
+ struct uffs_PageBufDescSt *pb = &dev->buf;
|
|
|
+ uffs_Buf *buf;
|
|
|
+
|
|
|
+ uffs_PerrorRaw(UFFS_ERR_NORMAL, "------------- page buffer inspect ---------" TENDSTR);
|
|
|
+ uffs_PerrorRaw(UFFS_ERR_NORMAL, "all buffers: " TENDSTR);
|
|
|
+ for (buf = pb->head; buf; buf = buf->next) {
|
|
|
+ if (buf->mark != 0) {
|
|
|
+ uffs_PerrorRaw(UFFS_ERR_NORMAL, "\tF:%04x S:%04x P:%02d R:%02d D:%03d M:%c EM:%d" TENDSTR,
|
|
|
+ buf->parent, buf->serial, buf->page_id, buf->ref_count, buf->data_len, buf->mark == UFFS_BUF_VALID ? 'V' : 'D', buf->ext_mark);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ uffs_PerrorRaw(UFFS_ERR_NORMAL, "--------------------------------------------" TENDSTR);
|
|
|
+}
|
|
|
+
|
|
|
+/**
|
|
|
+ * \brief initialize page buffers for device
|
|
|
+ * in UFFS, each device has one buffer pool
|
|
|
+ * \param[in] dev uffs device
|
|
|
+ * \param[in] buf_max maximum buffer number, normally use #MAX_PAGE_BUFFERS
|
|
|
+ * \param[in] dirty_buf_max maximum dirty buffer allowed, if the dirty buffer over this number,
|
|
|
+ * than need to be flush to flash
|
|
|
+ */
|
|
|
+URET uffs_BufInit(uffs_Device *dev, int buf_max, int dirty_buf_max)
|
|
|
+{
|
|
|
+ void *pool;
|
|
|
+ u8 *data;
|
|
|
+ uffs_Buf *buf;
|
|
|
+ int size;
|
|
|
+ int i, slot;
|
|
|
+
|
|
|
+ if (!dev)
|
|
|
+ return U_FAIL;
|
|
|
+
|
|
|
+ //init device common parameters, which are needed by page buffers
|
|
|
+ dev->com.pg_size = dev->attr->page_data_size; // we use the whole page.
|
|
|
+ dev->com.header_size = sizeof(struct uffs_MiniHeaderSt); // mini header
|
|
|
+ dev->com.pg_data_size = dev->com.pg_size - dev->com.header_size;
|
|
|
+
|
|
|
+ if (dev->buf.pool != NULL) {
|
|
|
+ uffs_Perror(UFFS_ERR_NORMAL, "buf.pool is not NULL, buf already inited ?");
|
|
|
+ return U_FAIL;
|
|
|
+ }
|
|
|
+
|
|
|
+ size = (sizeof(uffs_Buf) + dev->com.pg_size) * buf_max;
|
|
|
+ if (dev->mem.pagebuf_pool_size == 0) {
|
|
|
+ if (dev->mem.malloc) {
|
|
|
+ dev->mem.pagebuf_pool_buf = dev->mem.malloc(dev, size);
|
|
|
+ if (dev->mem.pagebuf_pool_buf)
|
|
|
+ dev->mem.pagebuf_pool_size = size;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ if (size > dev->mem.pagebuf_pool_size) {
|
|
|
+ uffs_Perror(UFFS_ERR_DEAD, "page buffers require %d but only %d available.", size, dev->mem.pagebuf_pool_size);
|
|
|
+ return U_FAIL;
|
|
|
+ }
|
|
|
+ pool = dev->mem.pagebuf_pool_buf;
|
|
|
+
|
|
|
+ uffs_Perror(UFFS_ERR_NOISY, "alloc %d bytes.", size);
|
|
|
+ dev->buf.pool = pool;
|
|
|
+
|
|
|
+ for (i = 0; i < buf_max; i++) {
|
|
|
+ buf = (uffs_Buf *)((u8 *)pool + (sizeof(uffs_Buf) * i));
|
|
|
+ memset(buf, 0, sizeof(uffs_Buf));
|
|
|
+ data = (u8 *)pool + (sizeof(uffs_Buf) * buf_max) + (dev->com.pg_size * i);
|
|
|
+ buf->header = data;
|
|
|
+ buf->data = data + dev->com.header_size;
|
|
|
+ buf->mark = UFFS_BUF_EMPTY;
|
|
|
+ memset(buf->header, 0, dev->com.pg_size);
|
|
|
+ if (i == 0) {
|
|
|
+ buf->prev = NULL;
|
|
|
+ dev->buf.head = buf;
|
|
|
+ }
|
|
|
+ else {
|
|
|
+ buf->prev = (uffs_Buf *)((u8 *)buf - sizeof(uffs_Buf));
|
|
|
+ }
|
|
|
+
|
|
|
+ if (i == (buf_max - 1)) {
|
|
|
+ buf->next = NULL;
|
|
|
+ dev->buf.tail = buf;
|
|
|
+ }
|
|
|
+ else {
|
|
|
+ buf->next = (uffs_Buf *)((u8 *)buf + sizeof(uffs_Buf));
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ dev->buf.buf_max = buf_max;
|
|
|
+ dev->buf.dirty_buf_max = (dirty_buf_max > dev->attr->pages_per_block ? dev->attr->pages_per_block : dirty_buf_max);
|
|
|
+
|
|
|
+ for (slot = 0; slot < MAX_DIRTY_BUF_GROUPS; slot++) {
|
|
|
+ dev->buf.dirtyGroup[slot].dirty = NULL;
|
|
|
+ dev->buf.dirtyGroup[slot].count = 0;
|
|
|
+ }
|
|
|
+ return U_SUCC;
|
|
|
+}
|
|
|
+
|
|
|
+/**
|
|
|
+ * \brief flush all buffers
|
|
|
+ */
|
|
|
+URET uffs_BufFlushAll(struct uffs_DeviceSt *dev)
|
|
|
+{
|
|
|
+ int slot;
|
|
|
+ for (slot = 0; slot < MAX_DIRTY_BUF_GROUPS; slot++) {
|
|
|
+ if(_BufFlush(dev, FALSE, slot) != U_SUCC) {
|
|
|
+ uffs_Perror(UFFS_ERR_NORMAL, "fail to flush buffer(slot %d)", slot);
|
|
|
+ return U_FAIL;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return U_SUCC;
|
|
|
+}
|
|
|
+
|
|
|
+/**
|
|
|
+ * \brief release all page buffer, this function should be called
|
|
|
+ when unmounting a uffs device
|
|
|
+ * \param[in] dev uffs device
|
|
|
+ * \note if there are page buffers in used, it may cause fail to release
|
|
|
+ */
|
|
|
+URET uffs_BufReleaseAll(uffs_Device *dev)
|
|
|
+{
|
|
|
+ uffs_Buf *p;
|
|
|
+
|
|
|
+ if (!dev)
|
|
|
+ return U_FAIL;
|
|
|
+
|
|
|
+ //now release all buffer
|
|
|
+ p = dev->buf.head;
|
|
|
+ while (p) {
|
|
|
+ if (p->ref_count != 0) {
|
|
|
+ uffs_Perror(UFFS_ERR_NORMAL,
|
|
|
+ PFX "can't release buffers, \
|
|
|
+ parent:%d, serial:%d, page_id:%d still in used.\n", p->parent, p->serial, p->page_id);
|
|
|
+ return U_FAIL;
|
|
|
+ }
|
|
|
+ p = p->next;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (uffs_BufFlushAll(dev) != U_SUCC) {
|
|
|
+ uffs_Perror(UFFS_ERR_NORMAL, "can't release buf, fail to flush buffer");
|
|
|
+ return U_FAIL;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (dev->mem.free)
|
|
|
+ dev->mem.free(dev, dev->buf.pool);
|
|
|
+
|
|
|
+ dev->buf.pool = NULL;
|
|
|
+ dev->buf.head = dev->buf.tail = NULL;
|
|
|
+
|
|
|
+ return U_SUCC;
|
|
|
+}
|
|
|
+
|
|
|
+
|
|
|
+static void _BreakFromBufList(uffs_Device *dev, uffs_Buf *buf)
|
|
|
+{
|
|
|
+ if(buf->next)
|
|
|
+ buf->next->prev = buf->prev;
|
|
|
+
|
|
|
+ if(buf->prev)
|
|
|
+ buf->prev->next = buf->next;
|
|
|
+
|
|
|
+ if(dev->buf.head == buf)
|
|
|
+ dev->buf.head = buf->next;
|
|
|
+
|
|
|
+ if(dev->buf.tail == buf)
|
|
|
+ dev->buf.tail = buf->prev;
|
|
|
+
|
|
|
+}
|
|
|
+
|
|
|
+static void _LinkToBufListHead(uffs_Device *dev, uffs_Buf *buf)
|
|
|
+{
|
|
|
+ if (buf == dev->buf.head)
|
|
|
+ return;
|
|
|
+
|
|
|
+ buf->prev = NULL;
|
|
|
+ buf->next = dev->buf.head;
|
|
|
+
|
|
|
+ if (dev->buf.head)
|
|
|
+ dev->buf.head->prev = buf;
|
|
|
+
|
|
|
+ if (dev->buf.tail == NULL)
|
|
|
+ dev->buf.tail = buf;
|
|
|
+
|
|
|
+ dev->buf.head = buf;
|
|
|
+}
|
|
|
+
|
|
|
+static void _LinkToBufListTail(uffs_Device *dev, uffs_Buf *buf)
|
|
|
+{
|
|
|
+ if (dev->buf.tail == buf)
|
|
|
+ return;
|
|
|
+
|
|
|
+ buf->prev = dev->buf.tail;
|
|
|
+ buf->next = NULL;
|
|
|
+
|
|
|
+ if (dev->buf.tail)
|
|
|
+ dev->buf.tail->next = buf;
|
|
|
+
|
|
|
+ if (dev->buf.head == NULL)
|
|
|
+ dev->buf.head = buf;
|
|
|
+
|
|
|
+ dev->buf.tail = buf;
|
|
|
+}
|
|
|
+
|
|
|
+//move a node which linked in the list to the head of list
|
|
|
+static void _MoveNodeToHead(uffs_Device *dev, uffs_Buf *p)
|
|
|
+{
|
|
|
+ if (p == dev->buf.head)
|
|
|
+ return;
|
|
|
+
|
|
|
+ //break from list
|
|
|
+ _BreakFromBufList(dev, p);
|
|
|
+
|
|
|
+ //link to head
|
|
|
+ _LinkToBufListHead(dev, p);
|
|
|
+}
|
|
|
+
|
|
|
+// check if the buf is already in dirty list
|
|
|
+static UBOOL _IsBufInInDirtyList(uffs_Device *dev, int slot, uffs_Buf *buf)
|
|
|
+{
|
|
|
+ uffs_Buf *work;
|
|
|
+ work = dev->buf.dirtyGroup[slot].dirty;
|
|
|
+ while (work) {
|
|
|
+ if (work == buf)
|
|
|
+ return U_TRUE;
|
|
|
+ work = work->next_dirty;
|
|
|
+ }
|
|
|
+
|
|
|
+ return U_FALSE;
|
|
|
+}
|
|
|
+
|
|
|
+static void _LinkToDirtyList(uffs_Device *dev, int slot, uffs_Buf *buf)
|
|
|
+{
|
|
|
+
|
|
|
+ if (buf == NULL) {
|
|
|
+ uffs_Perror(UFFS_ERR_SERIOUS, "Try to insert a NULL node into dirty list ?");
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ buf->mark = UFFS_BUF_DIRTY;
|
|
|
+ buf->prev_dirty = NULL;
|
|
|
+ buf->next_dirty = dev->buf.dirtyGroup[slot].dirty;
|
|
|
+
|
|
|
+ if (dev->buf.dirtyGroup[slot].dirty)
|
|
|
+ dev->buf.dirtyGroup[slot].dirty->prev_dirty = buf;
|
|
|
+
|
|
|
+ dev->buf.dirtyGroup[slot].dirty = buf;
|
|
|
+ dev->buf.dirtyGroup[slot].count++;
|
|
|
+}
|
|
|
+
|
|
|
+static int CountFreeBuf(uffs_Device *dev)
|
|
|
+{
|
|
|
+ int count = 0;
|
|
|
+
|
|
|
+ uffs_Buf *buf = dev->buf.head;
|
|
|
+
|
|
|
+ while (buf) {
|
|
|
+
|
|
|
+ if (buf->ref_count == 0 &&
|
|
|
+ buf->mark != UFFS_BUF_DIRTY)
|
|
|
+ count++;
|
|
|
+
|
|
|
+ buf = buf->next;
|
|
|
+ }
|
|
|
+
|
|
|
+ return count;
|
|
|
+}
|
|
|
+
|
|
|
+static uffs_Buf * _FindFreeBufEx(uffs_Device *dev, int clone)
|
|
|
+{
|
|
|
+ uffs_Buf *buf;
|
|
|
+
|
|
|
+ if (!clone && CountFreeBuf(dev) <= CLONE_BUFFERS_THRESHOLD)
|
|
|
+ return NULL;
|
|
|
+
|
|
|
+#if 1
|
|
|
+ buf = dev->buf.head;
|
|
|
+ while (buf) {
|
|
|
+
|
|
|
+ if (buf->ref_count == 0 &&
|
|
|
+ buf->mark != UFFS_BUF_DIRTY)
|
|
|
+ return buf;
|
|
|
+
|
|
|
+ buf = buf->next;
|
|
|
+ }
|
|
|
+#else
|
|
|
+ buf = dev->buf.tail;
|
|
|
+ while (buf) {
|
|
|
+
|
|
|
+ if(buf->ref_count == 0 &&
|
|
|
+ buf->mark != UFFS_BUF_DIRTY)
|
|
|
+ return buf;
|
|
|
+
|
|
|
+ buf = buf->prev;
|
|
|
+ }
|
|
|
+#endif
|
|
|
+
|
|
|
+ return buf;
|
|
|
+}
|
|
|
+
|
|
|
+static uffs_Buf * _FindFreeBuf(uffs_Device *dev)
|
|
|
+{
|
|
|
+ return _FindFreeBufEx(dev, 0);
|
|
|
+}
|
|
|
+
|
|
|
+
|
|
|
+/**
|
|
|
+ * load psychical page data into buf and do ecc check
|
|
|
+ * \param[in] dev uffs device
|
|
|
+ * \param[in] buf buf to be load in
|
|
|
+ * \param[in] block psychical block number
|
|
|
+ * \param[in] page psychical page number
|
|
|
+ * \return return U_SUCC if no error, return U_FAIL if I/O error or ecc check fail
|
|
|
+ */
|
|
|
+URET uffs_BufLoadPhyData(uffs_Device *dev, uffs_Buf *buf, u32 block, u32 page)
|
|
|
+{
|
|
|
+ int ret;
|
|
|
+
|
|
|
+ ret = uffs_FlashReadPage(dev, block, page, buf);
|
|
|
+
|
|
|
+ if (UFFS_FLASH_HAVE_ERR(ret)) {
|
|
|
+ buf->mark = UFFS_BUF_EMPTY;
|
|
|
+ return U_FAIL;
|
|
|
+ }
|
|
|
+ else {
|
|
|
+ buf->mark = UFFS_BUF_VALID;
|
|
|
+ return U_SUCC;
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+/**
|
|
|
+ * \brief load psychical page data into buf and ignore ECC result
|
|
|
+ *
|
|
|
+ * \param[in] dev uffs device
|
|
|
+ * \param[in] buf buf to be load in
|
|
|
+ * \param[in] block psychical block number
|
|
|
+ * \param[in] page psychical page number
|
|
|
+ *
|
|
|
+ * \return return U_SUCC if no error, return U_FAIL if I/O error
|
|
|
+ * \note this function should be only used when doing bad block recover.
|
|
|
+ */
|
|
|
+URET uffs_LoadPhyDataToBufEccUnCare(uffs_Device *dev, uffs_Buf *buf, u32 block, u32 page)
|
|
|
+{
|
|
|
+ int ret;
|
|
|
+
|
|
|
+ ret = uffs_FlashReadPage(dev, block, page, buf);
|
|
|
+
|
|
|
+ if (ret == UFFS_FLASH_IO_ERR) {
|
|
|
+ buf->mark = UFFS_BUF_EMPTY;
|
|
|
+ return U_FAIL;
|
|
|
+ }
|
|
|
+ else {
|
|
|
+ buf->mark = UFFS_BUF_VALID;
|
|
|
+ return U_SUCC;
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+
|
|
|
+/**
|
|
|
+ * find a buffer in the pool
|
|
|
+ * \param[in] dev uffs device
|
|
|
+ * \param[in] parent parent serial num
|
|
|
+ * \param[in] serial serial num
|
|
|
+ * \param[in] page_id page_id
|
|
|
+ * \return return found buffer, return NULL if buffer not found
|
|
|
+ */
|
|
|
+uffs_Buf * uffs_BufFind(uffs_Device *dev, u16 parent, u16 serial, u16 page_id)
|
|
|
+{
|
|
|
+ uffs_Buf *p = dev->buf.head;
|
|
|
+
|
|
|
+ while (p) {
|
|
|
+ if( p->parent == parent &&
|
|
|
+ p->serial == serial &&
|
|
|
+ p->page_id == page_id &&
|
|
|
+ p->mark != UFFS_BUF_EMPTY)
|
|
|
+ {
|
|
|
+ //they have match one
|
|
|
+ return p;
|
|
|
+ }
|
|
|
+ p = p->next;
|
|
|
+ }
|
|
|
+
|
|
|
+ return NULL; //buffer not found
|
|
|
+}
|
|
|
+
|
|
|
+static uffs_Buf * _FindBufInDirtyList(uffs_Buf *dirty, u16 page_id)
|
|
|
+{
|
|
|
+ while(dirty) {
|
|
|
+ if (dirty->page_id == page_id)
|
|
|
+ return dirty;
|
|
|
+ dirty = dirty->next_dirty;
|
|
|
+ }
|
|
|
+ return NULL;
|
|
|
+}
|
|
|
+
|
|
|
+static URET _BreakFromDirty(uffs_Device *dev, uffs_Buf *dirtyBuf)
|
|
|
+{
|
|
|
+ int slot = -1;
|
|
|
+
|
|
|
+ if (dirtyBuf->mark != UFFS_BUF_DIRTY) {
|
|
|
+ uffs_Perror(UFFS_ERR_NORMAL, "try to break a non-dirty buf from dirty list ?");
|
|
|
+ return U_FAIL;
|
|
|
+ }
|
|
|
+
|
|
|
+ slot = uffs_BufFindGroupSlot(dev, dirtyBuf->parent, dirtyBuf->serial);
|
|
|
+ if (slot < 0) {
|
|
|
+ uffs_Perror(UFFS_ERR_NORMAL, "no dirty list exit ?");
|
|
|
+ return U_FAIL;
|
|
|
+ }
|
|
|
+
|
|
|
+ // break from the link
|
|
|
+ if (dirtyBuf->next_dirty) {
|
|
|
+ dirtyBuf->next_dirty->prev_dirty = dirtyBuf->prev_dirty;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (dirtyBuf->prev_dirty) {
|
|
|
+ dirtyBuf->prev_dirty->next_dirty = dirtyBuf->next_dirty;
|
|
|
+ }
|
|
|
+
|
|
|
+ // check if it's the link head ...
|
|
|
+ if (dev->buf.dirtyGroup[slot].dirty == dirtyBuf) {
|
|
|
+ dev->buf.dirtyGroup[slot].dirty = dirtyBuf->next_dirty;
|
|
|
+ }
|
|
|
+
|
|
|
+ dirtyBuf->next_dirty = dirtyBuf->prev_dirty = NULL; // clear dirty link
|
|
|
+
|
|
|
+ dev->buf.dirtyGroup[slot].count--;
|
|
|
+
|
|
|
+ return U_SUCC;
|
|
|
+}
|
|
|
+
|
|
|
+static u16 _GetDirOrFileNameSum(uffs_Device *dev, uffs_Buf *buf)
|
|
|
+{
|
|
|
+ u16 data_sum = 0; //default: 0
|
|
|
+ uffs_FileInfo *fi;
|
|
|
+
|
|
|
+ dev = dev;
|
|
|
+ //FIXME: We use the same schema for both dir and file.
|
|
|
+ if (buf->type == UFFS_TYPE_FILE || buf->type == UFFS_TYPE_DIR) {
|
|
|
+ if (buf->page_id == 0) {
|
|
|
+ fi = (uffs_FileInfo *)(buf->data);
|
|
|
+ data_sum = uffs_MakeSum16(fi->name, fi->name_len);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ return data_sum;
|
|
|
+}
|
|
|
+
|
|
|
+
|
|
|
+static URET _CheckDirtyList(uffs_Buf *dirty)
|
|
|
+{
|
|
|
+ u16 parent;
|
|
|
+ u16 serial;
|
|
|
+
|
|
|
+ if (dirty == NULL) {
|
|
|
+ return U_SUCC;
|
|
|
+ }
|
|
|
+
|
|
|
+ parent = dirty->parent;
|
|
|
+ serial = dirty->serial;
|
|
|
+ dirty = dirty->next_dirty;
|
|
|
+
|
|
|
+ while (dirty) {
|
|
|
+ if (parent != dirty->parent ||
|
|
|
+ serial != dirty->serial) {
|
|
|
+ uffs_Perror(UFFS_ERR_SERIOUS, "parent or serial in dirty pages buffer are not the same ?");
|
|
|
+ return U_FAIL;
|
|
|
+ }
|
|
|
+ if (dirty->mark != UFFS_BUF_DIRTY) {
|
|
|
+ uffs_Perror(UFFS_ERR_SERIOUS, "non-dirty page buffer in dirty buffer list ?");
|
|
|
+ return U_FAIL;
|
|
|
+ }
|
|
|
+ dirty = dirty->next_dirty;
|
|
|
+ }
|
|
|
+ return U_SUCC;
|
|
|
+}
|
|
|
+
|
|
|
+/** find a page in dirty list, which has minimum page_id */
|
|
|
+uffs_Buf * _FindMinimunPageIdFromDirtyList(uffs_Buf *dirtyList)
|
|
|
+{
|
|
|
+ uffs_Buf * work = dirtyList;
|
|
|
+ uffs_Buf * buf = dirtyList;
|
|
|
+
|
|
|
+ work = work->next_dirty;
|
|
|
+ while (work) {
|
|
|
+ if (work->page_id < buf->page_id)
|
|
|
+ buf = work;
|
|
|
+ work = work->next_dirty;
|
|
|
+ }
|
|
|
+ return buf;
|
|
|
+}
|
|
|
+
|
|
|
+
|
|
|
+/**
|
|
|
+ * \brief flush buffer with block recover
|
|
|
+ *
|
|
|
+ * Scenario:
|
|
|
+ * 1. get a free (erased) block --> newNode <br>
|
|
|
+ * 2. copy from old block ---> oldNode, or copy from dirty list, <br>
|
|
|
+ * sorted by page_id, to new block. Skips the invalid pages when copy pages.<br>
|
|
|
+ * 3. erased old block. set new info to oldNode, set newNode->block = old block,<br>
|
|
|
+ * and put newNode to erased list.<br>
|
|
|
+ * \note IT'S IMPORTANT TO KEEP OLD NODE IN THE LIST, so you don't need to update the obj->node :-)
|
|
|
+ */
|
|
|
+static URET uffs_BufFlush_Exist_With_BlockCover(
|
|
|
+ uffs_Device *dev,
|
|
|
+ int slot, //!< dirty group slot
|
|
|
+ TreeNode *node, //!< old data node on tree
|
|
|
+ uffs_BlockInfo *bc //!< old data block info
|
|
|
+ )
|
|
|
+{
|
|
|
+ u16 i;
|
|
|
+ u8 type, timeStamp;
|
|
|
+ u16 page, parent, serial;
|
|
|
+ uffs_Buf *buf;
|
|
|
+ TreeNode *newNode;
|
|
|
+ uffs_BlockInfo *newBc;
|
|
|
+ uffs_Tags *tag, *oldTag;
|
|
|
+ int x;
|
|
|
+ u16 newBlock;
|
|
|
+ UBOOL succRecover; //U_TRUE: recover successful, erase old block,
|
|
|
+ //U_FALSE: fail to recover, erase new block
|
|
|
+ UBOOL flash_op_err;
|
|
|
+ u16 data_sum;
|
|
|
+
|
|
|
+ type = dev->buf.dirtyGroup[slot].dirty->type;
|
|
|
+ parent = dev->buf.dirtyGroup[slot].dirty->parent;
|
|
|
+ serial = dev->buf.dirtyGroup[slot].dirty->serial;
|
|
|
+
|
|
|
+retry:
|
|
|
+ flash_op_err = UFFS_FLASH_NO_ERR;
|
|
|
+ succRecover = U_FALSE;
|
|
|
+
|
|
|
+ newNode = uffs_TreeGetErasedNode(dev);
|
|
|
+ if (newNode == NULL) {
|
|
|
+ uffs_Perror(UFFS_ERR_NOISY, "no enough erased block!");
|
|
|
+ goto ext;
|
|
|
+ }
|
|
|
+ newBlock = newNode->u.list.block;
|
|
|
+ newBc = uffs_BlockInfoGet(dev, newBlock);
|
|
|
+ if (newBc == NULL) {
|
|
|
+ uffs_Perror(UFFS_ERR_SERIOUS, "get block info fail!");
|
|
|
+ uffs_InsertToErasedListHead(dev, newNode); //put node back to erased list
|
|
|
+ //because it doesn't use, so put to head
|
|
|
+ goto ext;
|
|
|
+ }
|
|
|
+
|
|
|
+ uffs_BlockInfoLoad(dev, newBc, UFFS_ALL_PAGES);
|
|
|
+ timeStamp = uffs_GetNextBlockTimeStamp(uffs_GetBlockTimeStamp(dev, bc));
|
|
|
+
|
|
|
+// uffs_Perror(UFFS_ERR_NOISY, "Flush buffers with Block Recover, from %d to %d",
|
|
|
+// bc->block, newBc->block);
|
|
|
+
|
|
|
+ for (i = 0; i < dev->attr->pages_per_block; i++) {
|
|
|
+ tag = GET_TAG(newBc, i);
|
|
|
+ TAG_BLOCK_TS(tag) = timeStamp;
|
|
|
+ TAG_PARENT(tag) = parent;
|
|
|
+ TAG_SERIAL(tag) = serial;
|
|
|
+ TAG_TYPE(tag) = type;
|
|
|
+ TAG_PAGE_ID(tag) = (u8)i; //now, page_id = page, FIX ME!! if more than 256 pages in a block
|
|
|
+
|
|
|
+ buf = _FindBufInDirtyList(dev->buf.dirtyGroup[slot].dirty, i);
|
|
|
+ if (buf != NULL) {
|
|
|
+ if (i == 0)
|
|
|
+ data_sum = _GetDirOrFileNameSum(dev, buf);
|
|
|
+
|
|
|
+ TAG_DATA_LEN(tag) = buf->data_len;
|
|
|
+
|
|
|
+ if (buf->data_len == 0) // this could happen when truncating a file
|
|
|
+ flash_op_err = UFFS_FLASH_NO_ERR;
|
|
|
+ else
|
|
|
+ flash_op_err = uffs_FlashWritePageCombine(dev, newBlock, i, buf, tag);
|
|
|
+
|
|
|
+ if (flash_op_err == UFFS_FLASH_BAD_BLK) {
|
|
|
+ uffs_Perror(UFFS_ERR_NORMAL, "new bad block %d discovered.", newBlock);
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ else if (flash_op_err == UFFS_FLASH_IO_ERR) {
|
|
|
+ uffs_Perror(UFFS_ERR_NORMAL, "writing to block %d page %d, I/O error ?", (int)newBlock, (int)i);
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ else if (buf->ext_mark & UFFS_BUF_EXT_MARK_TRUNC_TAIL) {
|
|
|
+ // when truncating a file, the last dirty buf will be set as UFFS_BUF_EXT_MARK_TAIL.
|
|
|
+ // so that we don't do page recovery for the rest pages in the block.
|
|
|
+ uffs_BlockInfoExpire(dev, newBc, i);
|
|
|
+ succRecover = U_TRUE;
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ else {
|
|
|
+ page = uffs_FindPageInBlockWithPageId(dev, bc, i);
|
|
|
+ if (page == UFFS_INVALID_PAGE) {
|
|
|
+ uffs_BlockInfoExpire(dev, newBc, i);
|
|
|
+ succRecover = U_TRUE;
|
|
|
+ break; //end of last page, normal break
|
|
|
+ }
|
|
|
+ page = uffs_FindBestPageInBlock(dev, bc, page);
|
|
|
+
|
|
|
+ oldTag = GET_TAG(bc, page);
|
|
|
+ buf = uffs_BufClone(dev, NULL);
|
|
|
+ if (buf == NULL) {
|
|
|
+ uffs_Perror(UFFS_ERR_SERIOUS, "Can't clone a new buf!");
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ x = uffs_BufLoadPhyData(dev, buf, bc->block, page);
|
|
|
+ if (x == U_FAIL) {
|
|
|
+ if (HAVE_BADBLOCK(dev) && dev->bad.block == bc->block) {
|
|
|
+ // the old block is a bad block, we'll process it later.
|
|
|
+ uffs_Perror(UFFS_ERR_SERIOUS, "the old block %d is a bad block, but ignore it for now.", bc->block);
|
|
|
+ }
|
|
|
+ else {
|
|
|
+ uffs_Perror(UFFS_ERR_SERIOUS, "I/O error ?");
|
|
|
+ uffs_BufFreeClone(dev, buf);
|
|
|
+ flash_op_err = UFFS_FLASH_IO_ERR;
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ buf->data_len = TAG_DATA_LEN(oldTag);
|
|
|
+ if (buf->data_len > dev->com.pg_data_size) {
|
|
|
+ uffs_Perror(UFFS_ERR_NOISY, "data length over flow!!!");
|
|
|
+ buf->data_len = dev->com.pg_data_size;
|
|
|
+ }
|
|
|
+
|
|
|
+ buf->type = type;
|
|
|
+ buf->parent = parent;
|
|
|
+ buf->serial = serial;
|
|
|
+ buf->data_len = TAG_DATA_LEN(oldTag);
|
|
|
+ buf->page_id = TAG_PAGE_ID(oldTag);
|
|
|
+
|
|
|
+ TAG_DATA_LEN(tag) = buf->data_len;
|
|
|
+ if (i == 0)
|
|
|
+ data_sum = _GetDirOrFileNameSum(dev, buf);
|
|
|
+
|
|
|
+ flash_op_err = uffs_FlashWritePageCombine(dev, newBlock, i, buf, tag);
|
|
|
+ uffs_BufFreeClone(dev, buf);
|
|
|
+ if (flash_op_err == UFFS_FLASH_BAD_BLK) {
|
|
|
+ uffs_Perror(UFFS_ERR_NORMAL, "new bad block %d discovered.", newBlock);
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ else if (flash_op_err == UFFS_FLASH_IO_ERR) {
|
|
|
+ uffs_Perror(UFFS_ERR_NORMAL, "I/O error ?", newBlock);
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ } //end of for
|
|
|
+
|
|
|
+ if (i == dev->attr->pages_per_block)
|
|
|
+ succRecover = U_TRUE;
|
|
|
+
|
|
|
+ if (flash_op_err == UFFS_FLASH_BAD_BLK) {
|
|
|
+ uffs_BlockInfoExpire(dev, newBc, UFFS_ALL_PAGES);
|
|
|
+ uffs_BlockInfoPut(dev, newBc);
|
|
|
+ if (newNode->u.list.block == dev->bad.block) {
|
|
|
+ // the recovered block is a BAD block, we need to
|
|
|
+ // deal with it immediately (mark it as 'bad' and put into bad block list).
|
|
|
+ uffs_BadBlockProcess(dev, newNode);
|
|
|
+ }
|
|
|
+ goto retry; // retry on a new erased block ...
|
|
|
+ }
|
|
|
+
|
|
|
+ if (succRecover == U_TRUE) {
|
|
|
+ // now it's time to clean the dirty buffers
|
|
|
+ for (i = 0; i < dev->attr->pages_per_block; i++) {
|
|
|
+ buf = _FindBufInDirtyList(dev->buf.dirtyGroup[slot].dirty, i);
|
|
|
+ if (buf) {
|
|
|
+ if (_BreakFromDirty(dev, buf) == U_SUCC) {
|
|
|
+ buf->mark = UFFS_BUF_VALID;
|
|
|
+ buf->ext_mark &= ~UFFS_BUF_EXT_MARK_TRUNC_TAIL;
|
|
|
+ _MoveNodeToHead(dev, buf);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // swap the old block node and new block node.
|
|
|
+ // it's important that we 'swap' the block and keep the node unchanged
|
|
|
+ // so that allowing someone hold the node pointer unawared.
|
|
|
+ switch (type) {
|
|
|
+ case UFFS_TYPE_DIR:
|
|
|
+ node->u.dir.parent = parent;
|
|
|
+ node->u.dir.serial = serial;
|
|
|
+ node->u.dir.block = newBlock;
|
|
|
+ node->u.dir.checksum = data_sum;
|
|
|
+ break;
|
|
|
+ case UFFS_TYPE_FILE:
|
|
|
+ node->u.file.parent = parent;
|
|
|
+ node->u.file.serial = serial;
|
|
|
+ node->u.file.block = newBlock;
|
|
|
+ node->u.file.checksum = data_sum;
|
|
|
+ break;
|
|
|
+ case UFFS_TYPE_DATA:
|
|
|
+ node->u.data.parent = parent;
|
|
|
+ node->u.data.serial = serial;
|
|
|
+ node->u.data.block = newBlock;
|
|
|
+ break;
|
|
|
+ default:
|
|
|
+ uffs_Perror(UFFS_ERR_SERIOUS, "UNKNOW TYPE");
|
|
|
+ break;
|
|
|
+ }
|
|
|
+
|
|
|
+ newNode->u.list.block = bc->block;
|
|
|
+ uffs_BlockInfoExpire(dev, bc, UFFS_ALL_PAGES);
|
|
|
+
|
|
|
+ // if the recovered block is a bad block, it's time to process it.
|
|
|
+ if (HAVE_BADBLOCK(dev) && dev->bad.block == newNode->u.list.block) {
|
|
|
+ uffs_BadBlockProcess(dev, newNode);
|
|
|
+ }
|
|
|
+ else {
|
|
|
+ // erase recovered block, put it back to erased block list.
|
|
|
+ uffs_FlashEraseBlock(dev, bc->block);
|
|
|
+ if (HAVE_BADBLOCK(dev))
|
|
|
+ uffs_BadBlockProcess(dev, newNode);
|
|
|
+ else
|
|
|
+ uffs_TreeInsertToErasedListTail(dev, newNode);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ else {
|
|
|
+ uffs_BlockInfoExpire(dev, newBc, UFFS_ALL_PAGES);
|
|
|
+ uffs_FlashEraseBlock(dev, newBlock);
|
|
|
+ newNode->u.list.block = newBlock;
|
|
|
+ if (HAVE_BADBLOCK(dev))
|
|
|
+ uffs_BadBlockProcess(dev, newNode);
|
|
|
+ else
|
|
|
+ uffs_TreeInsertToErasedListTail(dev, newNode);
|
|
|
+ }
|
|
|
+
|
|
|
+ if (dev->buf.dirtyGroup[slot].dirty != NULL || dev->buf.dirtyGroup[slot].count != 0) {
|
|
|
+ uffs_Perror(UFFS_ERR_NORMAL, "still has dirty buffer ?");
|
|
|
+ }
|
|
|
+
|
|
|
+ uffs_BlockInfoPut(dev, newBc);
|
|
|
+ext:
|
|
|
+ uffs_BlockInfoExpire(dev, bc, UFFS_ALL_PAGES);
|
|
|
+ return (succRecover == U_TRUE ? U_SUCC : U_FAIL);
|
|
|
+
|
|
|
+}
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+/**
|
|
|
+ * \brief flush buffer to a new block which is not registered in tree
|
|
|
+ *
|
|
|
+ * Scenario:
|
|
|
+ * 1. get a new block
|
|
|
+ * 2. write pages in dirty list to new block, sorted by page_id
|
|
|
+ * 3. insert new block to tree
|
|
|
+ */
|
|
|
+static URET _BufFlush_NewBlock(uffs_Device *dev, int slot)
|
|
|
+{
|
|
|
+ u8 type;
|
|
|
+ TreeNode *node;
|
|
|
+ uffs_BlockInfo *bc;
|
|
|
+ URET ret;
|
|
|
+
|
|
|
+ ret = U_FAIL;
|
|
|
+
|
|
|
+ node = uffs_TreeGetErasedNode(dev);
|
|
|
+ if (node == NULL) {
|
|
|
+ uffs_Perror(UFFS_ERR_NOISY, "no erased block!");
|
|
|
+ goto ext;
|
|
|
+ }
|
|
|
+ bc = uffs_BlockInfoGet(dev, node->u.list.block);
|
|
|
+ if (bc == NULL) {
|
|
|
+ uffs_Perror(UFFS_ERR_SERIOUS, "get block info fail!");
|
|
|
+ uffs_InsertToErasedListHead(dev, node); //put node back to erased list
|
|
|
+ goto ext;
|
|
|
+ }
|
|
|
+
|
|
|
+ type = dev->buf.dirtyGroup[slot].dirty->type;
|
|
|
+
|
|
|
+ ret = uffs_BufFlush_Exist_With_BlockCover(dev, slot, node, bc);
|
|
|
+
|
|
|
+ if (ret == U_SUCC)
|
|
|
+ uffs_InsertNodeToTree(dev, type, node);
|
|
|
+ else {
|
|
|
+ uffs_FlashEraseBlock(dev, bc->block);
|
|
|
+ uffs_InsertToErasedListHead(dev, node);
|
|
|
+ }
|
|
|
+
|
|
|
+ uffs_BlockInfoPut(dev, bc);
|
|
|
+ext:
|
|
|
+ return ret;
|
|
|
+}
|
|
|
+
|
|
|
+
|
|
|
+/**
|
|
|
+ * \brief flush buffer to a block with enough free pages
|
|
|
+ *
|
|
|
+ * pages in dirty list must be sorted by page_id to write to flash
|
|
|
+ */
|
|
|
+static
|
|
|
+URET
|
|
|
+ uffs_BufFlush_Exist_With_Enough_FreePage(
|
|
|
+ uffs_Device *dev,
|
|
|
+ int slot, //!< dirty group slot
|
|
|
+ TreeNode *node, //!< tree node
|
|
|
+ uffs_BlockInfo *bc, //!< block info (Source, also destination)
|
|
|
+ u16 freePages //!< how many free pages left on destination block
|
|
|
+ )
|
|
|
+{
|
|
|
+ u16 page;
|
|
|
+ uffs_Buf *buf;
|
|
|
+ uffs_Tags *tag;
|
|
|
+ URET ret;
|
|
|
+ int x;
|
|
|
+
|
|
|
+// uffs_Perror(UFFS_ERR_NOISY, "Flush buffers with Enough Free Page, in block %d",
|
|
|
+// bc->block);
|
|
|
+ ret = U_FAIL;
|
|
|
+ for (page = dev->attr->pages_per_block - freePages; //page: free page num
|
|
|
+ dev->buf.dirtyGroup[slot].count > 0; //still has dirty pages?
|
|
|
+ page++) {
|
|
|
+
|
|
|
+ buf = _FindMinimunPageIdFromDirtyList(dev->buf.dirtyGroup[slot].dirty);
|
|
|
+ if (buf == NULL) {
|
|
|
+ uffs_Perror(UFFS_ERR_SERIOUS, "count > 0, but no dirty pages in list ?");
|
|
|
+ goto ext;
|
|
|
+ }
|
|
|
+
|
|
|
+ //writre the dirty page (id: buf->page_id) to page i (free page)
|
|
|
+ uffs_BlockInfoLoad(dev, bc, page);
|
|
|
+ tag = GET_TAG(bc, page);
|
|
|
+ TAG_BLOCK_TS(tag) = uffs_GetBlockTimeStamp(dev, bc);
|
|
|
+ TAG_DATA_LEN(tag) = buf->data_len;
|
|
|
+ TAG_TYPE(tag) = buf->type;
|
|
|
+ //tag->data_sum = _GetDirOrFileNameSum(dev, buf);
|
|
|
+ TAG_PARENT(tag) = buf->parent;
|
|
|
+ TAG_SERIAL(tag) = buf->serial;
|
|
|
+ TAG_PAGE_ID(tag) = (u8)(buf->page_id);
|
|
|
+
|
|
|
+ x = uffs_FlashWritePageCombine(dev, bc->block, page, buf, tag);
|
|
|
+ if (x == UFFS_FLASH_IO_ERR) {
|
|
|
+ uffs_Perror(UFFS_ERR_NORMAL, "I/O error <1>?");
|
|
|
+ goto ext;
|
|
|
+ }
|
|
|
+ else if (x == UFFS_FLASH_BAD_BLK) {
|
|
|
+ ret = uffs_BufFlush_Exist_With_BlockCover(dev, slot, node, bc);
|
|
|
+ goto ext;
|
|
|
+ }
|
|
|
+ else {
|
|
|
+ if(_BreakFromDirty(dev, buf) == U_SUCC) {
|
|
|
+ buf->mark = UFFS_BUF_VALID;
|
|
|
+ _MoveNodeToHead(dev, buf);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ } //end of for
|
|
|
+
|
|
|
+ if (dev->buf.dirtyGroup[slot].dirty != NULL || dev->buf.dirtyGroup[slot].count != 0) {
|
|
|
+ uffs_Perror(UFFS_ERR_NORMAL, "still has dirty buffer ?");
|
|
|
+ }
|
|
|
+ else {
|
|
|
+ ret = U_SUCC;
|
|
|
+ }
|
|
|
+
|
|
|
+ext:
|
|
|
+ return ret;
|
|
|
+}
|
|
|
+
|
|
|
+
|
|
|
+URET _BufFlush(struct uffs_DeviceSt *dev, UBOOL force_block_recover, int slot)
|
|
|
+{
|
|
|
+ uffs_Buf *dirty;
|
|
|
+ TreeNode *node;
|
|
|
+ uffs_BlockInfo *bc;
|
|
|
+ u16 n;
|
|
|
+ URET ret;
|
|
|
+ u8 type;
|
|
|
+ u16 parent;
|
|
|
+ u16 serial;
|
|
|
+ int block;
|
|
|
+
|
|
|
+ if (dev->buf.dirtyGroup[slot].count == 0) {
|
|
|
+ return U_SUCC;
|
|
|
+ }
|
|
|
+
|
|
|
+ dirty = dev->buf.dirtyGroup[slot].dirty;
|
|
|
+
|
|
|
+ if (_CheckDirtyList(dirty) == U_FAIL)
|
|
|
+ return U_FAIL;
|
|
|
+
|
|
|
+ type = dirty->type;
|
|
|
+ parent = dirty->parent;
|
|
|
+ serial = dirty->serial;
|
|
|
+
|
|
|
+ switch (type) {
|
|
|
+ case UFFS_TYPE_DIR:
|
|
|
+ node = uffs_TreeFindDirNode(dev, serial);
|
|
|
+ break;
|
|
|
+ case UFFS_TYPE_FILE:
|
|
|
+ node = uffs_TreeFindFileNode(dev, serial);
|
|
|
+ break;
|
|
|
+ case UFFS_TYPE_DATA:
|
|
|
+ node = uffs_TreeFindDataNode(dev, parent, serial);
|
|
|
+ break;
|
|
|
+ default:
|
|
|
+ uffs_Perror(UFFS_ERR_SERIOUS, "unknown type");
|
|
|
+ return U_FAIL;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (node == NULL) {
|
|
|
+ //not found in the tree, need to generate a new block
|
|
|
+ ret = _BufFlush_NewBlock(dev, slot);
|
|
|
+ }
|
|
|
+ else {
|
|
|
+ switch (type) {
|
|
|
+ case UFFS_TYPE_DIR:
|
|
|
+ block = node->u.dir.block;
|
|
|
+ break;
|
|
|
+ case UFFS_TYPE_FILE:
|
|
|
+ block = node->u.file.block;
|
|
|
+ break;
|
|
|
+ case UFFS_TYPE_DATA:
|
|
|
+ block = node->u.data.block;
|
|
|
+ break;
|
|
|
+ default:
|
|
|
+ uffs_Perror(UFFS_ERR_SERIOUS, "unknown type.");
|
|
|
+ return U_FAIL;
|
|
|
+ }
|
|
|
+ bc = uffs_BlockInfoGet(dev, block);
|
|
|
+ if(bc == NULL) {
|
|
|
+ uffs_Perror(UFFS_ERR_SERIOUS, "get block info fail.");
|
|
|
+ return U_FAIL;
|
|
|
+ }
|
|
|
+ uffs_BlockInfoLoad(dev, bc, UFFS_ALL_PAGES);
|
|
|
+ n = uffs_GetFreePagesCount(dev, bc);
|
|
|
+
|
|
|
+ if (n >= dev->buf.dirtyGroup[slot].count && !force_block_recover) {
|
|
|
+ //The free pages are enough for the dirty pages
|
|
|
+ ret = uffs_BufFlush_Exist_With_Enough_FreePage(dev, slot, node, bc, n);
|
|
|
+ }
|
|
|
+ else {
|
|
|
+ ret = uffs_BufFlush_Exist_With_BlockCover(dev, slot, node, bc);
|
|
|
+ }
|
|
|
+ uffs_BlockInfoPut(dev, bc);
|
|
|
+ }
|
|
|
+
|
|
|
+ return ret;
|
|
|
+}
|
|
|
+
|
|
|
+static int _FindMostDirtyGroup(struct uffs_DeviceSt *dev)
|
|
|
+{
|
|
|
+ int i, slot = -1;
|
|
|
+ int max_count = 0;
|
|
|
+
|
|
|
+ for (i = 0; i < MAX_DIRTY_BUF_GROUPS; i++) {
|
|
|
+ if (dev->buf.dirtyGroup[i].dirty && dev->buf.dirtyGroup[i].lock == 0) {
|
|
|
+ if (dev->buf.dirtyGroup[i].count > max_count) {
|
|
|
+ max_count = dev->buf.dirtyGroup[i].count;
|
|
|
+ slot = i;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ return slot;
|
|
|
+}
|
|
|
+
|
|
|
+/** lock dirty group */
|
|
|
+URET uffs_BufLockGroup(struct uffs_DeviceSt *dev, int slot)
|
|
|
+{
|
|
|
+ URET ret = U_FAIL;
|
|
|
+ if (slot >= 0 && slot < MAX_DIRTY_BUF_GROUPS) {
|
|
|
+ dev->buf.dirtyGroup[slot].lock++;
|
|
|
+ ret = U_SUCC;
|
|
|
+ }
|
|
|
+ return ret;
|
|
|
+}
|
|
|
+
|
|
|
+/** unlock dirty group */
|
|
|
+URET uffs_BufUnLockGroup(struct uffs_DeviceSt *dev, int slot)
|
|
|
+{
|
|
|
+ URET ret = U_FAIL;
|
|
|
+
|
|
|
+ if (slot >= 0 && slot < MAX_DIRTY_BUF_GROUPS) {
|
|
|
+ if (dev->buf.dirtyGroup[slot].lock > 0)
|
|
|
+ dev->buf.dirtyGroup[slot].lock--;
|
|
|
+ else {
|
|
|
+ uffs_Perror(UFFS_ERR_SERIOUS, "Try to unlock an unlocked group ?");
|
|
|
+ }
|
|
|
+ ret = U_SUCC;
|
|
|
+ }
|
|
|
+ return ret;
|
|
|
+}
|
|
|
+
|
|
|
+
|
|
|
+/**
|
|
|
+ * flush buffers to flash.
|
|
|
+ * this will flush all dirty groups.
|
|
|
+ * \param[in] dev uffs device
|
|
|
+ */
|
|
|
+URET uffs_BufFlush(struct uffs_DeviceSt *dev)
|
|
|
+{
|
|
|
+ int slot;
|
|
|
+
|
|
|
+ slot = uffs_BufFindFreeGroupSlot(dev);
|
|
|
+ if (slot >= 0)
|
|
|
+ return U_SUCC; // do nothing if there is free slot
|
|
|
+ else
|
|
|
+ return uffs_BufFlushMostDirtyGroup(dev);
|
|
|
+}
|
|
|
+
|
|
|
+/**
|
|
|
+ * flush most dirty group
|
|
|
+ * \param[in] dev uffs device
|
|
|
+ */
|
|
|
+URET uffs_BufFlushMostDirtyGroup(struct uffs_DeviceSt *dev)
|
|
|
+{
|
|
|
+ int slot;
|
|
|
+
|
|
|
+ slot = _FindMostDirtyGroup(dev);
|
|
|
+ if (slot >= 0) {
|
|
|
+ return _BufFlush(dev, U_FALSE, slot);
|
|
|
+ }
|
|
|
+ return U_SUCC;
|
|
|
+}
|
|
|
+
|
|
|
+/**
|
|
|
+ * flush buffers to flash
|
|
|
+ * this will pick up a most dirty group, and flush it if there is no free dirty group slot.
|
|
|
+ * \param[in] dev uffs device
|
|
|
+ * \param[in] force_block_recover #U_TRUE: force a block recover even there are enough free pages
|
|
|
+ */
|
|
|
+URET uffs_BufFlushEx(struct uffs_DeviceSt *dev, UBOOL force_block_recover)
|
|
|
+{
|
|
|
+ int slot;
|
|
|
+
|
|
|
+ slot = uffs_BufFindFreeGroupSlot(dev);
|
|
|
+ if (slot >= 0) {
|
|
|
+ return U_SUCC; //there is free slot, do nothing.
|
|
|
+ }
|
|
|
+ else {
|
|
|
+ slot = _FindMostDirtyGroup(dev);
|
|
|
+ return _BufFlush(dev, force_block_recover, slot);
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+/**
|
|
|
+ * flush buffer group with given parent/serial num.
|
|
|
+ *
|
|
|
+ * \param[in] dev uffs device
|
|
|
+ * \param[in] parent parent num of the group
|
|
|
+ * \param[in] serial serial num of the group
|
|
|
+ */
|
|
|
+URET uffs_BufFlushGroup(struct uffs_DeviceSt *dev, u16 parent, u16 serial)
|
|
|
+{
|
|
|
+ int slot;
|
|
|
+
|
|
|
+ slot = uffs_BufFindGroupSlot(dev, parent, serial);
|
|
|
+ if (slot >= 0) {
|
|
|
+ return _BufFlush(dev, U_FALSE, slot);
|
|
|
+ }
|
|
|
+
|
|
|
+ return U_SUCC;
|
|
|
+}
|
|
|
+
|
|
|
+/**
|
|
|
+ * flush buffer group with given parent/serial num and force_block_recover indicator.
|
|
|
+ *
|
|
|
+ * \param[in] dev uffs device
|
|
|
+ * \param[in] parent parent num of the group
|
|
|
+ * \param[in] serial serial num of group
|
|
|
+ * \param[in] force_block_recover indicator
|
|
|
+ */
|
|
|
+URET uffs_BufFlushGroupEx(struct uffs_DeviceSt *dev, u16 parent, u16 serial, UBOOL force_block_recover)
|
|
|
+{
|
|
|
+ int slot;
|
|
|
+
|
|
|
+ slot = uffs_BufFindGroupSlot(dev, parent, serial);
|
|
|
+ if (slot >= 0) {
|
|
|
+ return _BufFlush(dev, force_block_recover, slot);
|
|
|
+ }
|
|
|
+
|
|
|
+ return U_SUCC;
|
|
|
+}
|
|
|
+
|
|
|
+
|
|
|
+/**
|
|
|
+ * flush buffer group/groups which match given parent num.
|
|
|
+ *
|
|
|
+ * \param[in] dev uffs device
|
|
|
+ * \param[in] parent parent num of the group
|
|
|
+ * \param[in] serial serial num of group
|
|
|
+ * \param[in] force_block_recover indicator
|
|
|
+ */
|
|
|
+URET uffs_BufFlushGroupMatchParent(struct uffs_DeviceSt *dev, u16 parent)
|
|
|
+{
|
|
|
+ int slot;
|
|
|
+ uffs_Buf *buf;
|
|
|
+ URET ret = U_SUCC;
|
|
|
+
|
|
|
+ for (slot = 0; slot < MAX_DIRTY_BUF_GROUPS && ret == U_SUCC; slot++) {
|
|
|
+ if (dev->buf.dirtyGroup[slot].dirty) {
|
|
|
+ buf = dev->buf.dirtyGroup[slot].dirty;
|
|
|
+ if (buf->parent == parent) {
|
|
|
+ ret = _BufFlush(dev, U_FALSE, slot);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ return ret;
|
|
|
+}
|
|
|
+
|
|
|
+/**
|
|
|
+ * find a free dirty group slot
|
|
|
+ *
|
|
|
+ * \param[in] dev uffs device
|
|
|
+ * \return slot index (0 to MAX_DIRTY_BUF_GROUPS - 1) if found one, otherwise return -1.
|
|
|
+ */
|
|
|
+int uffs_BufFindFreeGroupSlot(struct uffs_DeviceSt *dev)
|
|
|
+{
|
|
|
+ int i, slot = -1;
|
|
|
+
|
|
|
+ for (i = 0; i < MAX_DIRTY_BUF_GROUPS; i++) {
|
|
|
+ if (dev->buf.dirtyGroup[i].dirty == NULL) {
|
|
|
+ slot = i;
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return slot;
|
|
|
+}
|
|
|
+
|
|
|
+/**
|
|
|
+ * find a dirty group slot with given parent/serial num.
|
|
|
+ *
|
|
|
+ * \param[in] dev uffs device
|
|
|
+ * \param[in] parent parent num of the group
|
|
|
+ * \param[in] serial serial num of group
|
|
|
+ * \return slot index (0 to MAX_DIRTY_BUF_GROUPS - 1) if found one, otherwise return -1.
|
|
|
+ */
|
|
|
+int uffs_BufFindGroupSlot(struct uffs_DeviceSt *dev, u16 parent, u16 serial)
|
|
|
+{
|
|
|
+ uffs_Buf *buf;
|
|
|
+ int i, slot = -1;
|
|
|
+
|
|
|
+ for (i = 0; i < MAX_DIRTY_BUF_GROUPS; i++) {
|
|
|
+ if (dev->buf.dirtyGroup[i].dirty) {
|
|
|
+ buf = dev->buf.dirtyGroup[i].dirty;
|
|
|
+ if (buf->parent == parent && buf->serial == serial) {
|
|
|
+ slot = i;
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return slot;
|
|
|
+}
|
|
|
+
|
|
|
+/**
|
|
|
+ * \brief get a page buffer
|
|
|
+ * \param[in] dev uffs device
|
|
|
+ * \param[in] parent parent serial num
|
|
|
+ * \param[in] serial serial num
|
|
|
+ * \param[in] page_id page_id
|
|
|
+ * \return return the buffer found in buffer list, if not found, return NULL.
|
|
|
+ */
|
|
|
+uffs_Buf * uffs_BufGet(struct uffs_DeviceSt *dev, u16 parent, u16 serial, u16 page_id)
|
|
|
+{
|
|
|
+ uffs_Buf *p;
|
|
|
+
|
|
|
+ //first, check whether the buffer exist in buf list ?
|
|
|
+ p = uffs_BufFind(dev, parent, serial, page_id);
|
|
|
+
|
|
|
+ if (p) {
|
|
|
+ p->ref_count++;
|
|
|
+ _MoveNodeToHead(dev, p);
|
|
|
+ }
|
|
|
+
|
|
|
+ return p;
|
|
|
+}
|
|
|
+
|
|
|
+/**
|
|
|
+ * New generate a buffer
|
|
|
+ */
|
|
|
+uffs_Buf *uffs_BufNew(struct uffs_DeviceSt *dev, u8 type, u16 parent, u16 serial, u16 page_id)
|
|
|
+{
|
|
|
+ uffs_Buf *buf;
|
|
|
+
|
|
|
+ buf = uffs_BufGet(dev, parent, serial, page_id);
|
|
|
+ if (buf) {
|
|
|
+ if (buf->ref_count > 1) {
|
|
|
+ uffs_Perror(UFFS_ERR_SERIOUS, "When create new buf, an exist buffer has ref count %d, possibly bug!", buf->ref_count);
|
|
|
+ }
|
|
|
+ else {
|
|
|
+ buf->data_len = 0;
|
|
|
+ }
|
|
|
+ _MoveNodeToHead(dev, buf);
|
|
|
+ return buf;
|
|
|
+ }
|
|
|
+
|
|
|
+ buf = _FindFreeBuf(dev);
|
|
|
+ if (buf == NULL) {
|
|
|
+ uffs_BufFlushMostDirtyGroup(dev);
|
|
|
+ buf = _FindFreeBuf(dev);
|
|
|
+ if (buf == NULL) {
|
|
|
+ uffs_Perror(UFFS_ERR_SERIOUS, "no free page buf!");
|
|
|
+ return NULL;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ buf->mark = UFFS_BUF_EMPTY;
|
|
|
+ buf->type = type;
|
|
|
+ buf->parent = parent;
|
|
|
+ buf->serial = serial;
|
|
|
+ buf->page_id = page_id;
|
|
|
+ buf->data_len = 0;
|
|
|
+ buf->ref_count++;
|
|
|
+ memset(buf->data, 0xff, dev->com.pg_data_size);
|
|
|
+
|
|
|
+ _MoveNodeToHead(dev, buf);
|
|
|
+
|
|
|
+ return buf;
|
|
|
+}
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+/**
|
|
|
+ * get a page buffer
|
|
|
+ * \param[in] dev uffs device
|
|
|
+ * \param[in] type dir, file or data ?
|
|
|
+ * \param[in] node node on the tree
|
|
|
+ * \param[in] page_id page_id
|
|
|
+ * \return return the buffer if found in buffer list, if not found in
|
|
|
+ * buffer list, it will get a free buffer, and load data from flash.
|
|
|
+ * return NULL if not free buffer.
|
|
|
+ */
|
|
|
+uffs_Buf *uffs_BufGetEx(struct uffs_DeviceSt *dev, u8 type, TreeNode *node, u16 page_id)
|
|
|
+{
|
|
|
+ uffs_Buf *buf;
|
|
|
+ u16 parent, serial, block, page;
|
|
|
+ uffs_BlockInfo *bc;
|
|
|
+
|
|
|
+ switch (type) {
|
|
|
+ case UFFS_TYPE_DIR:
|
|
|
+ parent = node->u.dir.parent;
|
|
|
+ serial = node->u.dir.serial;
|
|
|
+ block = node->u.dir.block;
|
|
|
+ break;
|
|
|
+ case UFFS_TYPE_FILE:
|
|
|
+ parent = node->u.file.parent;
|
|
|
+ serial = node->u.file.serial;
|
|
|
+ block = node->u.file.block;
|
|
|
+ break;
|
|
|
+ case UFFS_TYPE_DATA:
|
|
|
+ parent = node->u.data.parent;
|
|
|
+ serial = node->u.data.serial;
|
|
|
+ block = node->u.data.block;
|
|
|
+ break;
|
|
|
+ default:
|
|
|
+ uffs_Perror(UFFS_ERR_SERIOUS, "unknown type");
|
|
|
+ return NULL;
|
|
|
+ }
|
|
|
+
|
|
|
+ buf = uffs_BufFind(dev, parent, serial, page_id);
|
|
|
+ if (buf) {
|
|
|
+ buf->ref_count++;
|
|
|
+ return buf;
|
|
|
+ }
|
|
|
+
|
|
|
+ buf = _FindFreeBuf(dev);
|
|
|
+ if (buf == NULL) {
|
|
|
+ uffs_BufFlushMostDirtyGroup(dev);
|
|
|
+ buf = _FindFreeBuf(dev);
|
|
|
+ if (buf == NULL) {
|
|
|
+ uffs_Perror(UFFS_ERR_SERIOUS, "no free page buf!");
|
|
|
+ return NULL;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ bc = uffs_BlockInfoGet(dev, block);
|
|
|
+ if (bc == NULL) {
|
|
|
+ uffs_Perror(UFFS_ERR_SERIOUS, "Can't get block info!");
|
|
|
+ return NULL;
|
|
|
+ }
|
|
|
+
|
|
|
+ page = uffs_FindPageInBlockWithPageId(dev, bc, page_id);
|
|
|
+ if (page == UFFS_INVALID_PAGE) {
|
|
|
+ uffs_BlockInfoPut(dev, bc);
|
|
|
+ uffs_Perror(UFFS_ERR_SERIOUS, "can't find right page ?");
|
|
|
+ return NULL;
|
|
|
+ }
|
|
|
+ page = uffs_FindBestPageInBlock(dev, bc, page);
|
|
|
+ uffs_BlockInfoPut(dev, bc);
|
|
|
+
|
|
|
+ buf->mark = UFFS_BUF_EMPTY;
|
|
|
+ buf->type = type;
|
|
|
+ buf->parent = parent;
|
|
|
+ buf->serial = serial;
|
|
|
+ buf->page_id = page_id;
|
|
|
+
|
|
|
+ if (UFFS_FLASH_HAVE_ERR(uffs_FlashReadPage(dev, block, page, buf))) {
|
|
|
+ uffs_Perror(UFFS_ERR_SERIOUS, "can't load page from flash !");
|
|
|
+ return NULL;
|
|
|
+ }
|
|
|
+
|
|
|
+ buf->data_len = TAG_DATA_LEN(GET_TAG(bc, page));
|
|
|
+ buf->mark = UFFS_BUF_VALID;
|
|
|
+ buf->ref_count++;
|
|
|
+
|
|
|
+ _MoveNodeToHead(dev, buf);
|
|
|
+
|
|
|
+ return buf;
|
|
|
+
|
|
|
+}
|
|
|
+
|
|
|
+/**
|
|
|
+ * \brief Put back a page buffer, make reference count decrease by one
|
|
|
+ * \param[in] dev uffs device
|
|
|
+ * \param[in] buf buffer to be put back
|
|
|
+ */
|
|
|
+URET uffs_BufPut(uffs_Device *dev, uffs_Buf *buf)
|
|
|
+{
|
|
|
+ URET ret = U_FAIL;
|
|
|
+
|
|
|
+ dev = dev;
|
|
|
+ if (buf == NULL) {
|
|
|
+ uffs_Perror(UFFS_ERR_NORMAL, "Can't put an NULL buffer!");
|
|
|
+ }
|
|
|
+ else if (buf->ref_count == 0) {
|
|
|
+ uffs_Perror(UFFS_ERR_NORMAL, "Putting an unused page buffer ? ");
|
|
|
+ }
|
|
|
+ else if (buf->ref_count == CLONE_BUF_MARK) {
|
|
|
+ uffs_Perror(UFFS_ERR_NORMAL, "Putting an cloned page buffer ? ");
|
|
|
+ ret = uffs_BufFreeClone(dev, buf);
|
|
|
+ }
|
|
|
+ else {
|
|
|
+ buf->ref_count--;
|
|
|
+ ret = U_SUCC;
|
|
|
+ }
|
|
|
+
|
|
|
+ return ret;
|
|
|
+}
|
|
|
+
|
|
|
+
|
|
|
+/**
|
|
|
+ * \brief clone from an exist buffer.
|
|
|
+ allocate memory for new buffer, and copy data from original buffer if
|
|
|
+ original buffer is not NULL.
|
|
|
+ * \param[in] dev uffs device
|
|
|
+ * \param[in] buf page buffer to be clone from. if NULL presented here, data copy will not be processed
|
|
|
+ * \return return the cloned page buffer, all data copied from source
|
|
|
+ * \note the cloned buffer is not linked in page buffer list in uffs device,
|
|
|
+ * so you should use #uffs_BufFreeClone instead of #uffs_BufPut when you put back or release buffer
|
|
|
+ */
|
|
|
+uffs_Buf * uffs_BufClone(uffs_Device *dev, uffs_Buf *buf)
|
|
|
+{
|
|
|
+ uffs_Buf *p;
|
|
|
+
|
|
|
+ p = _FindFreeBufEx(dev, 1);
|
|
|
+ if (p == NULL) {
|
|
|
+ uffs_Perror(UFFS_ERR_SERIOUS, "no enough free pages for clone! Please increase Clone Buffer Count threshold.");
|
|
|
+ }
|
|
|
+ else {
|
|
|
+ _BreakFromBufList(dev, p);
|
|
|
+
|
|
|
+ if (buf) {
|
|
|
+ p->parent = buf->parent;
|
|
|
+ p->type = buf->type;
|
|
|
+ p->serial = buf->serial;
|
|
|
+ p->page_id = buf->page_id;
|
|
|
+
|
|
|
+ p->data_len = buf->data_len;
|
|
|
+ //athough the valid data length is .data_len,
|
|
|
+ //but we still need copy the whole buffer, include header
|
|
|
+ memcpy(p->header, buf->header, dev->com.pg_size);
|
|
|
+ }
|
|
|
+ p->next = p->prev = NULL; //because the cloned one is not linked to device buffer
|
|
|
+ p->next_dirty = p->prev_dirty = NULL;
|
|
|
+ p->ref_count = CLONE_BUF_MARK; //CLONE_BUF_MARK indicates that this is an cloned buffer
|
|
|
+ }
|
|
|
+
|
|
|
+ return p;
|
|
|
+}
|
|
|
+
|
|
|
+/**
|
|
|
+ * \brief release cloned buffer
|
|
|
+ * \param[in] dev uffs device
|
|
|
+ * \param[in] buf cloned buffer
|
|
|
+ */
|
|
|
+URET uffs_BufFreeClone(uffs_Device *dev, uffs_Buf *buf)
|
|
|
+{
|
|
|
+ dev = dev; //make compiler happy
|
|
|
+ if (!buf)
|
|
|
+ return U_FAIL;
|
|
|
+
|
|
|
+ if (buf->ref_count != CLONE_BUF_MARK) {
|
|
|
+ /* a cloned buffer must have a ref_count of CLONE_BUF_MARK */
|
|
|
+ uffs_Perror(UFFS_ERR_SERIOUS, "Try to release a non-cloned page buffer ?");
|
|
|
+ return U_FAIL;
|
|
|
+ }
|
|
|
+
|
|
|
+ buf->ref_count = 0;
|
|
|
+ buf->mark = UFFS_BUF_EMPTY;
|
|
|
+ _LinkToBufListTail(dev, buf);
|
|
|
+
|
|
|
+ return U_SUCC;
|
|
|
+}
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+UBOOL uffs_BufIsAllFree(struct uffs_DeviceSt *dev)
|
|
|
+{
|
|
|
+ uffs_Buf *buf = dev->buf.head;
|
|
|
+
|
|
|
+ while (buf) {
|
|
|
+ if(buf->ref_count != 0) return U_FALSE;
|
|
|
+ buf = buf->next;
|
|
|
+ }
|
|
|
+
|
|
|
+ return U_TRUE;
|
|
|
+}
|
|
|
+
|
|
|
+UBOOL uffs_BufIsAllEmpty(struct uffs_DeviceSt *dev)
|
|
|
+{
|
|
|
+ uffs_Buf *buf = dev->buf.head;
|
|
|
+
|
|
|
+ while (buf) {
|
|
|
+ if(buf->mark != UFFS_BUF_EMPTY) return U_FALSE;
|
|
|
+ buf = buf->next;
|
|
|
+ }
|
|
|
+
|
|
|
+ return U_TRUE;
|
|
|
+}
|
|
|
+
|
|
|
+
|
|
|
+URET uffs_BufSetAllEmpty(struct uffs_DeviceSt *dev)
|
|
|
+{
|
|
|
+ uffs_Buf *buf = dev->buf.head;
|
|
|
+
|
|
|
+ while (buf) {
|
|
|
+ buf->mark = UFFS_BUF_EMPTY;
|
|
|
+ buf = buf->next;
|
|
|
+ }
|
|
|
+ return U_SUCC;
|
|
|
+}
|
|
|
+
|
|
|
+
|
|
|
+void uffs_BufIncRef(uffs_Buf *buf)
|
|
|
+{
|
|
|
+ buf->ref_count++;
|
|
|
+}
|
|
|
+
|
|
|
+void uffs_BufDecRef(uffs_Buf *buf)
|
|
|
+{
|
|
|
+ if (buf->ref_count > 0)
|
|
|
+ buf->ref_count--;
|
|
|
+}
|
|
|
+
|
|
|
+/** mark buffer as #UFFS_BUF_EMPTY if ref_count == 0, and discard all data it holds */
|
|
|
+void uffs_BufMarkEmpty(uffs_Device *dev, uffs_Buf *buf)
|
|
|
+{
|
|
|
+ if (buf->mark != UFFS_BUF_EMPTY) {
|
|
|
+ if (buf->ref_count == 0) {
|
|
|
+ if (buf->mark == UFFS_BUF_DIRTY)
|
|
|
+ _BreakFromDirty(dev, buf);
|
|
|
+ buf->mark = UFFS_BUF_EMPTY;
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+#if 0
|
|
|
+static UBOOL _IsBufInDirtyList(struct uffs_DeviceSt *dev, uffs_Buf *buf)
|
|
|
+{
|
|
|
+ uffs_Buf *p = dev->buf.dirtyGroup[slot].dirty;
|
|
|
+
|
|
|
+ while (p) {
|
|
|
+ if(p == buf) return U_TRUE;
|
|
|
+ p = p->next_dirty;
|
|
|
+ }
|
|
|
+
|
|
|
+ return U_FALSE;
|
|
|
+}
|
|
|
+#endif
|
|
|
+
|
|
|
+URET uffs_BufWrite(struct uffs_DeviceSt *dev, uffs_Buf *buf, void *data, u32 ofs, u32 len)
|
|
|
+{
|
|
|
+ int slot;
|
|
|
+
|
|
|
+ if(ofs + len > dev->com.pg_data_size) {
|
|
|
+ uffs_Perror(UFFS_ERR_SERIOUS, "data length out of range! %d+%d", ofs, len);
|
|
|
+ return U_FAIL;
|
|
|
+ }
|
|
|
+
|
|
|
+ slot = uffs_BufFindGroupSlot(dev, buf->parent, buf->serial);
|
|
|
+
|
|
|
+ if (slot < 0) {
|
|
|
+ // need to take a free slot
|
|
|
+ slot = uffs_BufFindFreeGroupSlot(dev);
|
|
|
+ if (slot < 0) {
|
|
|
+ // no free slot ? flush buffer
|
|
|
+ if (uffs_BufFlushMostDirtyGroup(dev) != U_SUCC)
|
|
|
+ return U_FAIL;
|
|
|
+
|
|
|
+ slot = uffs_BufFindFreeGroupSlot(dev);
|
|
|
+ if (slot < 0) {
|
|
|
+ // still no free slot ??
|
|
|
+ uffs_Perror(UFFS_ERR_SERIOUS, "no free slot ?");
|
|
|
+ return U_FAIL;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ memcpy(buf->data + ofs, data, len);
|
|
|
+
|
|
|
+ if (ofs + len > buf->data_len)
|
|
|
+ buf->data_len = ofs + len;
|
|
|
+
|
|
|
+ if (_IsBufInInDirtyList(dev, slot, buf) == U_FALSE) {
|
|
|
+ _LinkToDirtyList(dev, slot, buf);
|
|
|
+ }
|
|
|
+
|
|
|
+ if (dev->buf.dirtyGroup[slot].count >= dev->buf.dirty_buf_max) {
|
|
|
+ if (uffs_BufFlushGroup(dev, buf->parent, buf->serial) != U_SUCC) {
|
|
|
+ return U_FAIL;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ return U_SUCC;
|
|
|
+}
|
|
|
+
|
|
|
+URET uffs_BufRead(struct uffs_DeviceSt *dev, uffs_Buf *buf, void *data, u32 ofs, u32 len)
|
|
|
+{
|
|
|
+ u32 readSize;
|
|
|
+ u32 pg_data_size = dev->com.pg_data_size;
|
|
|
+
|
|
|
+ readSize = (ofs >= pg_data_size ? 0 : (ofs + len >= pg_data_size ? pg_data_size - ofs : len));
|
|
|
+
|
|
|
+ if (readSize > 0)
|
|
|
+ memcpy(data, buf->data + ofs, readSize);
|
|
|
+
|
|
|
+ return U_SUCC;
|
|
|
+}
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|