static uffs_DIR * GetDirEntry(void) { uffs_DIR *dirp = (uffs_DIR *) uffs_PoolGet(&_dir_pool); if (dirp) memset(dirp, 0, sizeof(uffs_DIR)); return dirp; }
/** * alloc a new object structure * \return the new object */ uffs_Object * uffs_GetObject(void) { uffs_Object * obj; obj = (uffs_Object *) uffs_PoolGet(&_object_pool); if (obj) { memset(obj, 0, sizeof(uffs_Object)); obj->attr_loaded = U_FALSE; obj->open_succ = U_FALSE; } return obj; }
/** * mark a clean page tag as 'dirty' and 'invalid'. * * \param[in] dev uffs device * \param[in] bc block info * \param[in] page * * \return #UFFS_FLASH_NO_ERR: success. * #UFFS_FLASH_IO_ERR: I/O error, expect retry ? * #UFFS_FLASH_BAD_BLK: a new bad block detected. */ int uffs_FlashMarkDirtyPage(uffs_Device *dev, uffs_BlockInfo *bc, int page) { u8 *spare; uffs_FlashOps *ops = dev->ops; UBOOL is_bad = U_FALSE; int ret = UFFS_FLASH_UNKNOWN_ERR; int block = bc->block; uffs_Tags *tag = GET_TAG(bc, page); struct uffs_TagStoreSt *ts = &tag->s; spare = (u8 *) uffs_PoolGet(SPOOL(dev)); if (spare == NULL) goto ext; memset(ts, 0xFF, sizeof(struct uffs_TagStoreSt)); ts->dirty = TAG_DIRTY; // set only 'dirty' bit, leave 'valid' bit to 1 (invalid). if (dev->attr->ecc_opt != UFFS_ECC_NONE) TagMakeEcc(ts); if (ops->WritePageWithLayout) { ret = ops->WritePageWithLayout(dev, block, page, NULL, 0, NULL, ts); } else { uffs_FlashMakeSpare(dev, ts, NULL, spare); ret = ops->WritePage(dev, block, page, NULL, 0, spare, dev->mem.spare_data_size); } if (UFFS_FLASH_IS_BAD_BLOCK(ret)) is_bad = U_TRUE; ext: if (is_bad) uffs_BadBlockAdd(dev, block); if (spare) uffs_PoolPut(SPOOL(dev), spare); return ret; }
static void _ForceFormatAndCheckBlock(uffs_Device *dev, int block) { int i, j; uffs_Buf *buf = NULL; UBOOL bad = U_TRUE; URET ret; struct uffs_FlashOpsSt *ops = dev->ops; struct uffs_TagStoreSt ts; u8 *spare = NULL; buf = uffs_BufClone(dev, NULL); if (buf == NULL) { uffs_Perror(UFFS_MSG_SERIOUS, "Alloc page buffer fail ! Format stoped."); goto ext; } spare = (u8 *)uffs_PoolGet(SPOOL(dev)); if (spare == NULL) goto ext; //step 1: Erase, fully fill with 0x0, and check ret = uffs_FlashEraseBlock(dev, block); if (UFFS_FLASH_IS_BAD_BLOCK(ret)) goto bad_out; memset(buf->header, 0, dev->com.pg_size); memset(&ts, 0, sizeof(ts)); memset(spare, 0, dev->attr->spare_size); for (i = 0; i < dev->attr->pages_per_block; i++) { if (ops->WritePageWithLayout) ret = ops->WritePageWithLayout(dev, block, i, buf->header, dev->com.pg_size, NULL, &ts); else ret = ops->WritePage(dev, block, i, buf->header, dev->com.pg_size, spare, dev->attr->spare_size); if (UFFS_FLASH_IS_BAD_BLOCK(ret)) goto bad_out; } for (i = 0; i < dev->attr->pages_per_block; i++) { memset(buf->header, 0xFF, dev->com.pg_size); memset(&ts, 0xFF, sizeof(ts)); memset(spare, 0xFF, dev->attr->spare_size); if (ops->ReadPageWithLayout) { ret = ops->ReadPageWithLayout(dev, block, i, buf->header, dev->com.pg_size, NULL, &ts, NULL); if (UFFS_FLASH_IS_BAD_BLOCK(ret)) goto bad_out; for (j = 0; j < dev->com.pg_size; j++) if (buf->header[j] != 0) goto bad_out; for (j = 0; j < sizeof(ts); j++) if (((u8 *)&ts)[j] != 0) goto bad_out; } else { ret = ops->ReadPage(dev, block, i, buf->header, dev->com.pg_size, NULL, spare, dev->attr->spare_size); if (UFFS_FLASH_IS_BAD_BLOCK(ret)) goto bad_out; for (j = 0; j < dev->com.pg_size; j++) if (buf->header[j] != 0) goto bad_out; for (j = 0; j < dev->attr->spare_size; j++) if (spare[j] != 0) goto bad_out; } } //step 2: Erase, and check ret = uffs_FlashEraseBlock(dev, block); if (UFFS_FLASH_IS_BAD_BLOCK(ret)) goto bad_out; for (i = 0; i < dev->attr->pages_per_block; i++) { memset(buf->header, 0, dev->com.pg_size); memset(&ts, 0, sizeof(ts)); memset(spare, 0, dev->attr->spare_size); if (ops->ReadPageWithLayout) { ret = ops->ReadPageWithLayout(dev, block, i, buf->header, dev->com.pg_size, NULL, &ts, NULL); if (UFFS_FLASH_IS_BAD_BLOCK(ret)) goto bad_out; for (j = 0; j < dev->com.pg_size; j++) if (buf->header[j] != 0xFF) goto bad_out; for (j = 0; j < sizeof(ts); j++) if (((u8 *)&ts)[j] != 0xFF) goto bad_out; } else { ret = ops->ReadPage(dev, block, i, buf->header, dev->com.pg_size, NULL, spare, dev->attr->spare_size); if (UFFS_FLASH_IS_BAD_BLOCK(ret)) goto bad_out; for (j = 0; j < dev->com.pg_size; j++) if (buf->header[j] != 0xFF) goto bad_out; for (j = 0; j < dev->attr->spare_size; j++) if (spare[j] != 0xFF) goto bad_out; } } // format succ bad = U_FALSE; bad_out: if (bad == U_TRUE) uffs_FlashMarkBadBlock(dev, block); ext: if (buf) uffs_BufFreeClone(dev, buf); if (spare) uffs_PoolPut(SPOOL(dev), spare); return; }
static URET _BuildTreeStepOne(uffs_Device *dev) { int block_lt; uffs_BlockInfo *bc = NULL; TreeNode *node; struct uffs_TreeSt *tree; uffs_Pool *pool; struct uffs_MiniHeaderSt header; URET ret = U_SUCC; struct BlockTypeStatSt st = {0, 0, 0}; tree = &(dev->tree); pool = TPOOL(dev); tree->bad = NULL; tree->bad_count = 0; tree->erased = NULL; tree->erased_tail = NULL; tree->erased_count = 0; uffs_Perror(UFFS_MSG_NOISY, "build tree step one"); // printf("s:%d e:%d\n", dev->par.start, dev->par.end); for (block_lt = dev->par.start; block_lt <= dev->par.end; block_lt++) { bc = uffs_BlockInfoGet(dev, block_lt); if (bc == NULL) { uffs_Perror(UFFS_MSG_SERIOUS, "step one:fail to get block info"); ret = U_FAIL; break; } node = (TreeNode *)uffs_PoolGet(pool); if (node == NULL) { uffs_Perror(UFFS_MSG_SERIOUS, "insufficient tree node!"); ret = U_FAIL; break; } // Need to check bad block at first ! if (uffs_FlashIsBadBlock(dev, block_lt) == U_TRUE) { node->u.list.block = block_lt; uffs_TreeInsertToBadBlockList(dev, node); uffs_Perror(UFFS_MSG_NORMAL, "found bad block %d", block_lt); } else if (uffs_IsPageErased(dev, bc, 0) == U_TRUE) { //@ read one spare: 0 // page 0 tag shows it's an erased block, we need to check the mini header status to make sure it is clean. if (uffs_LoadMiniHeader(dev, block_lt, 0, &header) == U_FAIL) { uffs_Perror(UFFS_MSG_SERIOUS, "I/O error when reading mini header !" "block %d page %d", block_lt, 0); ret = U_FAIL; break; } if (header.status != 0xFF) { // page 0 tag is clean but page data is dirty ??? // this block should be erased immediately ! uffs_FlashEraseBlock(dev, block_lt); } node->u.list.block = block_lt; if (HAVE_BADBLOCK(dev)) { uffs_Perror(UFFS_MSG_NORMAL, "New bad block (%d) discovered.", block_lt); uffs_BadBlockProcess(dev, node); } else { // page 0 is clean does not means all pages in this block are clean, // need to check this block later before use it. uffs_TreeInsertToErasedListTailEx(dev, node, 1); } } else { // this block have valid data page(s). ret = _ScanAndFixUnCleanPage(dev, bc); if (ret == U_FAIL) break; ret = _BuildValidTreeNode(dev, node, bc, &st); if (ret == U_FAIL) break; } uffs_BlockInfoPut(dev, bc); } //end of for if(ret == U_FAIL) uffs_BlockInfoPut(dev, bc); uffs_Perror(UFFS_MSG_NORMAL, "DIR %d, FILE %d, DATA %d", st.dir, st.file, st.data); return ret; }
/** * Check the block by reading all pages. * * \return U_SUCC - all pages are clean, * U_FAIL - block is not clean */ URET uffs_FlashCheckErasedBlock(uffs_Device *dev, int block) { u8 *spare = NULL; uffs_FlashOps *ops = dev->ops; int ret = U_SUCC; int page; int flash_ret; u8 ecc_store[UFFS_MAX_ECC_SIZE]; uffs_TagStore ts; uffs_Buf *buf = NULL; int size = dev->com.pg_size; int i; u8 *p; spare = (u8 *) uffs_PoolGet(SPOOL(dev)); if (spare == NULL) { uffs_Perror(UFFS_MSG_SERIOUS, "Can't allocate spare buf."); goto ext; } buf = uffs_BufClone(dev, NULL); if (buf == NULL) { uffs_Perror(UFFS_MSG_SERIOUS, "Can't clone buf."); goto ext; } for (page = 0; page < dev->attr->pages_per_block; page++) { if (ops->ReadPageWithLayout) { flash_ret = ops->ReadPageWithLayout(dev, block, page, buf->header, size, NULL, &ts, ecc_store); if (flash_ret != UFFS_FLASH_IO_ERR) { // check page tag, should be all 0xFF for (i = 0, p = (u8 *)(&ts); i < sizeof(ts); i++, p++) { if (*p != 0xFF) { ret = U_FAIL; goto ext; } } // for hw or soft ecc, check stored ecc, should be all 0xFF if (dev->attr->ecc_opt == UFFS_ECC_HW || dev->attr->ecc_opt == UFFS_ECC_SOFT) { for (i = 0, p = ecc_store; i < ECC_SIZE(dev); i++, p++) { if (*p != 0xFF) { ret = U_FAIL; goto ext; } } } } } else { flash_ret = ops->ReadPage(dev, block, page, buf->header, size, NULL, spare, dev->attr->spare_size); if (flash_ret != UFFS_FLASH_IO_ERR) { // check spare data, should be all 0xFF for (i = 0, p = spare; i < dev->attr->spare_size; i++, p++) { if (*p != 0xFF) { ret = U_FAIL; goto ext; } } } } if (flash_ret != UFFS_FLASH_IO_ERR) { // check page data, should be all 0xFF for (i = 0, p = buf->header; i < size; i++, p++) { if (*p != 0xFF) { ret = U_FAIL; goto ext; } } } } ext: if (spare) uffs_PoolPut(SPOOL(dev), spare); if (buf) uffs_BufFreeClone(dev, buf); return ret; }
/** * write the whole page, include data and tag * * \param[in] dev uffs device * \param[in] block * \param[in] page * \param[in] buf contains data to be wrote * \param[in] tag tag to be wrote * * \return #UFFS_FLASH_NO_ERR: success. * #UFFS_FLASH_IO_ERR: I/O error, expect retry ? * #UFFS_FLASH_BAD_BLK: a new bad block detected. */ int uffs_FlashWritePageCombine(uffs_Device *dev, int block, int page, uffs_Buf *buf, uffs_Tags *tag) { uffs_FlashOps *ops = dev->ops; int size = dev->com.pg_size; u8 ecc_buf[UFFS_MAX_ECC_SIZE]; u8 *ecc = NULL; u8 *spare; struct uffs_MiniHeaderSt *header; int ret = UFFS_FLASH_UNKNOWN_ERR; UBOOL is_bad = U_FALSE; uffs_Buf *verify_buf; #ifdef CONFIG_PAGE_WRITE_VERIFY uffs_Tags chk_tag; #endif spare = (u8 *) uffs_PoolGet(SPOOL(dev)); if (spare == NULL) goto ext; // setup header header = HEADER(buf); memset(header, 0xFF, sizeof(struct uffs_MiniHeaderSt)); header->status = 0; #ifdef CONFIG_ENABLE_PAGE_DATA_CRC header->crc = uffs_crc16sum(buf->data, size - sizeof(struct uffs_MiniHeaderSt)); #endif // setup tag TAG_DIRTY_BIT(tag) = TAG_DIRTY; //!< set dirty bit TAG_VALID_BIT(tag) = TAG_VALID; //!< set valid bit SEAL_TAG(tag); //!< seal tag (the real seal byte will be set in uffs_FlashMakeSpare()) if (dev->attr->ecc_opt != UFFS_ECC_NONE) TagMakeEcc(&tag->s); else tag->s.tag_ecc = TAG_ECC_DEFAULT; if (dev->attr->ecc_opt == UFFS_ECC_SOFT) { uffs_EccMake(buf->header, size, ecc_buf); ecc = ecc_buf; } else if (dev->attr->ecc_opt == UFFS_ECC_HW) { ecc = ecc_buf; } if (ops->WritePageWithLayout) { ret = ops->WritePageWithLayout(dev, block, page, buf->header, size, ecc, &tag->s); } else { if (!uffs_Assert(!(dev->attr->layout_opt == UFFS_LAYOUT_FLASH || dev->attr->ecc_opt == UFFS_ECC_HW || dev->attr->ecc_opt == UFFS_ECC_HW_AUTO), "WritePageWithLayout() not implemented ?")) { ret = UFFS_FLASH_IO_ERR; goto ext; } uffs_FlashMakeSpare(dev, &tag->s, ecc, spare); ret = ops->WritePage(dev, block, page, buf->header, size, spare, dev->mem.spare_data_size); } if (UFFS_FLASH_IS_BAD_BLOCK(ret)) is_bad = U_TRUE; if (UFFS_FLASH_HAVE_ERR(ret)) goto ext; #ifdef CONFIG_PAGE_WRITE_VERIFY verify_buf = uffs_BufClone(dev, NULL); if (verify_buf) { ret = uffs_FlashReadPage(dev, block, page, verify_buf, U_FALSE); if (!UFFS_FLASH_HAVE_ERR(ret)) { if (memcmp(buf->header, verify_buf->header, size) != 0) { uffs_Perror(UFFS_MSG_NORMAL, "Page write verify failed (block %d page %d)", block, page); ret = UFFS_FLASH_BAD_BLK; } } uffs_BufFreeClone(dev, verify_buf); } else { uffs_Perror(UFFS_MSG_SERIOUS, "Insufficient buf, clone buf failed."); } ret = uffs_FlashReadPageTag(dev, block, page, &chk_tag); if (UFFS_FLASH_HAVE_ERR(ret)) goto ext; if (memcmp(&tag->s, &chk_tag.s, sizeof(uffs_TagStore)) != 0) { uffs_Perror(UFFS_MSG_NORMAL, "Page tag write verify failed (block %d page %d)", block, page); ret = UFFS_FLASH_BAD_BLK; } if (UFFS_FLASH_IS_BAD_BLOCK(ret)) is_bad = U_TRUE; #endif ext: if (is_bad) uffs_BadBlockAdd(dev, block); if (spare) uffs_PoolPut(SPOOL(dev), spare); return ret; }
/** * Read page data to buf (do ECC error correction if needed) * \param[in] dev uffs device * \param[in] block flash block num * \param[in] page flash page num of the block * \param[out] buf holding the read out data * \param[in] skip_ecc skip ecc when reading data from flash * * \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. * #UFFS_FLASH_CRC_ERR: CRC verification failed. * #UFFS_FLASH_UNKNOWN_ERR: * * \note if skip_ecc is U_TRUE, skip CRC as well. */ int uffs_FlashReadPage(uffs_Device *dev, int block, int page, uffs_Buf *buf, UBOOL skip_ecc) { uffs_FlashOps *ops = dev->ops; struct uffs_StorageAttrSt *attr = dev->attr; int size = dev->com.pg_size; u8 ecc_buf[UFFS_MAX_ECC_SIZE]; u8 ecc_store[UFFS_MAX_ECC_SIZE]; UBOOL is_bad = U_FALSE; #ifdef CONFIG_ENABLE_PAGE_DATA_CRC UBOOL crc_ok = U_TRUE; #endif u8 * spare; int ret = UFFS_FLASH_UNKNOWN_ERR; spare = (u8 *) uffs_PoolGet(SPOOL(dev)); if (spare == NULL) goto ext; if (ops->ReadPageWithLayout) { if (skip_ecc) ret = ops->ReadPageWithLayout(dev, block, page, buf->header, size, NULL, NULL, NULL); else ret = ops->ReadPageWithLayout(dev, block, page, buf->header, size, ecc_buf, NULL, ecc_store); } else { if (skip_ecc) ret = ops->ReadPage(dev, block, page, buf->header, size, NULL, NULL, 0); else ret = ops->ReadPage(dev, block, page, buf->header, size, ecc_buf, spare, dev->mem.spare_data_size); } if (UFFS_FLASH_IS_BAD_BLOCK(ret)) is_bad = U_TRUE; if (UFFS_FLASH_HAVE_ERR(ret)) goto ext; #ifdef CONFIG_ENABLE_PAGE_DATA_CRC if (!skip_ecc) { crc_ok = (HEADER(buf)->crc == uffs_crc16sum(buf->data, size - sizeof(struct uffs_MiniHeaderSt)) ? U_TRUE : U_FALSE); if (crc_ok) goto ext; // CRC is matched, no need to do ECC correction. else { if (dev->attr->ecc_opt == UFFS_ECC_NONE || dev->attr->ecc_opt == UFFS_ECC_HW_AUTO) { // ECC is not enabled or ecc correction already done, error return immediately, // otherwise, we try CRC check again after ecc correction. ret = UFFS_FLASH_CRC_ERR; goto ext; } } } #endif // make ECC for UFFS_ECC_SOFT if (attr->ecc_opt == UFFS_ECC_SOFT && !skip_ecc) uffs_EccMake(buf->header, size, ecc_buf); // unload ecc_store if driver doesn't do the layout if (ops->ReadPageWithLayout == NULL) { if (!skip_ecc && (attr->ecc_opt == UFFS_ECC_SOFT || attr->ecc_opt == UFFS_ECC_HW)) uffs_FlashUnloadSpare(dev, spare, NULL, ecc_store); } // check page data ecc if (!skip_ecc && (dev->attr->ecc_opt == UFFS_ECC_SOFT || dev->attr->ecc_opt == UFFS_ECC_HW)) { ret = uffs_EccCorrect(buf->header, size, ecc_store, ecc_buf); 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; } #ifdef CONFIG_ENABLE_PAGE_DATA_CRC if (!skip_ecc && !UFFS_FLASH_HAVE_ERR(ret)) { // Everything seems ok, do CRC check again. if (HEADER(buf)->crc == uffs_crc16sum(buf->data, size - sizeof(struct uffs_MiniHeaderSt))) { ret = UFFS_FLASH_CRC_ERR; goto ext; } } #endif ext: switch(ret) { case UFFS_FLASH_IO_ERR: uffs_Perror(UFFS_MSG_NORMAL, "Read block %d page %d I/O error", block, page); break; case UFFS_FLASH_ECC_FAIL: uffs_Perror(UFFS_MSG_NORMAL, "Read block %d page %d ECC failed", block, page); ret = UFFS_FLASH_BAD_BLK; // treat ECC FAIL as BAD BLOCK is_bad = U_TRUE; break; case UFFS_FLASH_ECC_OK: uffs_Perror(UFFS_MSG_NORMAL, "Read block %d page %d bit flip corrected by ECC", block, page); break; case UFFS_FLASH_BAD_BLK: uffs_Perror(UFFS_MSG_NORMAL, "Read block %d page %d BAD BLOCK found", block, page); break; case UFFS_FLASH_UNKNOWN_ERR: uffs_Perror(UFFS_MSG_NORMAL, "Read block %d page %d UNKNOWN error!", block, page); break; case UFFS_FLASH_CRC_ERR: uffs_Perror(UFFS_MSG_NORMAL, "Read block %d page %d CRC failed", block, page); break; default: break; } if (is_bad) uffs_BadBlockAdd(dev, block); if (spare) uffs_PoolPut(SPOOL(dev), spare); return ret; }
/** * 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; }