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; 
}