/** * \brief calculate data length of a file block * \param[in] dev uffs device * \param[in] bc block info */ int uffs_GetBlockFileDataLength(uffs_Device *dev, uffs_BlockInfo *bc, u8 type) { u16 page_id; u16 i; uffs_Tags *tag; int size = 0; u16 page; u16 lastPage = dev->attr->pages_per_block - 1; uffs_BlockInfoLoad(dev, bc, lastPage); tag = GET_TAG(bc, lastPage); if (TAG_IS_GOOD(tag) && TAG_PAGE_ID(tag) == lastPage) { // First try the last page. // if it's the full loaded file/data block, then we have a quick path. if (type == UFFS_TYPE_FILE) { size = dev->com.pg_data_size * (dev->attr->pages_per_block - 2) + TAG_DATA_LEN(tag); return size; } if (type == UFFS_TYPE_DATA) { size = dev->com.pg_data_size * (dev->attr->pages_per_block - 1) + TAG_DATA_LEN(tag); return size; } } // ok, it's not the full loaded file/data block, // need to read all spares.... uffs_BlockInfoLoad(dev, bc, UFFS_ALL_PAGES); tag = GET_TAG(bc, 0); if (uffs_Assert(TAG_IS_GOOD(tag), "block %d page 0 does not have good tag ?", bc->block)) { if (TAG_TYPE(tag) == UFFS_TYPE_FILE) { page_id = 1; //In file header block, file data page_id from 1 i = 1; //search from page 1 } else { page_id = 0; //in normal file data block, page_id from 0 i = 0; //in normal file data block, search from page 0 } for (; i < dev->attr->pages_per_block; i++) { tag = GET_TAG(bc, i); if (TAG_IS_GOOD(tag)) { if (page_id == TAG_PAGE_ID(tag)) { page = uffs_FindBestPageInBlock(dev, bc, i); if (uffs_Assert(page != UFFS_INVALID_PAGE, "got an invalid page ?")) { size += TAG_DATA_LEN(GET_TAG(bc, page)); page_id++; } } } } } return size; }
/** * \brief calculate data length of a file block * \param[in] dev uffs device * \param[in] bc block info */ int uffs_GetBlockFileDataLength(uffs_Device *dev, uffs_BlockInfo *bc, u8 type) { u16 page_id; u16 i; uffs_Tags *tag; int size = 0; u16 page; u16 lastPage = dev->attr->pages_per_block - 1; // TODO: Need to speed up this procedure! // First try the last page. will hit it // if it's the full loaded file/data block. uffs_BlockInfoLoad(dev, bc, lastPage); tag = GET_TAG(bc, lastPage); if (type == UFFS_TYPE_FILE) { if(TAG_PAGE_ID(tag) == (lastPage - 1) && TAG_DATA_LEN(tag) == dev->com.pg_data_size) { size = dev->com.pg_data_size * (dev->attr->pages_per_block - 1); return size; } } if (type == UFFS_TYPE_DATA) { if(TAG_PAGE_ID(tag) == lastPage && TAG_DATA_LEN(tag) == dev->com.pg_data_size) { size = dev->com.pg_data_size * dev->attr->pages_per_block; return size; } } // ok, it's not the full loaded file/data block, // need to read all spares.... uffs_BlockInfoLoad(dev, bc, UFFS_ALL_PAGES); tag = GET_TAG(bc, 0); if (TAG_TYPE(tag) == UFFS_TYPE_FILE) { page_id = 1; //In file header block, file data page_id from 1 i = 1; //search from page 1 } else { page_id = 0; //in normal file data block, page_id from 0 i = 0; //in normal file data block, search from page 0 } for (; i < dev->attr->pages_per_block; i++) { tag = GET_TAG(bc, i); if (page_id == TAG_PAGE_ID(tag)) { page = uffs_FindBestPageInBlock(dev, bc, i); size += TAG_DATA_LEN(GET_TAG(bc, page)); page_id++; } } return size; }
/** * \brief given a page, search the block to find * a better page with the same page id * * \param[in] dev uffs device * \param[in] bc block info * \param[in] page page number to be compared with * * \return the better page number, could be the same with the given page. * if the given page does not have good tag, return UFFS_INVALID_PAGE. */ u16 uffs_FindBestPageInBlock(uffs_Device *dev, uffs_BlockInfo *bc, u16 page) { int i; uffs_Tags *tag, *tag_old; u16 lastPage = dev->attr->pages_per_block - 1; if (!uffs_Assert(page != UFFS_INVALID_PAGE, "invalid param !")) return page; // just in case ... uffs_BlockInfoLoad(dev, bc, page); // load old page tag_old = GET_TAG(bc, page); if (!uffs_Assert(TAG_IS_GOOD(tag_old), "try to find a invalid page ?")) return UFFS_INVALID_PAGE; if (page == lastPage) // already the last page ? return page; // check for fully loaded block, in which case the given // page id is the best page id uffs_BlockInfoLoad(dev, bc, lastPage); tag = GET_TAG(bc, lastPage); if (TAG_IS_GOOD(tag) && TAG_PAGE_ID(tag) == lastPage) return page; // block not fully loaded, search from bottom to top for (i = lastPage; i > page; i--) { uffs_BlockInfoLoad(dev, bc, i); tag = GET_TAG(bc, i); if (TAG_IS_GOOD(tag) && TAG_PAGE_ID(tag) == TAG_PAGE_ID(tag_old) && TAG_PARENT(tag) == TAG_PARENT(tag_old) && TAG_SERIAL(tag) == TAG_SERIAL(tag_old)) { break; } } return i; }
/** * \brief Is this block the last block of file ? * (no free pages, and full filled with full page_id) */ UBOOL uffs_IsDataBlockReguFull(uffs_Device *dev, uffs_BlockInfo *bc) { uffs_Tags *tag; uffs_BlockInfoLoad(dev, bc, dev->attr->pages_per_block - 1); tag = GET_TAG(bc, dev->attr->pages_per_block - 1); if (TAG_PAGE_ID(tag) == (dev->attr->pages_per_block - 1) && TAG_DATA_LEN(tag) == dev->com.pg_data_size) { return U_TRUE; } return U_FALSE; }
/** * \brief given a page, search the block to find * a better page with the same page id * * \param[in] dev uffs device * \param[in] bc block info * \param[in] page page number to be compared with * * \return the better page number, could be the same with the given page */ u16 uffs_FindBestPageInBlock(uffs_Device *dev, uffs_BlockInfo *bc, u16 page) { int i; int best; uffs_Tags *tag, *tag_old; if (page == dev->attr->pages_per_block - 1) return page; uffs_BlockInfoLoad(dev, bc, page); //load old page tag_old = GET_TAG(bc, page); for (i = dev->attr->pages_per_block - 1; i > page; i--) { uffs_BlockInfoLoad(dev, bc, i); tag = GET_TAG(bc, i); if (TAG_PAGE_ID(tag) == TAG_PAGE_ID(tag_old)) { if (TAG_PARENT(tag) == TAG_PARENT(tag_old) && TAG_SERIAL(tag) == TAG_SERIAL(tag_old) && TAG_IS_DIRTY(tag) && //0: dirty, 1:clear TAG_IS_VALID(tag_old)) { //0: valid, 1:invalid break; } } } best = i; #if 0 if (TAG_PAGE_ID(tag_old) == page) { //well, try to speed up by probing the last page .... uffs_BlockInfoLoad(dev, bc, dev->attr->pages_per_block - 1); tag = GET_TAG(bc, dev->attr->pages_per_block - 1); if (TAG_IS_VALID(tag) && TAG_IS_DIRTY(tag) && TAG_PAGE_ID(tag) == dev->attr->pages_per_block - 1) { return page; } } uffs_BlockInfoLoad(dev, bc, UFFS_ALL_PAGES); best = page; //the better page must be ahead of page, so ...i = page + 1; i < ... for (i = page + 1; i < dev->attr->pages_per_block; i++) { tag = GET_TAG(bc, i); if (TAG_PAGE_ID(tag) == TAG_PAGE_ID(tag_old)) { if (TAG_PARENT(tag) == TAG_PARENT(tag_old) && TAG_SERIAL(tag) == TAG_SERIAL(tag_old) && TAG_IS_DIRTY(tag) && //0: dirty, 1:clear TAG_IS_VALID(tag_old)) { //0: valid, 1:invalid if (i > best) best = i; } } } #endif return best; }
/** * \brief find a valid page with given page_id * \param[in] dev uffs device * \param[in] bc block info * \param[in] page_id page_id to be find * \return the valid page number which has given page_id * \retval >=0 page number * \retval UFFS_INVALID_PAGE page not found */ u16 uffs_FindPageInBlockWithPageId(uffs_Device *dev, uffs_BlockInfo *bc, u16 page_id) { u16 page; uffs_Tags *tag; //Indeed, the page which has page_id, should ahead of page_id ... for (page = page_id; page < dev->attr->pages_per_block; page++) { uffs_BlockInfoLoad(dev, bc, page); tag = &(bc->spares[page].tag); if (TAG_IS_GOOD(tag) && TAG_PAGE_ID(tag) == page_id) return page; } return UFFS_INVALID_PAGE; }
/* usage: t_pgrw * * This test case test page read/write */ static int cmd_TestPageReadWrite(int argc, char *argv[]) { TreeNode *node = NULL; uffs_Device *dev; uffs_Tags local_tag; uffs_Tags *tag = &local_tag; int ret; u16 block; u16 page; uffs_Buf *buf = NULL; u32 i; int rc = -1; dev = uffs_GetDeviceFromMountPoint("/"); if (!dev) goto ext; buf = uffs_BufClone(dev, NULL); if (!buf) goto ext; node = uffs_TreeGetErasedNode(dev); if (!node) { MSGLN("no free block ?"); goto ext; } for (i = 0; i < dev->com.pg_data_size; i++) { buf->data[i] = i & 0xFF; } block = node->u.list.block; page = 1; TAG_DIRTY_BIT(tag) = TAG_DIRTY; TAG_VALID_BIT(tag) = TAG_VALID; TAG_DATA_LEN(tag) = dev->com.pg_data_size; TAG_TYPE(tag) = UFFS_TYPE_DATA; TAG_PAGE_ID(tag) = 3; TAG_PARENT(tag) = 100; TAG_SERIAL(tag) = 10; TAG_BLOCK_TS(tag) = 1; SEAL_TAG(tag); ret = uffs_FlashWritePageCombine(dev, block, page, buf, tag); if (UFFS_FLASH_HAVE_ERR(ret)) { MSGLN("Write page error: %d", ret); goto ext; } ret = uffs_FlashReadPage(dev, block, page, buf, U_FALSE); if (UFFS_FLASH_HAVE_ERR(ret)) { MSGLN("Read page error: %d", ret); goto ext; } for (i = 0; i < dev->com.pg_data_size; i++) { if (buf->data[i] != (i & 0xFF)) { MSGLN("Data verify fail at: %d", i); goto ext; } } ret = uffs_FlashReadPageTag(dev, block, page, tag); if (UFFS_FLASH_HAVE_ERR(ret)) { MSGLN("Read tag (page spare) error: %d", ret); goto ext; } // verify tag: if (!TAG_IS_SEALED(tag)) { MSGLN("not sealed ? Tag verify fail!"); goto ext; } if (!TAG_IS_DIRTY(tag)) { MSGLN("not dirty ? Tag verify fail!"); goto ext; } if (!TAG_IS_VALID(tag)) { MSGLN("not valid ? Tag verify fail!"); goto ext; } if (TAG_DATA_LEN(tag) != dev->com.pg_data_size || TAG_TYPE(tag) != UFFS_TYPE_DATA || TAG_PAGE_ID(tag) != 3 || TAG_PARENT(tag) != 100 || TAG_SERIAL(tag) != 10 || TAG_BLOCK_TS(tag) != 1) { MSGLN("Tag verify fail!"); goto ext; } MSGLN("Page read/write test succ."); rc = 0; ext: if (node) { uffs_TreeEraseNode(dev, node); uffs_TreeInsertToErasedListTail(dev, node); } if (dev) uffs_PutDevice(dev); if (buf) uffs_BufFreeClone(dev, buf); return rc; }
/* usage: t_pgrw * * This test case test page read/write */ static BOOL cmdTestPageReadWrite(const char *tail) { TreeNode *node; uffs_Device *dev; uffs_Tags local_tag; uffs_Tags *tag = &local_tag; int ret; u16 block; u16 page; uffs_Buf *buf; u32 i; dev = uffs_GetDeviceFromMountPoint("/"); if (!dev) goto ext; buf = uffs_BufClone(dev, NULL); if (!buf) goto ext; node = uffs_TreeGetErasedNode(dev); if (!node) { MSGLN("no free block ?"); goto ext; } for (i = 0; i < dev->com.pg_data_size; i++) { buf->data[i] = i & 0xFF; } block = node->u.list.block; page = 1; TAG_DATA_LEN(tag) = dev->com.pg_data_size; TAG_TYPE(tag) = UFFS_TYPE_DATA; TAG_PAGE_ID(tag) = 3; TAG_PARENT(tag) = 100; TAG_SERIAL(tag) = 10; TAG_BLOCK_TS(tag) = 1; ret = uffs_FlashWritePageCombine(dev, block, page, buf, tag); if (UFFS_FLASH_HAVE_ERR(ret)) { MSGLN("Write page error: %d", ret); goto ext; } ret = uffs_FlashReadPage(dev, block, page, buf); if (UFFS_FLASH_HAVE_ERR(ret)) { MSGLN("Read page error: %d", ret); goto ext; } for (i = 0; i < dev->com.pg_data_size; i++) { if (buf->data[i] != (i & 0xFF)) { MSGLN("Data verify fail at: %d", i); goto ext; } } ret = uffs_FlashReadPageTag(dev, block, page, tag); if (UFFS_FLASH_HAVE_ERR(ret)) { MSGLN("Read tag (page spare) error: %d", ret); goto ext; } // verify tag: if (!TAG_IS_DIRTY(tag)) { MSGLN("not dirty ? Tag verify fail!"); goto ext; } if (!TAG_IS_VALID(tag)) { MSGLN("not valid ? Tag verify fail!"); goto ext; } if (TAG_DATA_LEN(tag) != dev->com.pg_data_size || TAG_TYPE(tag) != UFFS_TYPE_DATA || TAG_PAGE_ID(tag) != 3 || TAG_PARENT(tag) != 100 || TAG_SERIAL(tag) != 10 || TAG_BLOCK_TS(tag) != 1) { MSGLN("Tag verify fail!"); goto ext; } MSGLN("Page read/write test succ."); ext: if (node) { uffs_FlashEraseBlock(dev, node->u.list.block); if (HAVE_BADBLOCK(dev)) uffs_BadBlockProcess(dev, node); else uffs_InsertToErasedListHead(dev, node); } if (dev) uffs_PutDevice(dev); if (buf) uffs_BufFreeClone(dev, buf); return TRUE; }
/** * \brief recover bad block * \param[in] dev uffs device */ void uffs_BadBlockRecover(uffs_Device *dev) { TreeNode *good, *bad; uffs_Buf *buf; u16 i; u16 page; uffs_BlockInfo *bc = NULL; uffs_Tags *tag; uffs_Tags newTag; UBOOL succRecov; UBOOL goodBlockIsDirty = U_FALSE; int ret; int region; u8 type; if (dev->bad.block == UFFS_INVALID_BLOCK) return; // pick up an erased good block good = uffs_TreeGetErasedNode(dev); if (good == NULL) { uffs_Perror(UFFS_ERR_SERIOUS, "no free block to replace bad block!"); return; } //recover block bc = uffs_BlockInfoGet(dev, dev->bad.block); if (bc == NULL) { uffs_Perror(UFFS_ERR_SERIOUS, "can't get bad block info"); return; } succRecov = U_TRUE; for (i = 0; i < dev->attr->pages_per_block; i++) { page = uffs_FindPageInBlockWithPageId(dev, bc, i); if(page == UFFS_INVALID_PAGE) { break; //end of last valid page, normal break } page = uffs_FindBestPageInBlock(dev, bc, page); tag = GET_TAG(bc, page); buf = uffs_BufClone(dev, NULL); if (buf == NULL) { uffs_Perror(UFFS_ERR_SERIOUS, "Can't clone a new buf!"); succRecov = U_FALSE; break; } //NOTE: since this is a bad block, we can't guarantee the data is ECC ok, so just load data even ECC is not OK. ret = uffs_LoadPhyDataToBufEccUnCare(dev, buf, bc->block, page); if (ret == U_FAIL) { uffs_Perror(UFFS_ERR_SERIOUS, "I/O error ?"); uffs_BufFreeClone(dev, buf); succRecov = U_FALSE; break; } buf->data_len = TAG_DATA_LEN(tag); 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->parent = TAG_PARENT(tag); buf->serial = TAG_SERIAL(tag); buf->type = TAG_TYPE(tag); buf->page_id = TAG_PAGE_ID(tag); newTag = *tag; TAG_BLOCK_TS(&newTag) = uffs_GetNextBlockTimeStamp(TAG_BLOCK_TS(tag)); ret = uffs_FlashWritePageCombine(dev, good->u.list.block, i, buf, &newTag); goodBlockIsDirty = U_TRUE; uffs_BufFreeClone(dev, buf); if (ret == UFFS_FLASH_IO_ERR) { uffs_Perror(UFFS_ERR_NORMAL, "I/O error ?"); succRecov = U_FALSE; break; } } if (succRecov == U_TRUE) { //successful recover bad block, so need to mark bad block, and replace with good one region = SEARCH_REGION_DIR|SEARCH_REGION_FILE|SEARCH_REGION_DATA; bad = uffs_TreeFindNodeByBlock(dev, dev->bad.block, ®ion); if (bad != NULL) { switch (region) { case SEARCH_REGION_DIR: bad->u.dir.block = good->u.list.block; type = UFFS_TYPE_DIR; break; case SEARCH_REGION_FILE: bad->u.file.block = good->u.list.block; type = UFFS_TYPE_FILE; break; case SEARCH_REGION_DATA: bad->u.data.block = good->u.list.block; type = UFFS_TYPE_DATA; } //from now, the 'bad' is actually good block :))) uffs_Perror(UFFS_ERR_NOISY, "new bad block %d found, and replaced by %d!", dev->bad.block, good->u.list.block); uffs_BlockInfoExpire(dev, bc, UFFS_ALL_PAGES); //we reuse the 'good' node as bad block node, and process the bad block. good->u.list.block = dev->bad.block; uffs_BadBlockProcess(dev, good); } else { uffs_Perror(UFFS_ERR_SERIOUS, "can't find the reported bad block(%d) in the tree???", dev->bad.block); if (goodBlockIsDirty == U_TRUE) dev->ops->EraseBlock(dev, good->u.list.block); uffs_TreeInsertToErasedListTail(dev, good); } } else { if (goodBlockIsDirty == U_TRUE) dev->ops->EraseBlock(dev, good->u.list.block); uffs_TreeInsertToErasedListTail(dev, good); //put back to erased list } uffs_BlockInfoPut(dev, bc); }