/** * \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 Is the block erased ? * \param[in] dev uffs device * \param[in] bc block info * \param[in] page page number to be check * \retval U_TRUE block is erased, ready to use * \retval U_FALSE block is dirty, maybe use by file */ UBOOL uffs_IsPageErased(uffs_Device *dev, uffs_BlockInfo *bc, u16 page) { uffs_Tags *tag; uffs_BlockInfoLoad(dev, bc, page); tag = GET_TAG(bc, page); if (!TAG_IS_DIRTY(tag) && !TAG_IS_VALID(tag)) { return U_TRUE; } return U_FALSE; }
static URET _ScanAndFixUnCleanPage(uffs_Device *dev, uffs_BlockInfo *bc) { int page; uffs_Tags *tag; struct uffs_MiniHeaderSt header; /* in most case, the valid block contents fewer free page, so it's better scan from the last page ... to page 1. note: scanning page 0 is not necessary, will check it later. The worse case: read (pages_per_block - 1) * (mini header + spares) ! most case: read one spare. */ for (page = dev->attr->pages_per_block - 1; page > 0; page--) { uffs_BlockInfoLoad(dev, bc, page); tag = GET_TAG(bc, page); if (TAG_IS_SEALED(tag)) break; // tag sealed, no unclean page in this block. if (TAG_IS_DIRTY(tag) || TAG_IS_VALID(tag)) { // tag not sealed but dirty/valid ? uffs_Perror(UFFS_MSG_NORMAL, "unclean page found, block %d page %d", bc->block, page); // ok, an unclean page found. // This unclean page can be identified by tag. // We can leave it as it is, but performing a block recover would be good ? // There won't be another unclean page in this block ... stop here. break; } // now we have a clean tag (all 0xFF ?). Need to check mini header to see if it's an unclean page. if (uffs_LoadMiniHeader(dev, bc->block, page, &header) == U_FAIL) return U_FAIL; if (header.status != 0xFF) { // page data is dirty? this is an unclean page and we should explicitly mark tag as 'dirty and invalid'. // This writing does not violate "no partial program" claim, because we are writing to a clean page spare. uffs_Perror(UFFS_MSG_NORMAL, "unclean page found, block %d page %d, mark it.", bc->block, page); uffs_FlashMarkDirtyPage(dev, bc, page); } } return U_SUCC; }
/** * \brief Is the block erased ? * \param[in] dev uffs device * \param[in] bc block info * \param[in] page page number to be check * \retval U_TRUE block is erased, ready to use * \retval U_FALSE block is dirty, maybe use by file */ UBOOL uffs_IsPageErased(uffs_Device *dev, uffs_BlockInfo *bc, u16 page) { uffs_Tags *tag; URET ret; ret = uffs_BlockInfoLoad(dev, bc, page); if (ret == U_SUCC) { tag = GET_TAG(bc, page); if (!TAG_IS_SEALED(tag) && !TAG_IS_DIRTY(tag) && !TAG_IS_VALID(tag)) { return U_TRUE; } } return U_FALSE; }
/* 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; }
static URET _BuildValidTreeNode(uffs_Device *dev, TreeNode *node, //!< empty node uffs_BlockInfo *bc, struct BlockTypeStatSt *st) { uffs_Tags *tag; TreeNode *node_alt; u16 block, parent, serial, block_alt, block_save; uffs_BlockInfo *bc_alt; u8 type; int page; UBOOL needToInsertToTree = U_FALSE; uffs_Buf *buf = NULL; uffs_FileInfo *info; u16 data_sum = 0; // check the first page on the block ... uffs_BlockInfoLoad(dev, bc, 0); tag = GET_TAG(bc, 0); //get first page's tag if (!TAG_IS_DIRTY(tag)) { // should never go here ... unless mark dirty page failed ? uffs_Perror(UFFS_MSG_NORMAL, "First page is clean in a non-erased block ?"); return U_FAIL; } if (!TAG_IS_VALID(tag)) { //first page is invalid ? should be erased now! uffs_Perror(UFFS_MSG_NORMAL, "first page in block %d is invalid, will be erased now!", bc->block); goto process_invalid_block; } block = bc->block; parent = TAG_PARENT(tag); serial = TAG_SERIAL(tag); type = TAG_TYPE(tag); // check if there is an 'alternative block' // (node which has the same serial number) in tree ? node_alt = uffs_FindFromTree(dev, type, parent, serial); if (node_alt != NULL) { //find a alternate node ! need to check the timestamp ! block_alt = _GetBlockFromNode(type, node_alt); uffs_Perror(UFFS_MSG_NORMAL, "Process unclean block (%d vs %d)", block, block_alt); if (block_alt == INVALID_UFFS_SERIAL) { uffs_Perror(UFFS_MSG_SERIOUS, "invalid block ?"); return U_FAIL; } bc_alt = uffs_BlockInfoGet(dev, block_alt); if (bc_alt == NULL) { uffs_Perror(UFFS_MSG_SERIOUS, "can't get block info "); return U_FAIL; } uffs_BlockInfoLoad(dev, bc_alt, 0); if (uffs_IsSrcNewerThanObj ( TAG_BLOCK_TS(tag), TAG_BLOCK_TS(GET_TAG(bc_alt, 0))) == U_TRUE) { //the node is newer than node_alt, so keep node_alt, and erase node uffs_FlashEraseBlock(dev, block); node->u.list.block = block; if (HAVE_BADBLOCK(dev)) uffs_BadBlockProcess(dev, node); else uffs_TreeInsertToErasedListTail(dev, node); uffs_BlockInfoPut(dev, bc_alt); //put back bc_alt before we return. return U_SUCC; } else { //the node is older than node_alt, so keep node, and erase node_alt //we use node as erased node to insert to erased list block_save = _GetBlockFromNode(type, node_alt); uffs_FlashEraseBlock(dev, block_save); node->u.list.block = block_save; if (HAVE_BADBLOCK(dev)) uffs_BadBlockProcess(dev, node); else uffs_TreeInsertToErasedListTail(dev, node); //put back bc_alt because we don't need it anymore. uffs_BlockInfoPut(dev, bc_alt); //use node_alt to store new informations in following node = node_alt; needToInsertToTree = U_FALSE; } } else { needToInsertToTree = U_TRUE; } if (type == UFFS_TYPE_DIR || type == UFFS_TYPE_FILE) { buf = uffs_BufClone(dev, NULL); if (buf == NULL) return U_FAIL; uffs_BlockInfoLoad(dev, bc, UFFS_ALL_PAGES); page = uffs_FindPageInBlockWithPageId(dev, bc, 0); if (page == UFFS_INVALID_PAGE) { uffs_BufFreeClone(dev, buf); uffs_Perror(UFFS_MSG_SERIOUS, "Can't find any valid page for page_id=0 ? invalid block !" "this might be caused by the tag layout change.\n"); goto process_invalid_block; } page = uffs_FindBestPageInBlock(dev, bc, page); uffs_FlashReadPage(dev, block, page, buf, U_FALSE); info = (uffs_FileInfo *) (buf->data); data_sum = uffs_MakeSum16(info->name, info->name_len); uffs_BufFreeClone(dev, buf); } switch (type) { case UFFS_TYPE_DIR: node->u.dir.block = bc->block; node->u.dir.checksum = data_sum; node->u.dir.parent = TAG_PARENT(tag); node->u.dir.serial = TAG_SERIAL(tag); st->dir++; break; case UFFS_TYPE_FILE: node->u.file.block = bc->block; node->u.file.checksum = data_sum; node->u.file.parent = TAG_PARENT(tag); node->u.file.serial = TAG_SERIAL(tag); node->u.file.len = uffs_GetBlockFileDataLength(dev, bc, UFFS_TYPE_FILE); st->file++; break; case UFFS_TYPE_DATA: node->u.data.block = bc->block; node->u.data.parent = TAG_PARENT(tag); node->u.data.serial = TAG_SERIAL(tag); node->u.data.len = uffs_GetBlockFileDataLength(dev, bc, UFFS_TYPE_DATA); st->data++; break; } if (needToInsertToTree == U_TRUE) { uffs_InsertNodeToTree(dev, type, node); } return U_SUCC; process_invalid_block: /* erase the invalid block */ uffs_FlashEraseBlock(dev, bc->block); node->u.list.block = bc->block; if (HAVE_BADBLOCK(dev)) uffs_BadBlockProcess(dev, node); else uffs_TreeInsertToErasedListTail(dev, node); return U_SUCC; }
/** * Read tag from page spare * * \param[in] dev uffs device * \param[in] block flash block num * \param[in] page flash page num * \param[out] tag tag to be filled * * \return #UFFS_FLASH_NO_ERR: success and/or has no flip bits. * #UFFS_FLASH_IO_ERR: I/O error, expect retry ? * #UFFS_FLASH_ECC_FAIL: spare data has flip bits and ecc correct failed. * #UFFS_FLASH_ECC_OK: spare data has flip bits and corrected by ecc. */ int uffs_FlashReadPageTag(uffs_Device *dev, int block, int page, uffs_Tags *tag) { uffs_FlashOps *ops = dev->ops; u8 * spare_buf; int ret = UFFS_FLASH_UNKNOWN_ERR; int tmp_ret; UBOOL is_bad = U_FALSE; spare_buf = (u8 *) uffs_PoolGet(SPOOL(dev)); if (spare_buf == NULL) goto ext; if (ops->ReadPageWithLayout) { ret = ops->ReadPageWithLayout(dev, block, page, NULL, 0, NULL, tag ? &tag->s : NULL, NULL); if (tag) tag->seal_byte = (ret == UFFS_FLASH_NOT_SEALED ? 0xFF : 0); } else { ret = ops->ReadPage(dev, block, page, NULL, 0, NULL, spare_buf, dev->mem.spare_data_size); if (tag) { tag->seal_byte = SEAL_BYTE(dev, spare_buf); if (!UFFS_FLASH_HAVE_ERR(ret)) uffs_FlashUnloadSpare(dev, spare_buf, &tag->s, NULL); } } if (UFFS_FLASH_IS_BAD_BLOCK(ret)) is_bad = U_TRUE; if (UFFS_FLASH_HAVE_ERR(ret)) goto ext; if (tag) { if (!TAG_IS_SEALED(tag)) // not sealed ? don't try tag ECC correction goto ext; if (!TAG_IS_VALID(tag)) { if (dev->attr->ecc_opt != UFFS_ECC_NONE) { /* * There could be a special case if: * a) tag is sealed (so we are here), and * b) s.valid == 1 and this bit is a 'bad' bit, and * c) after tag ECC (corrected by tag ECC) s.valid == 0. * * So we need to try tag ECC (don't treat it as bad block if ECC failed) */ struct uffs_TagStoreSt s; memcpy(&s, &tag->s, sizeof(s)); tmp_ret = TagEccCorrect(&s); if (tmp_ret <= 0 || !TAG_IS_VALID(tag)) // can not corrected by ECC. goto ext; } else { goto ext; } } // do tag ecc correction if (dev->attr->ecc_opt != UFFS_ECC_NONE) { ret = TagEccCorrect(&tag->s); ret = (ret < 0 ? UFFS_FLASH_ECC_FAIL : (ret > 0 ? UFFS_FLASH_ECC_OK : UFFS_FLASH_NO_ERR)); if (UFFS_FLASH_IS_BAD_BLOCK(ret)) is_bad = U_TRUE; if (UFFS_FLASH_HAVE_ERR(ret)) goto ext; } } ext: if (is_bad) { uffs_BadBlockAdd(dev, block); uffs_Perror(UFFS_MSG_NORMAL, "A new bad block (%d) is detected.", block); } if (spare_buf) uffs_PoolPut(SPOOL(dev), spare_buf); return ret; }