www.pudn.com > efs.rar > fs_pm.c
/**********************************************************************
* fs_pm.c
*
* Copyright (C) 2003, 2004, 2005, 2006, Qualcomm, Inc.
* Page Manager for EFS.
*/
/*===========================================================================
EDIT HISTORY FOR MODULE
This section contains comments describing changes made to the module.
Notice that changes are listed in reverse chronological order.
$Header: //depot/asic/MSMSHARED/services/efs/MSM_EFS.01.02/fs_pm.c#18 $ $DateTime: 2006/11/13 09:22:31 $ $Author: davidb $
when who what, where, why
-------- --- ------------------------------------------------------
11/09/06 dlb Add pm query of freemap.
09/20/06 sh Only resume the erases that were suspended in read.
09/20/06 dlb Lint cleanup.
09/14/06 sh Do not leave NOR suspended after a read.
09/11/06 dlb Use flash driver wrappers.
08/23/06 dlb Add code to log all reverse changes at startup.
08/18/06 yg Fixed Compiler warning for NOR targets.
07/03/06 sh Moved dog & lcd_message prototypes to fs_pm.c
26/06/06 yg Memory reduction effort
06/13/06 dlb Clean up revision history a bit.
05/07/06 sh Fixed RVCT compiler warnings
01/13/06 dlb Remove extra test from unit test.
12/14/05 dlb Handle NOR partial writes on bad powerdown.
10/30/05 sh Lint cleanup.
11/03/05 dlb Proper page ordering for large NAND.
08/24/05 sh Allow 50ms per erase work unit instead of 20ms.
05/26/05 sd Compilation fixes for L4.
01/04/05 dlb Update copyright line.
12/30/04 dlb Remove excess infiltration of factory image code.
10/15/04 dlb Update copyright line.
10/07/04 dlb Whitespace cleanup.
08/18/04 dlb Faster initial start on NOR.
07/09/04 drh Change "assert" to "ASSERT"
05/20/04 dlb Restart page manager after factory start.
05/05/04 dlb Handle page write failures in data region.
04/20/04 dlb Fix problem with startups and no transactions.
01/14/04 adm Added free count setting for ptable_gc in case of factory
start.
12/11/03 dlb Enable urgent GC on NOR flash.
11/13/03 dlb Increase space utilization.
10/17/03 gr Moved a couple of functions out to fs_util.c.
06/17/03 bgc Added garbage logging of garbaged incomplete
transactions from previous bad powerdown.
06/17/03 dlb Fixed erase failure handling code.
06/16/03 dlb Formatting cleanup.
06/15/03 gr Added support for recovery from erase failures.
06/12/03 adm Add support for factory start.
05/28/03 bgc Removed clipping of unfinished work to a block.
Cleaned up fs_pm_single().
05/22/03 dlb Remove an extraneous log flush.
05/15/03 jkl Return 0 if clock time become negative or too large.
04/24/03 bgc Added calls to ptable->begin_erase() to invalidate
the ptable during the erase. All pages will be
considered garbage until ptable->erase() is called
to mark the block as empty and valid.
04/07/03 gr Added space limit computation for NAND devices.
Somewhere here, the dates were in a different format.
!03/04/04 bgc Added support for incremental garbage collection for
! NOR devices. Erases are left running between
! operations. Suspend and resume are used to spread out
! erases over several EFS transactions.
!03/03/07 dlb Add support for NAND.
!03/02/25 bgc Added more fixes for fs_pm_log_visit_node().
!03/02/25 bgc Removed dealloc effects in fs_pm_log_visit_node() to
! prevent gc_next from skipping to anywhere in the flash.
!03/02/13 dlb Created from fs_pm_gc.c. Improved bad power down.
===========================================================================*/
#include
#ifdef FEATURE_IG_EFS_EXT_SERVER
#include "amssassert.h"
#else
#include "assert.h"
#endif
#ifndef FS_STANDALONE
#include "dog.h"
#ifndef FEATURE_IG_EFS_EXT_SERVER
extern void lcd_debug_message (char *msg);
#endif
#endif
#include "fs_sys_types.h"
#include "fs_err.h"
#include "fs_pm.h"
#include "fs_buffer.h"
#include "fs_util.h"
#include "qw.h"
static void cleanup_logs (fs_pm_flash_t gc);
#define BUMP_PAGE(gc, pvar) \
do { \
(pvar)++; \
if ((pvar) >= (gc)->max_page) \
(pvar) = (gc)->min_page; \
} while (0)
#define BUMP_BLOCK(gc, pvar) \
do { \
(pvar) = ((pvar) & (gc)->ptable->super.block_mask) + \
(gc)->ptable->super.data.block_size; \
if ((pvar) >= (gc)->max_page) \
(pvar) = (gc)->min_page; \
} while (0)
/*
* This is the minimum amount of time that we will wait for a NOR
* Erase operation to proceed before suspending it.
* Really, this should be defined by the particular NOR flash, but
* it is thought to be sufficient for all parts.
* This value was previously 20, which proved too small for AMD NOR
* parts, which only make progress in 10ms increments.
*/
#define FS_NOR_ERASE_INCREMENT 50
static void fs_pm_init_ops (struct fs_pm_ops *);
static void
fs_pm_log_visit_node (void *vgc, fs_log_code_t code, uint32 *args);
static void fs_pm_inc_gc_start_op (fs_pm_flash_t gc);
static void fs_pm_inc_gc_end_op (fs_pm_flash_t gc);
static void fs_pm_inc_gc_erase (fs_pm_flash_t gc);
static void fs_pm_suspend_erase(fs_pm_flash_t gc, boolean do_work );
static void fs_pm_do_urgent (fs_pm_flash_t gc, boolean allow_gc);
static int fs_pm_top_off_log(fs_pm_flash_t gc);
#ifdef FEATURE_EFS_EFS2_ON_NAND
static void nand_flush_superblock (fs_pm_flash_t gc);
static void
fs_pm_log_visit_nand_node (void *vgc, fs_log_code_t code, uint32 *args);
static int nand_log_check (fs_pm_flash_t gc);
void fs_ptable_init_nand (
fs_ptable_t pt,
fs_log_t log,
int *fresh_start);
#endif
#ifdef FEATURE_EFS_EFS2_ON_NOR
void fs_ptable_init_nor (
fs_ptable_t pt,
fs_log_t log,
int *fresh_start);
#endif
#ifdef FS_UNIT_TEST
#error code not present
#endif
/**********************************************************************
* XXX: May want to support more than one ptable instance eventually.
* Currently a single union type allows either type of ptable to be used
* below. To allow more than one instance, the ptable init should allocate
* the appropriate type, probably from a pool with compile-time decided
* sizes. */
static union fs_ptable_combined_data ptable_pool[1];
static int ptable_pool_used = 0;
/* An upper bound on the time it takes (in milliseconds) for initialization on
* a fresh start.
*/
#define FS_FRESH_START_INIT_TIME 160000
void
fs_ptable_init (
fs_ptable_t pt,
fs_device_t dev,
fs_log_t log,
int *fresh_start,
fs_logr_t logr)
{
pt->dev = dev;
pt->logr = logr;
fs_super_init (&pt->super, dev, fresh_start);
#ifndef FS_STANDALONE
if (*fresh_start) {
#ifndef FEATURE_UI_CORE_REMOVED
#ifndef FEATURE_IG_EFS_EXT_SERVER
lcd_debug_message ("File System Startup. Please Wait");
#endif
#endif
/* If a fresh start on a NOR flash, EFS initialization might take a while.
* Let the watchdog know.
*/
if (FS_ISNOR (&pt->super))
dog_set_startup_time (FS_FRESH_START_INIT_TIME);
}
#endif
if (FS_ISNOR (&pt->super)){
#ifdef FEATURE_EFS_EFS2_ON_NOR
fs_ptable_init_nor (pt, log, fresh_start);
#endif
}
else{
#ifdef FEATURE_EFS_EFS2_ON_NAND
fs_ptable_init_nand (pt, log, fresh_start);
#endif
}
}
/* Initialize the GC. */
void
fs_pm_init (
fs_pm_flash_t gc,
fs_device_t dev)
{
int fresh_start;
block_id block;
page_id tmp_page, tmp_stop;
page_id page_state;
page_id ptmp;
page_id erased_span_start = 0;
int best_span_start;
int erased_span_length, best_span_length;
int span_state;
int result;
uint32 args[3];
erased_span_start = 0;
fs_pm_init_ops (&gc->parent.ops);
gc->dev = dev;
gc->super_head = 0;
gc->super_tail = 0;
gc->inside_xact = 0;
gc->is_used_cb = NULL;
/* Incremental GC init */
gc->erase_state = FS_PM_ERASE_IDLE;
gc->unfinished_work = 0;
gc->erase_factor = 60;
gc->erase_time = 1000;
gc->next_super_page = INVALID_PAGE_ID;
if (ptable_pool_used > 0) {
FS_ERR_FATAL ("More than one GC/Ptable instance not supported",
0, 0, 0);
}
gc->ptable = &ptable_pool[0].nor.parent;
fs_log_init (&gc->log, dev);
fs_ptable_init (gc->ptable, dev, &gc->log, &fresh_start, &gc->logr);
if (FS_ISNOR (&gc->ptable->super)) {
gc->min_page = 0;
gc->max_page = gc->ptable->super.total_pages;
} else { /* NAND */
gc->min_page = 0;
gc->max_page = gc->ptable->super.data.u.nand.regions[0]
<< gc->ptable->super.block_shift;
/* Setup the log region. */
fs_logr_init (&gc->logr, &gc->ptable->super, fresh_start);
}
/* If this is a fresh start, need to "initialize" the garbage collector
* parameters. */
if (fresh_start) {
gc->alloc_next = 0;
gc->state = FS_PM_HEAD;
if (FS_ISNOR (&gc->ptable->super)) {
/* Erase the first two blocks of the flash to give the GC enough
* space to work with. The rest of the blocks will be erased as
* needed by the GC. */
gc->free_count = 0;
for (block = 0; block < 2; block++) {
result = fs_flash_erase_block (gc->dev, block, FS_FOP_USER);
if (result != FS_DEVICE_OK) {
FS_ERR_FATAL ("Unable to erase blocks to freshen filesystem",
0, 0, 0);
}
gc->free_count += gc->ptable->erase (gc->ptable, block);
}
gc->gc_next = 2 << gc->ptable->super.block_shift;
} else {
gc->gc_next = 0;
gc->free_count = 0;
}
} else {
/* Read the initial parameters out of the superblock. */
gc->gc_next = gc->ptable->super.data.gc_next[0];
gc->alloc_next = gc->ptable->super.data.alloc_next[0];
/* Replay the log, which can set table entries, as well as adjust these
* values. */
/*printf ("Pre log iterate: alloc=0x%x, gc=0x%x\n",
gc->alloc_next, gc->gc_next);*/
if (FS_ISNOR (&gc->ptable->super)) {
gc->ptable->log_iterate (gc->ptable, &gc->log,
fs_pm_log_visit_node, (void *) gc);
} else {
#ifdef FEATURE_EFS_EFS2_ON_NAND
fs_logr_iterate (&gc->logr, &gc->log,
fs_pm_log_visit_nand_node, (void *) gc);
#endif
}
/*printf ("Post log iterate: alloc=0x%x, gc=0x%x\n",
gc->alloc_next, gc->gc_next);*/
/* Perform mid-point initialization. */
if (gc->ptable->mid_init != NULL)
gc->ptable->mid_init (gc->ptable);
if (FS_ISNOR (&gc->ptable->super)) {
/* Set the GC state appropriately. */
if ((gc->gc_next & ~gc->ptable->super.block_mask) == 0)
gc->state = FS_PM_HEAD;
else
gc->state = FS_PM_MOVING;
/* Now, look for the block that is really erased. */
/* This misses the case where the previous un-recorded write
* really did wrap to the next block, but the last page written
* contained all FF's. However, what will happen at this point, is
* that the alloc will then skip this next block, and won't get it
* until next time around. The only problem is that we might run out
* of space to perform a garbage collection.
* XXX: Try to come up with a way to work around this. */
ptmp = gc->alloc_next;
/* Scan forward looking for a large erased region from the last known
* alloc_next point */
tmp_stop = gc->gc_next & gc->ptable->super.block_mask;
/* tmp_stop is the block being garbage collected. The erased region
* is somewhere between alloc_next and gc_next.
*/
span_state = 0; /* Start in search state */
erased_span_length = 0; /* Length of current erased span */
best_span_length = 0 /* Length of best erased span */;
best_span_start = -1; /* Intially invalid */
while(ptmp != tmp_stop)
{
page_state = gc->ptable->get_reverse (gc->ptable, ptmp);
if(page_state == FS_RMAP_PAGE_RESERVED)
{
/* Page tables do not count towards space */
BUMP_PAGE(gc, ptmp);
continue;
}
switch(span_state)
{
/* STATE 0: Searching for erased span start */
case 0:
/* Is page truly erased? */
if(page_state == FS_RMAP_BLOCK_ERASED &&
fs_flash_is_erased (gc->dev, ptmp))
{
/* Start an erased span */
erased_span_start = ptmp;
erased_span_length = 0;
span_state = 1;
}
break;
case 1: /* Found a span, looking for end */
if(page_state == FS_RMAP_BLOCK_ERASED &&
fs_flash_is_erased (gc->dev, ptmp))
{
erased_span_length++;
} else {
span_state = 0;
}
if(erased_span_length > best_span_length)
{
best_span_length = erased_span_length;
best_span_start = erased_span_start;
}
}
if(best_span_length >= (int)gc->ptable->super.data.block_size)
{
/* Optimization: One block of erased pages is good
* enough to ensure that this is the erased region */
break;
}
BUMP_PAGE(gc,ptmp);
}
if(best_span_start < 0) {
FS_ERR_FATAL ("GC did not find an erased span of blocks on restart",
0,0,0);
}
if(((best_span_start & gc->ptable->super.block_mask) >
(gc->alloc_next & gc->ptable->super.block_mask)) &&
((best_span_start & gc->ptable->super.block_mask) <
(gc->gc_next & gc->ptable->super.block_mask)))
{
int erase_result;
page_id p;
page_state = 0;
p = (best_span_start & gc->ptable->super.block_mask);
for( ; p < (page_id)best_span_start; p++)
{
page_state = gc->ptable->get_reverse (gc->ptable, p);
if(page_state != FS_RMAP_BLOCK_ERASED) break; /* cannot erase */
}
/* Was the last page in the block erased? */
if(page_state == FS_RMAP_BLOCK_ERASED)
{
int eblock;
/* This current block is all garbage and erased.
* Erase the garbage*/
/* printf("\nErasing block 0x%x (0x%x--0x%x) for alloc_next 0x%x "
"best 0x%x gc_next 0x%x\n",
best_span_start >> gc->ptable->super.block_shift,
best_span_start & gc->ptable->super.block_mask,
(((best_span_start >> gc->ptable->super.block_shift) + 1)<<
gc->ptable->super.block_shift)-1,
gc->alloc_next, best_span_start, gc->gc_next);
*/
eblock = (best_span_start >> gc->ptable->super.block_shift);
gc->ptable->begin_erase (gc->ptable, eblock);
erase_result = fs_flash_erase_block (gc->dev, eblock,
FS_FOP_USER);
if (erase_result != 0) {
FS_ERR_FATAL ("Unable to erase block", 0, 0, 0);
}
(void) gc->ptable->erase (gc->ptable, eblock);
best_span_start = (best_span_start & gc->ptable->super.block_mask);
}
}
/* Now best_span_start points to where we really want alloc_next
* pointing to. Go through, and mark any pages in between as garbage. */
while (gc->alloc_next != (page_id)best_span_start) {
/* NOR specific. */
page_state = gc->ptable->get_reverse (gc->ptable, gc->alloc_next);
if (page_state == FS_RMAP_BLOCK_ERASED) {
gc->ptable->set_reverse (gc->ptable, gc->alloc_next,
FS_RMAP_PAGE_GARBAGE);
/* Note that this log is happening before there are any log
* pages. This will fail if the single log buffer overflows.
* This can happen with multiple consecutive bad-powerdowns. */
args[0] = gc->alloc_next;
fs_log_add (&gc->log, FS_LOG_ENTRY_GARBAGE, args);
}
BUMP_PAGE (gc, gc->alloc_next);
}
} else {
/* Advance the allocator until we find something that we expect to be
* erased and really is. */
fs_pm_top_off_log(gc);
while (1) {
if (fs_flash_is_erased (gc->dev, gc->alloc_next))
break;
/* If we're at the start of a block, make sure we find a block that
* should be erased. */
while ((gc->alloc_next & ~gc->ptable->super.block_mask) == 0
&& gc->ptable->get_reverse (gc->ptable, gc->alloc_next)
!= FS_RMAP_BLOCK_ERASED)
{
BUMP_BLOCK (gc, gc->alloc_next);
}
/* Note: we haven't allocated log pages so if too many pages are
* detected as garbage, we'll overflow the log. */
/* Mark it as garbage, then. */
gc->ptable->set_reverse (gc->ptable, gc->alloc_next,
FS_RMAP_PAGE_GARBAGE);
args[0] = gc->alloc_next;
fs_log_add (&gc->log, FS_LOG_ENTRY_GARBAGE, args);
BUMP_PAGE (gc, gc->alloc_next);
}
/* Set the GC state appropriately. */
if ((gc->gc_next & ~gc->ptable->super.block_mask) == 0)
gc->state = FS_PM_HEAD;
else
gc->state = FS_PM_MOVING;
/* If the alloc_next is not at the end of a block, set its "next"
* field to erased. */
gc->ptable->set_reverse (gc->ptable, gc->alloc_next,
FS_RMAP_BLOCK_ERASED);
}
/* Re-compute the free count. */
gc->free_count = 0;
tmp_page = gc->alloc_next;
tmp_stop = gc->gc_next & gc->ptable->super.block_mask;
while (tmp_page != tmp_stop) {
page_state = gc->ptable->get_reverse (gc->ptable, tmp_page);
if (page_state == FS_RMAP_BLOCK_ERASED) {
/* XXX: Layering violation. */
if (FS_ISNOR (&gc->ptable->super)) {
fs_ptable_nor_t npt = (fs_ptable_nor_t) gc->ptable;
gc->free_count += npt->reserved_offset -
(tmp_page & ~gc->ptable->super.block_mask);
} else {
gc->free_count += gc->ptable->super.data.block_size -
(tmp_page & ~gc->ptable->super.block_mask);
}
}
BUMP_BLOCK (gc, tmp_page);
}
/*printf ("Recomputed free count: 0x%x\n", gc->free_count);*/
/* Call the appropriate restart. */
gc->ptable->post_init (gc->ptable);
}
#ifdef FEATURE_EFS_EFS2_ON_NAND
/* Force an initial superblock to be written. */
if (fresh_start && FS_ISNAND (&gc->ptable->super)) {
(void) fs_pm_top_off_log (gc);
nand_flush_superblock (gc);
}
#endif
fs_pm_do_urgent(gc, TRUE);
fs_log_flush (&gc->log);
#ifdef FS_UNIT_TEST
#error code not present
#endif
}
static void fs_pm_register_free_check_cb (fs_pm_t gen_gc,
int (*is_used_cb) (void *data, cluster_id cluster), void *data)
{
fs_pm_flash_t gc = (fs_pm_flash_t) gen_gc;
/* It is erroneous to register multiple callbacks. */
ASSERT (gc->is_used_cb == NULL);
gc->is_used_cb = is_used_cb;
gc->is_used_data = data;
}
void
fs_pm_shutdown_flush (fs_pm_t gen_gc)
{
fs_pm_flash_t gc = (fs_pm_flash_t) gen_gc;
(void) gc;
#ifdef FS_UNIT_TEST
#error code not present
#endif
}
#ifdef FEATURE_EFS_EFS2_ON_NAND
static void
fs_pm_log_visit_nand_node (void *vgc, fs_log_code_t code, uint32 *args)
{
fs_pm_flash_t gc = (fs_pm_flash_t) vgc;
fs_ptable_nand_log_visit (gc->ptable, code, args);
fs_pm_log_visit_node (vgc, code, args);
}
#endif
static void
fs_pm_log_visit_node (void *vgc, fs_log_code_t code, uint32 *args)
{
fs_pm_flash_t gc = (fs_pm_flash_t) vgc;
page_id page;
switch (code) {
case FS_LOG_ENTRY_PAGE_MOVE:
/*printf ("page move: 0x%x\n", args[2]);*/
page = args[2];
if (page < gc->min_page || page >= gc->max_page)
FS_ERR_FATAL ("Inconsistency", 0, 0, 0);
gc->alloc_next = page;
BUMP_PAGE (gc, gc->alloc_next);
break;
case FS_LOG_ENTRY_NEW_DATA:
/*printf ("new data: 0x%x\n", args[1]);*/
page = args[1];
if (page < gc->min_page || page >= gc->max_page)
FS_ERR_FATAL ("Inconsistency", 0, 0, 0);
gc->alloc_next = page;
BUMP_PAGE (gc, gc->alloc_next);
break;
case FS_LOG_ENTRY_GC_MOVE:
/*printf ("gc next: 0x%x\n", args[1]);*/
/*
gc->gc_next = args[1];
BUMP_PAGE (gc, gc->gc_next);
*/
page = args[2];
if (page < gc->min_page || page >= gc->max_page)
FS_ERR_FATAL ("Inconsistency", 0, 0, 0);
gc->alloc_next = page;
BUMP_PAGE (gc, gc->alloc_next);
break;
case FS_LOG_ENTRY_GARBAGE:
page = args[0];
if (page >= gc->min_page && page < gc->max_page) {
gc->ptable->set_reverse (gc->ptable, page, FS_RMAP_PAGE_GARBAGE);
gc->alloc_next = page;
BUMP_PAGE (gc, gc->alloc_next);
}
break;
case FS_LOG_ENTRY_ERASE_FINISH:
page = args[0] << gc->ptable->super.block_shift;
if (page >= gc->min_page && page < gc->max_page) {
gc->gc_next = page;
BUMP_BLOCK(gc, gc->gc_next);
}
break;
case FS_LOG_ENTRY_ERASE_FAIL:
page = args[0] << gc->ptable->super.block_shift;
if (page >= gc->min_page && page < gc->max_page) {
gc->ptable->set_reverse (gc->ptable, page, FS_RMAP_BLOCK_BAD);
}
break;
case FS_LOG_ENTRY_WRITE_FAIL:
page = args[0];
if (page >= gc->min_page && page < gc->max_page) {
gc->ptable->set_reverse (gc->ptable, page, FS_RMAP_BLOCK_BAD);
}
/* Alloc next needs not be advanced, since there isn't yet a log
* flush, and another alloc will always be tried next. */
break;
case FS_LOG_ENTRY_LOG_FLUSH:
break;
case FS_LOG_ENTRY_UPPER_DATA:
case FS_LOG_ENTRY_XACT_START:
case FS_LOG_ENTRY_XACT_END:
case FS_LOG_ENTRY_PTABLE_MOVE:
case FS_LOG_ENTRY_GC_DEALLOC:
case FS_LOG_ENTRY_KEYRANGE:
case FS_LOG_ENTRY_LOG_ALLOC:
break;
default:
FS_ERR_FATAL ("Unhandled log entry: %02x", code, 0, 0);
}
}
/* Determine the "urgency" of this collect. Numbers over 80 indicate that
* this GC is required.
* XXX: Implement the GC scheduler, and make sense of these values. */
static int
fs_pm_urgency (fs_pm_flash_t gc)
{
if (gc->free_count <
2 * gc->ptable->super.data.block_size + 2 + FS_MAX_TRANSACTION_SIZE)
return 80;
else
return 0;
}
/* static void cleanup_superblock_logs (fs_pm_t gc); */
/* Allocate a single page. Assumes that the GC has been run enough, that
* there are pages available. */
static page_id
fs_pm_alloc (fs_pm_flash_t gc)
{
page_id state;
page_id target;
ASSERT (gc->free_count > 0);
/* Loop until we return a result. */
while (1) {
state = gc->ptable->get_reverse (gc->ptable, gc->alloc_next);
switch (state) {
case FS_RMAP_BLOCK_ERASED:
if (((gc->alloc_next & (~gc->ptable->super.block_mask)) == 0
&& FS_ISNOR (&gc->ptable->super)))
{
gc->ptable->mark_alloc (gc->ptable, gc->alloc_next);
gc->ptable->set_reverse (gc->ptable, gc->alloc_next,
FS_RMAP_PAGE_SUPER);
gc->next_super_page = gc->alloc_next;
gc->free_count--;
BUMP_PAGE (gc, gc->alloc_next);
} else {
gc->ptable->mark_alloc (gc->ptable, gc->alloc_next);
target = gc->alloc_next;
BUMP_PAGE (gc, gc->alloc_next);
gc->free_count--;
#ifdef FS_UNIT_TEST
#error code not present
#endif
return target;
}
break;
case FS_RMAP_PAGE_SUPER:
case FS_RMAP_PAGE_RESERVED:
BUMP_PAGE (gc, gc->alloc_next);
break;
default:
BUMP_BLOCK(gc, gc->alloc_next);
}
}
}
/* Should be called right after alloc, if the write fails. Marks the given
* page with a 'BAD' marker, and advances the allocator to the following
* block. */
static void
fs_pm_mark_bad (fs_pm_flash_t gc, page_id page)
{
page_id tmp_page;
uint32 tmp;
uint32 args[3];
tmp_page = page;
BUMP_PAGE (gc, tmp_page);
ASSERT (tmp_page == gc->alloc_next);
/* A write failure on NOR either indicates end-of-life device wear, or,
* more likely, a hardware problem. Seeing this error during development
* is a strong indicator of either a hardware problem, or a device driver
* problem. */
if (FS_ISNOR (&gc->ptable->super))
FS_ERR_FATAL ("NOR write failure", 0, 0, 0);
/* Mark the page as bad. The garbage collector will detect this, and
* after copying valid data out of the block, mark the entire block as
* bad. */
args[0] = page;
fs_log_add (&gc->log, FS_LOG_ENTRY_WRITE_FAIL, args);
gc->ptable->set_reverse (gc->ptable, page, FS_RMAP_BLOCK_BAD);
/* If the first page in the block went bad, physically mark the block
* bad. */
if ((page & gc->ptable->super.block_mask) == 0) {
(void) fs_flash_mark_block_bad (gc->dev,
page >> gc->ptable->super.block_shift);
}
/* Advance alloc_next to the next block, and adjust free_count
* appropriately. Free_count is not adjusted if the last page in a block
* went bad, since no pages of free space are lost. */
tmp = gc->ptable->super.data.block_size -
(gc->alloc_next & ~gc->ptable->super.block_mask);
if (tmp < gc->ptable->super.data.block_size) {
gc->free_count -= tmp;
BUMP_BLOCK (gc, gc->alloc_next);
}
}
/* Count the number of pages of garbage in the block at the given starting
* address. */
static int
fs_pm_count_garbage (fs_pm_flash_t gc, page_id base)
{
page_id limit = base + gc->ptable->super.data.block_size;
int count = 0;
for (; base < limit; base++) {
switch (gc->ptable->get_reverse (gc->ptable, base)) {
case FS_RMAP_PAGE_GARBAGE:
case FS_RMAP_PAGE_LOG:
case FS_RMAP_BLOCK_ERASED:
case FS_RMAP_PAGE_SUPER:
count++;
break;
default:
break;
}
}
return count;
}
/* Perform a single unit of garbage collection. */
static void
fs_pm_single (fs_pm_flash_t gc, boolean allow_erase_pending)
{
page_id pstate;
page_id dest;
int count;
int result;
int moved;
uint32 args[3];
while (1) {
switch (gc->state) {
case FS_PM_HEAD:
/* The head state comes in only when the page is at the beginning of
* a block. */
ASSERT ((gc->gc_next & (~gc->ptable->super.block_mask)) == 0);
pstate = gc->ptable->get_reverse (gc->ptable, gc->gc_next);
switch (pstate) {
case FS_RMAP_BLOCK_UNKNOWN:
/* Check if this block is good. */
if (fs_flash_bad_block_check (gc->dev,
gc->gc_next >> gc->ptable->super.block_shift)) {
gc->ptable->set_reverse (gc->ptable, gc->gc_next,
FS_RMAP_BLOCK_BAD);
BUMP_BLOCK (gc, gc->gc_next);
} else {
gc->state = FS_PM_ERASE;
return;
}
break;
case FS_RMAP_BLOCK_BAD:
BUMP_BLOCK (gc, gc->gc_next);
break;
default:
/* Count up the garbage in this page. */
count = fs_pm_count_garbage (gc, gc->gc_next);
if (count == 0)
BUMP_BLOCK (gc, gc->gc_next);
else
gc->state = FS_PM_MOVING;
break;
}
break;
case FS_PM_MOVING:
pstate = gc->ptable->get_reverse (gc->ptable, gc->gc_next);
moved = 0;
switch (pstate) {
case FS_RMAP_PAGE_GARBAGE:
case FS_RMAP_PAGE_RESERVED:
case FS_RMAP_PAGE_SUPER:
case FS_RMAP_PAGE_LOG:
/* Only valid for NOR. */
case FS_RMAP_BLOCK_ERASED:
break;
case FS_RMAP_BLOCK_BAD:
/* Physically mark the block as bad. */
(void) fs_flash_mark_block_bad
(gc->dev, gc->gc_next >> gc->ptable->super.block_shift);
/* A bad block marked in the middle of a block means that we
* detected this failure while writing a page. At this point,
* we've already copied all good data out of the block, and can
* just mark the entire block as 'bad'. It is important that
* we do not erase it when we are done. */
gc->ptable->set_reverse (gc->ptable,
gc->gc_next & gc->ptable->super.block_mask,
FS_RMAP_BLOCK_BAD);
/* Log the change. */
{ int block = gc->gc_next >> gc->ptable->super.block_shift;
args[0] = block;
fs_log_add (&gc->log, FS_LOG_ENTRY_ERASE_FAIL, args);
fs_log_flush (&gc->log);
}
BUMP_BLOCK (gc, gc->gc_next);
gc->state = FS_PM_HEAD;
/* Consider this a unit of work. If anything, it makes getting
* out of this state easier. */
return;
default:
if (!FS_RMAP_IS_SPECIAL (pstate)) {
if (gc->is_used_cb == NULL ||
gc->is_used_cb (gc->is_used_data, pstate))
{
dest = fs_pm_alloc (gc);
result = fs_flash_read_page (gc->dev, gc->gc_next,
gc->realloc_buffer,
FS_FOP_DATA_GC_MOVE);
if (result != FS_DEVICE_OK) {
FS_ERR_FATAL ("Error reading from device", 0, 0, 0);
}
/* printf ("gc write: %d\n", dest); */
result = fs_flash_write_page (gc->dev, dest,
gc->realloc_buffer, FS_FOP_DATA_GC_MOVE);
while (result != FS_DEVICE_OK) {
/* XXX: Count up errors to handle them. */
/* Write error: Mark rest of block bad, and try allocation
* at the next available block. */
fs_pm_mark_bad (gc, dest);
/* Get another page, and try writing it. */
dest = fs_pm_alloc (gc);
result = fs_flash_write_page (gc->dev, dest,
gc->realloc_buffer, FS_FOP_DATA_GC_RECOVERY);
/* Loop back around to check for error. */
}
/* Log the change. */
args[0] = pstate;
args[1] = gc->gc_next;
args[2] = dest;
fs_log_add (&gc->log, FS_LOG_ENTRY_GC_MOVE, args);
cleanup_logs (gc);
gc->ptable->set_forward (gc->ptable, pstate, dest);
gc->ptable->set_reverse (gc->ptable, gc->gc_next,
FS_RMAP_PAGE_GARBAGE);
gc->ptable->set_reverse (gc->ptable, dest, pstate);
moved = 1;
} else {
/* The page exists, but is no longer in use. Deallocate
* the page, instead of moving it. */
args[0] = pstate;
args[1] = gc->gc_next;
fs_log_add (&gc->log, FS_LOG_ENTRY_GC_DEALLOC, args);
gc->ptable->set_forward (gc->ptable, pstate, INVALID_PAGE_ID);
gc->ptable->set_reverse (gc->ptable, gc->gc_next,
FS_RMAP_PAGE_GARBAGE);
fs_flash_dealloc_counter++;
}
} else {
FS_ERR_FATAL ("GC encountered unknown page type %08x",
pstate, 0, 0);
}
}
if (((gc->gc_next + 1) & (~gc->ptable->super.block_mask))
== 0) {
gc->state = FS_PM_ERASE;
} else {
BUMP_PAGE (gc, gc->gc_next);
}
/* Return even if only one page was considered for move on
NOR. Return if one page moved on NAND. */
if(moved || FS_ISNOR (&gc->ptable->super))
return;
break;
case FS_PM_ERASE:
/* Flush the log since we could be erasing data that is only described
* in that log. */
fs_log_flush (&gc->log);
cleanup_logs (gc);
if (FS_ISNAND (&gc->ptable->super)) {
int block = gc->gc_next >> gc->ptable->super.block_shift;
args[0] = block;
result = fs_flash_erase_block (gc->dev, block, FS_FOP_USER);
if (result != FS_DEVICE_OK) {
(void) fs_flash_mark_block_bad (gc->dev, block);
fs_log_add (&gc->log, FS_LOG_ENTRY_ERASE_FAIL, args);
fs_log_flush (&gc->log);
gc->ptable->set_reverse (gc->ptable,
gc->gc_next & gc->ptable->super.block_mask,
FS_RMAP_BLOCK_BAD);
gc->state = FS_PM_HEAD;
BUMP_BLOCK (gc, gc->gc_next);
break;
} else {
fs_log_add (&gc->log, FS_LOG_ENTRY_ERASE_FINISH, args);
gc->state = FS_PM_HEAD;
count = gc->ptable->erase (gc->ptable,
gc->gc_next >> gc->ptable->super.block_shift);
gc->free_count += count;
gc->trans_start_free_count += count;
BUMP_BLOCK (gc, gc->gc_next);
}
}
else if (allow_erase_pending == FALSE) {
if(gc->erase_state == FS_PM_ERASE_SUSPEND)
{
result = fs_flash_resume_erase(gc->dev);
while (result == FS_DEVICE_BUSY)
{
fs_util_wait(20);
result = fs_flash_erase_status(gc->dev);
}
gc->erase_state = FS_PM_ERASE_IDLE;
} else {
int block = (gc->gc_next >> gc->ptable->super.block_shift);
gc->ptable->begin_erase (gc->ptable, block);
result = fs_flash_erase_block (gc->dev, block, FS_FOP_USER);
}
if (result != FS_DEVICE_DONE) {
FS_ERR_FATAL ("Unable to erase block", 0, 0, 0);
}
/* Log the erase as finished. */
args[0] = gc->gc_next >> gc->ptable->super.block_shift;
fs_log_add (&gc->log, FS_LOG_ENTRY_ERASE_FINISH, args);
gc->state = FS_PM_HEAD;
count = gc->ptable->erase (gc->ptable,
gc->gc_next >> gc->ptable->super.block_shift);
gc->free_count += count;
gc->trans_start_free_count += count;
BUMP_BLOCK (gc, gc->gc_next);
} else {
gc->gc_current_erase_block = gc->gc_next >>
gc->ptable->super.block_shift;
gc->ptable->begin_erase (gc->ptable, gc->gc_current_erase_block);
result = fs_flash_begin_erase_block (gc->dev,
gc->gc_current_erase_block);
if (result != FS_DEVICE_DONE) {
FS_ERR_FATAL ("Unable to erase block", 0, 0, 0);
}
gc->erase_state = FS_PM_ERASE_BUSY;
fs_time_ms(gc->erase_start);
gc->erase_countdown = gc->erase_time;
}
/* A unit of work is done, so return. */
return;
default:
FS_ERR_FATAL ("Internal state error", 0, 0, 0);
}
}
}
/* Returns number of pages added to the log */
static int
fs_pm_top_off_log (fs_pm_flash_t gc)
{
int did_something = 0;
page_id tmp;
while (FS_ISNAND (&gc->ptable->super) &&
fs_log_page_count (&gc->log) < 8)
{
tmp = fs_logr_alloc (&gc->logr);
if ((tmp & (~gc->ptable->super.block_mask)) == 0) {
/* Use the first one of each as a superblock. */
gc->next_super_page = tmp;
tmp = fs_logr_alloc (&gc->logr);
}
fs_log_add_page (&gc->log, tmp);
did_something++;
}
if (FS_ISNOR (&gc->ptable->super) &&
fs_log_page_count (&gc->log) < 8)
{
uint32 args[3];
tmp = fs_pm_alloc (gc);
/* XXX: This new log entry breaks compatibility with previous versions,
* so can be disabled, but is important for handling all bad-powerdown
* cases. */
args[0] = tmp;
fs_log_add (&gc->log, FS_LOG_ENTRY_LOG_ALLOC, args);
gc->ptable->set_reverse (gc->ptable, tmp, FS_RMAP_PAGE_LOG);
fs_log_add_page (&gc->log, tmp);
did_something++;
}
return did_something;
}
/* Perform as much garbage collection as is necessary, in light of urgency. */
static void
fs_pm_do_urgent (fs_pm_flash_t gc, boolean allow_gc)
{
int did_something;
int new_log_pages = 0;
int log_pages = 0;
do {
did_something = 0;
if((log_pages = fs_pm_top_off_log(gc)) > 0)
{
new_log_pages += log_pages;
did_something=1;
}
if( allow_gc && (fs_pm_urgency (gc) >= 80) && (!gc->inside_xact) )
{
fs_pm_single (gc, FALSE);
did_something = 1;
}
#ifdef FEATURE_EFS_EFS2_ON_NAND
if (FS_ISNAND (&gc->ptable->super) /* &&
fs_log_written_count (&gc->log) > 20 */)
{
did_something |= nand_log_check (gc);
}
#endif
} while (did_something);
/* Remeber where the free count is to count how many pages were consumed
* during the efs operation */
gc->trans_start_free_count = gc->free_count + new_log_pages;
}
void
fs_pm_page_write (fs_pm_t gen_gc, cluster_id cluster, void *buffer)
{
fs_pm_flash_t gc = (fs_pm_flash_t) gen_gc;
page_id old_index;
page_id new_index;
int result;
uint32 args[32];
int error_count = 0;
old_index = gc->ptable->get_forward (gc->ptable, cluster);
new_index = fs_pm_alloc (gc);
/* printf ("Write page: %d\n", new_index); */
result = fs_flash_write_page (gc->dev, new_index, buffer,
FS_FOP_USER);
while (result != FS_DEVICE_OK) {
error_count++;
if (error_count > 2)
FS_ERR_FATAL ("Too many write errors, most likely hardware problem",
0, 0, 0);
/* Write error. Mark the rest of this block as bad, and try an
* allocation at the next available block. */
fs_pm_mark_bad (gc, new_index);
/* XXX: If transactions are changed so that they can split across log
* nodes, add a log_flush here. Adding it now will split any
* transaction here. The advantage to the flush is that a powerdown
* shortly after a bad page write is more likely to be logged.
* Otherwise, we may not catch the write failure until next time. */
/* Get another page, and try writing it. */
new_index = fs_pm_alloc (gc);
result = fs_flash_write_page (gc->dev, new_index, buffer,
FS_FOP_USER_RECOVERY);
/* Loop back around, and check the result again. */
}
/* Add logs as appropriate. */
if (old_index == INVALID_PAGE_ID) {
args[0] = cluster;
args[1] = new_index;
fs_log_add (&gc->log, FS_LOG_ENTRY_NEW_DATA, args);
} else {
args[0] = cluster;
args[1] = old_index;
args[2] = new_index;
fs_log_add (&gc->log, FS_LOG_ENTRY_PAGE_MOVE, args);
}
gc->ptable->set_forward (gc->ptable, cluster, new_index);
if (old_index != INVALID_PAGE_ID)
gc->ptable->set_reverse (gc->ptable, old_index, FS_RMAP_PAGE_GARBAGE);
gc->ptable->set_reverse (gc->ptable, new_index, cluster);
}
void
fs_pm_page_read (fs_pm_t gen_gc, cluster_id cluster, void *buffer)
{
fs_pm_flash_t gc = (fs_pm_flash_t) gen_gc;
int result;
int was_suspended_here;
page_id page;
/* If we are suspending an erase to perform this read, note it
* so that we can resume later. */
if (gc->erase_state == FS_PM_ERASE_BUSY) {
fs_pm_suspend_erase (gc, FALSE);
was_suspended_here = 1;
} else {
was_suspended_here = 0;
}
page = gc->ptable->get_forward (gc->ptable, cluster);
if (page == INVALID_PAGE_ID) {
memset (buffer, 0xFF, gc->ptable->super.data.page_size);
} else {
result = fs_flash_read_page (gc->dev, page, buffer, FS_FOP_USER);
if (result != FS_DEVICE_OK) {
FS_ERR_FATAL ("Unable to read data page", 0, 0, 0);
}
}
/* Resume suspended erase (NOR), but only if we did the suspend. */
if (was_suspended_here && (gc->erase_state == FS_PM_ERASE_SUSPEND))
fs_pm_inc_gc_erase (gc);
}
void
fs_pm_start_transaction (fs_pm_t gen_gc)
{
fs_pm_flash_t gc = (fs_pm_flash_t) gen_gc;
if (FS_ISNOR (&gc->ptable->super)) {
fs_pm_inc_gc_start_op(gc);
fs_pm_do_urgent (gc, TRUE);
} else {
fs_pm_do_urgent (gc, TRUE);
}
fs_log_flush (&gc->log);
fs_log_add (&gc->log, FS_LOG_ENTRY_XACT_START, NULL);
gc->inside_xact = 1;
}
#ifdef FEATURE_EFS_EFS2_ON_NAND
/* Flush out an initial superblock. We haven't written any log pages, yet,
* so we need to figure out where the first one is going to be written, and
* point the superblock there. This is only called during fresh_start. */
static void
nand_flush_superblock (fs_pm_flash_t gc)
{
ASSERT (!gc->inside_xact);
ASSERT (gc->next_super_page != INVALID_PAGE_ID);
gc->ptable->super.data.log_head = fs_log_peek_page (&gc->log);
gc->ptable->super.data.alloc_next[0] = gc->alloc_next;
gc->ptable->super.data.gc_next[0] = gc->gc_next;
/* Make sure other values get updated as well. */
gc->ptable->super_update (gc->ptable);
fs_super_update (&gc->ptable->super, gc->next_super_page);
gc->next_super_page = INVALID_PAGE_ID;
}
static int
nand_log_check (fs_pm_flash_t gc)
{
int did = gc->ptable->do_gc (gc->ptable);
if (!gc->inside_xact &&
gc->next_super_page != INVALID_PAGE_ID &&
fs_log_written_count (&gc->log) > 0)
{
fs_log_flush (&gc->log);
gc->ptable->super.data.log_head = fs_log_peek_written (&gc->log);
gc->ptable->super.data.alloc_next[0] = gc->alloc_next;
gc->ptable->super.data.gc_next[0] = gc->gc_next;
/* Make sure other values get updated as well. */
gc->ptable->super_update (gc->ptable);
/* On NAND, we can't write a superblock if the log has already written
* past this superblock. This only happens after an untimely powerdown
* when the logs were at the end of a block. Assume this isn't a
* repeated occurence. */
if (gc->log.last_written_log == INVALID_PAGE_ID ||
((gc->next_super_page & gc->ptable->super.block_mask) !=
(gc->log.last_written_log & gc->ptable->super.block_mask)))
{
fs_super_update (&gc->ptable->super, gc->next_super_page);
}
gc->next_super_page = INVALID_PAGE_ID;
}
return did;
}
#endif
/* NOR specific cleanup of the logs. */
static void
cleanup_logs (fs_pm_flash_t gc)
{
page_id page;
page_id buffer;
int flushed_one = 0;
int result;
/* The log needs to stay around in NAND until it has been dealt with by
* the NAND ptable gc code. */
if (FS_ISNAND (&gc->ptable->super)) {
return;
} else {
while ((page = fs_log_get_written (&gc->log)) != INVALID_PAGE_ID) {
gc->ptable->flush (gc->ptable);
/* Now it is safe to change the state of that log page. */
if (FS_ISNOR (&gc->ptable->super)) {
buffer = 0xFFFFFF00;
result = fs_flash_partial_write (gc->dev, page, &buffer, 4, 4);
if (result != 0)
FS_ERR_FATAL ("Unable to write log state change", 0, 0, 0);
}
flushed_one = 1;
}
}
/* Check to see if there is an appropriate superblock to write out. */
if (flushed_one && !gc->inside_xact &&
gc->next_super_page != INVALID_PAGE_ID)
{
gc->ptable->super.data.log_head = fs_log_peek_page (&gc->log);
gc->ptable->super.data.alloc_next[0] = gc->alloc_next;
gc->ptable->super.data.gc_next[0] = gc->gc_next;
fs_super_update (&gc->ptable->super, gc->next_super_page);
gc->next_super_page = INVALID_PAGE_ID;
}
}
#ifdef FS_UNIT_TEST
#error code not present
#endif
void
fs_pm_end_transaction (fs_pm_t gen_gc)
{
fs_pm_flash_t gc = (fs_pm_flash_t) gen_gc;
fs_log_add (&gc->log, FS_LOG_ENTRY_XACT_END, NULL);
fs_log_flush (&gc->log);
gc->inside_xact = 0;
if (FS_ISNOR (&gc->ptable->super)) {
fs_pm_inc_gc_end_op(gc);
fs_log_flush (&gc->log);
}
cleanup_logs (gc);
/* Not strictly necessary. */
if (FS_ISNAND (&gc->ptable->super)) {
fs_log_flush (&gc->log);
}
if (FS_ISNOR (&gc->ptable->super)) {
fs_pm_inc_gc_erase(gc);
}
#ifdef FS_UNIT_TEST
#error code not present
#endif
}
void
fs_pm_store_info (fs_pm_t gen_gc, unsigned offset, uint32 data)
{
fs_pm_flash_t gc = (fs_pm_flash_t) gen_gc;
uint32 args[2];
args[0] = offset;
args[1] = data;
fs_log_add (&gc->log, FS_LOG_ENTRY_UPPER_DATA, args);
gc->ptable->super.data.upper_data[offset] = data;
}
uint32
fs_pm_get_info (fs_pm_t gen_gc, unsigned offset)
{
fs_pm_flash_t gc = (fs_pm_flash_t) gen_gc;
return gc->ptable->super.data.upper_data[offset];
}
/* Compute the space limit for the lower layer code. This allows enough
* room for the garbage collector to run, a bad powerdown during a
* transaction, and to recover. This number is fairly conservative, but
* gives reasonable performance. The number returned is a count of pages
* available. */
uint32
fs_pm_space_limit (fs_pm_t gen_gc)
{
fs_pm_flash_t gc = (fs_pm_flash_t) gen_gc;
uint32 block_count = fs_flash_block_count (gc->dev);
uint32 block_size = fs_flash_block_size (gc->dev);
uint32 size;
if (FS_ISNOR (&gc->ptable->super))
{
size = block_count * block_size;
size -= 2 * block_size;
size -= FS_MAX_TRANSACTION_SIZE;
size = (size * 217) >> 8; /* Roughly 85% */
}
else
{
/* On NAND devices, we set the usable space to 85% of the data region.
* Operations slow down a lot past this point.
*/
size = gc->ptable->super.data.u.nand.regions[0] * block_size;
size = ((size * 243) >> 8) - (5 * block_size);
}
return size;
}
static void fs_pm_finish_erase( fs_pm_flash_t gc )
{
uint32 args[3]; /* Args for log */
int free_pages;
gc->erase_state = FS_PM_ERASE_IDLE;
/* Finish erase in log */
args[0] = gc->gc_current_erase_block;
fs_log_add (&gc->log, FS_LOG_ENTRY_ERASE_FINISH, args);
/* Update gc->free_count */
free_pages = gc->ptable->erase (gc->ptable, gc->gc_current_erase_block);
gc->free_count += free_pages;
/* Correct error in start free count */
gc->trans_start_free_count += free_pages;
/* Advance gc->gc_next to start garbage collecting next block */
BUMP_BLOCK (gc, gc->gc_next);
gc->state = FS_PM_HEAD;
}
/* Converting elapsed time into work is assumes that a block of work happens
* in gc->erase_time. One block of pages was erased, and a page erase is
* equivalent to gc->erase_factor/10 worth of page moves. A page move is the
* basic unit of work */
#define TIME_TO_WORK(gc,t,bs) ((t)*(bs)*((gc)->erase_factor)/ \
(10*((gc)->erase_time)))
/* Compute time since last erase start/resume */
static int
fs_pm_erase_time_elapsed (fs_pm_flash_t gc)
{
qword now;
qword elapsed;
fs_time_ms(now);
qw_sub(elapsed, now, gc->erase_start);
if(qw_hi(elapsed) != 0)
{
/* If erase time elapsed too large or negative, return 0; */
return 0;
}
return qw_lo(elapsed);
}
/* fs_pm_suspend_erase( gc, do_work)
do_work (boolean) : indicates that erase work is allowed.
When do_work is false, this function suspends the active
erase and updates unfinished work
When do_work is true, this function will wait until either
the erase is done, or unfinished work is done.
*/
static void
fs_pm_suspend_erase (fs_pm_flash_t gc, boolean do_work)
{
uint32 block_size = fs_flash_block_size (gc->dev);
boolean finish_erase = FALSE;
int status;
if( gc->erase_state == FS_PM_ERASE_SUSPEND && do_work &&
(gc->unfinished_work > 0))
{
/* An erase was previously suspended, but work needs to be done */
/* Restart the erase */
fs_pm_inc_gc_erase (gc);
/* State is now either ERASE_BUSY or ERASE_DONE. Fall through
* And do the necessary work. */
}
if( gc->erase_state == FS_PM_ERASE_BUSY)
{
int work = gc->unfinished_work;
/* An erase was running, check erase status */
if(fs_flash_erase_status (gc->dev) == FS_DEVICE_DONE)
{
/* Erase complete */
finish_erase = TRUE;
/* Account for work remaining */
if(gc->erase_countdown > 0) {
gc->unfinished_work -= TIME_TO_WORK(gc, gc->erase_countdown,
block_size);
}
} else {
/* Remove amount of time consumed so far from unfinished work. */
work -= TIME_TO_WORK(gc, fs_pm_erase_time_elapsed(gc), block_size);
/* Loop wait for remaining erase work to complete */
if(do_work && (work > 0))
{
do
{
fs_util_wait(FS_NOR_ERASE_INCREMENT);
status = fs_flash_erase_status(gc->dev);
work -= TIME_TO_WORK(gc, FS_NOR_ERASE_INCREMENT, block_size);
} while (status != FS_DEVICE_DONE && work > 0);
if(status == FS_DEVICE_DONE)
{
/* Erase finished in the last 10ms */
finish_erase = TRUE;
/* Account for work remaining */
if(gc->erase_countdown > 0) {
gc->unfinished_work -= TIME_TO_WORK(gc, gc->erase_countdown,
block_size);
}
}
}
/* The work is done, but the erase is still going */
if(! finish_erase )
{
int elapsed_time;
/* Erase is not done yet, suspend erase */
status = fs_flash_suspend_erase( gc->dev );
elapsed_time = fs_pm_erase_time_elapsed(gc);
if (status == FS_DEVICE_FAIL) {
FS_ERR_FATAL ("Unable to suspend erase", 0, 0, 0);
} else if(status == FS_DEVICE_BUSY) {
/* Erase suspended success */
gc->erase_state = FS_PM_ERASE_SUSPEND;
/* Remove amount of time consumed so far from unfinished work. */
if(elapsed_time > gc->erase_countdown) {
/* Erase finished early, only remove expected erase time from
unfinished work. */
if(gc->erase_countdown > 0) {
gc->unfinished_work -= TIME_TO_WORK(gc, gc->erase_countdown,
block_size);
}
} else {
/* Erase is still going, deduct elapsed time from work */
gc->unfinished_work -= TIME_TO_WORK(gc, elapsed_time, block_size);
}
/* Reduce countdown by elapsed time */
gc->erase_countdown -= elapsed_time;
}
else if( status == FS_DEVICE_DONE )
{
/* Erase just finished during the suspend */
finish_erase = 1;
if(gc->erase_countdown > 0) {
gc->unfinished_work -= TIME_TO_WORK(gc, gc->erase_countdown,
block_size);
}
}
}
}
if(finish_erase)
{
/* An erase finished, add to gc->free_count, and log it */
fs_pm_finish_erase( gc );
}
}
}
static void
fs_pm_do_page_moves (fs_pm_flash_t gc)
{
int i = 0;
while((gc->unfinished_work > 0) &&
(gc->state == FS_PM_MOVING || gc->state == FS_PM_HEAD))
{
/* Move a page */
fs_pm_single(gc, TRUE);
gc->unfinished_work--;
if(i++ > 40)
{
fs_pm_top_off_log(gc);
i = 0;
}
}
}
static void
fs_pm_inc_gc_do_work (fs_pm_flash_t gc)
{
boolean early_exit = 0;
while((gc->unfinished_work > 0) && !early_exit)
{
switch(gc->erase_state)
{
case FS_PM_ERASE_IDLE:
if(gc->state == FS_PM_ERASE)
{
/* Erase is the next stage of garbage collection */
if(gc->unfinished_work <
((int) fs_flash_block_size (gc->dev) * 3/2) *
gc->erase_factor / 10)
{
/* This is not a full block erase, let it run after the
operation completes */
early_exit = 1;
} else {
/* 1.5 blocks need to be done, do a blocking erase */
fs_pm_single(gc, FALSE); /* Erase and wait for it */
gc->unfinished_work -= fs_flash_block_size (gc->dev) *
gc->erase_factor / 10;
}
} else {
/* Do page moves */
fs_pm_do_page_moves(gc);
fs_pm_top_off_log(gc);
}
break;
case FS_PM_ERASE_SUSPEND:
if(gc->unfinished_work > 0)
{
/* Finish the unfinished work */
fs_pm_suspend_erase(gc, TRUE);
}
break;
case FS_PM_ERASE_BUSY:
fs_pm_suspend_erase(gc, TRUE);
break;
}
}
/* Even if no work is being done, be sure to suspend the erase */
if(gc->erase_state == FS_PM_ERASE_BUSY)
{
fs_pm_suspend_erase(gc, FALSE);
}
}
/* Perform transaction start tasks for the incremental garbage collector */
static void
fs_pm_inc_gc_start_op (fs_pm_flash_t gc)
{
fs_pm_inc_gc_do_work(gc);
gc->trans_start_free_count = gc->free_count;
fs_pm_inc_gc_do_work(gc);
}
/* Tuning values for how much garbage to collect based on the erased space.
* a value 100, means collect garbage equal to pages used. A value 0 means
* collect no garbage. The index of inc_gc_tune[] is the flash full ratio
* to the nearest 10th. tune[0] is the value for flash < 10% full.
* tune[2] is the tune value for flash < 20% full. tune[11] is for
* 110% full (when the reserved space for gc is being used).
*/
int inc_gc_tune[11] = {0,0,0,0,0,50,65,80,90,115,180};
/* Perform transaction end tasks for the incremental garbage collector */
static void
fs_pm_inc_gc_end_op (fs_pm_flash_t gc)
{
int block_size;
int garbage_ratio; /* average garbage present in 1000 pages */
int pages_used; /* pages used from the erased region this operation */
int fs_size; /* Size of the whole filesystem in pages*/
int data_size; /* Size of the data in pages */
int erase_size; /* Size of the erased region in pages */
int garbage_size; /* Size of the garbage in pages */
int pages_to_clean; /* Number of pages to clean up */
int tune; /* A tuning value from inc_gc_tune[] */
/* Compute free space used by this operation */
pages_used = gc->trans_start_free_count - gc->free_count;
if(pages_used < 0)
{
FS_ERR_FATAL("pages consumed should be positive, %d\n", pages_used,0,0);
}
/* Compute the ratio of garbage to data in the non-erased pages */
/* since those pages will be garbage collected. Remove the reserved */
/* 2 blocks and 20 pages that is needed for urgent garbage collection */
block_size = fs_flash_block_size(gc->dev);
fs_size = (fs_flash_block_count(gc->dev)-2) * block_size - 20;
erase_size = gc->free_count - 2*block_size - 20;
tune = 10 * (fs_size - erase_size) / fs_size;
if(tune < 0) tune = 0;
else if(tune > 10) tune = 10;
/* Now look up the tuning value */
tune = inc_gc_tune[tune];
if(erase_size < 0) {
/* free space is urgently low */
/* increase fs_size enough to keep erase_size non-negative */
fs_size -= erase_size;
erase_size = 0;
}
/* Only garbage collect if the tuning factor is non-zero */
if(tune > 0)
{
data_size = gc->ptable->super.data.upper_data[FS_FIELD_NUM_ALLOC];
garbage_size = fs_size - erase_size - data_size;
garbage_ratio = 1000 * garbage_size / (fs_size - erase_size);
/* Compute the amount of garbage to clean up
* tune is fixed point *100, and garbage_ratio is *1000 */
pages_to_clean = tune * pages_used * 10 / garbage_ratio;
gc->unfinished_work += pages_to_clean * (10 + gc->erase_factor)/10;
/*printf("GC %d pages, op %d, data %d work (%d,%d) ratio %.3f alloc 0x%x"
" next 0x%x\n",
pages_to_clean, pages_used, data_size, work,gc->unfinished_work,
garbage_ratio/1000.0, gc->alloc_next, gc->gc_next);*/
fs_pm_do_page_moves(gc);
/* Any erase work will be done between operations, starting at
fs_pm_inc_gc_erase() */
}
}
static void
fs_pm_inc_gc_erase (fs_pm_flash_t gc)
{
/* Resume a suspended erase */
if( gc->erase_state == FS_PM_ERASE_SUSPEND)
{
/* Resume erase */
if(fs_flash_resume_erase (gc->dev) == FS_DEVICE_FAIL)
{
FS_ERR("resume erase failed",0,0,0);
/* Ok, try a new erase if possible */
gc->erase_state = FS_PM_ERASE_IDLE;
} else {
fs_time_ms(gc->erase_start);
gc->erase_state = FS_PM_ERASE_BUSY;
/* printf("resume erase at %d, left %d\n", qw_lo(gc->erase_start),
gc->erase_countdown);*/
}
}
/* If the garbage collector is about to start an erase, then do it */
if(gc->state == FS_PM_ERASE && gc->erase_state == FS_PM_ERASE_IDLE)
{
/* Start the erase */
fs_pm_single(gc, TRUE);
fs_time_ms(gc->erase_start);
gc->erase_countdown = gc->erase_time;
gc->erase_state = FS_PM_ERASE_BUSY;
/* printf("start erase at %d left %d\n", qw_lo(gc->erase_start),
gc->erase_countdown);*/
}
}
#ifdef FS_UNIT_TEST
#error code not present
#endif
static void
fs_pm_init_ops (struct fs_pm_ops *ops)
{
ops->page_write = fs_pm_page_write;
ops->page_read = fs_pm_page_read;
ops->store_info = fs_pm_store_info;
ops->get_info = fs_pm_get_info;
ops->start_transaction = fs_pm_start_transaction;
ops->end_transaction = fs_pm_end_transaction;
ops->space_limit = fs_pm_space_limit;
ops->register_free_check_cb = fs_pm_register_free_check_cb;
}