www.pudn.com > efs.rar > fs_pm_ptable_nand.c


/********************************************************************** 
 * fs_pm_ptable_nand.c 
 * 
 * Copyright (C) 2002, 2003, 2004, 2005, 2006, Qualcomm, Inc. 
 * Page table management for NAND flash. 
 */ 
 
/*=========================================================================== 
 
                        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_ptable_nand.c#8 $ $DateTime: 2006/09/18 15:07:36 $ $Author: davidb $ 
 
when          who     what, where, why 
--------      ---     ------------------------------------------------------ 
2006-09-11    dlb     Use flash driver wrappers. 
2006-06-26     yg     Memory reduction effort 
2005-12-14    dlb     Add midpoint startup callback. 
2005-10-30    sh      Lint cleanup. 
2005-05-26    sd      Compilation fixes for L4. 
2005-04-26    dlb     Add 2K page support. 
2005-01-04    dlb     Update copyright line. 
2004-12-30    dlb     Remove excess infiltration of factory image code. 
2004-10-15    dlb     Update copyright header. 
2004-10-07    dlb     Whitespace cleanup. 
2003-11-13    dlb     Fix journal overflow. 
2003-08-11    dlb     Cache ptable pages. 
2003-07-24    dlb     Log and process log flushes. 
2003-06-17    bgc     Handled new FS_LOG_ENTRY_GARBAGE. 
2003-06-17    adm     Featurized factory start code. 
2003-06-17    dlb     Fixed erase failure handling code. 
2003-06-12    adm     Add support for factory start. 
2003-05-13    bgc     Added update of reverse_ptable on log iteration. 
2003-05-07    dlb     Fix bug with keyrange log not being grouped. 
2003-04-25     gr     Split off NOR and NAND support into separate files. 
2003-03-07    dlb     Add support for NAND. 
2003-03-05     gr     Put a message on the screen when doing a fresh start. 
2003-03-04     gr     Added a call to delay watchdog monitoring of tasks if 
                      EFS is doing a fresh start. Initialization takes 
                      longer in this case. 
2003-01-24    bgc     Added handling of FS_LOG_ENTRY_GC_DEALLOC. 
2002-11-15    dlb     Write NOR version. 
2002-08-08    drh     Created by dlb.  NAND version. 
 
===========================================================================*/ 
 
#include  
#include  
 
#ifdef FEATURE_IG_EFS_EXT_SERVER 
  #include "amssassert.h" 
#else 
#include "assert.h" 
#endif 
 
#include "customer.h" 
#include "fs_sys_types.h" 
#include "fs_err.h" 
 
#include "fs_pm_ptable.h" 
#include "fs_pm_super.h" 
#include "fs_device.h" 
#include "fs_pm_log.h" 
#include "fs_logr.h" 
 
#ifdef FEATURE_EFS_EFS2_ON_NAND 
 
#ifdef FS_UNIT_TEST 
#error code not present 
#endif 
 
void fs_ptable_init_nand ( 
    fs_ptable_t         pt, 
    fs_log_t            log, 
    int                *fresh_start); 
 
/********************************************************************** 
 * Entry points 
 *********************************************************************/ 
static void nand_log_iterate (fs_ptable_t pt, fs_log_t log, 
    void (*visit) (void *priv, fs_log_code_t code, uint32 *args), 
    void *priv); 
static page_id nand_get_forward (fs_ptable_t pt, cluster_id cluster); 
static cluster_id nand_get_reverse (fs_ptable_t pt, page_id page); 
static void nand_set_forward (fs_ptable_t pt, cluster_id cluster, 
    page_id page); 
static void nand_set_reverse (fs_ptable_t pt, page_id page, 
    page_id state); 
static int nand_erase (fs_ptable_t pt, block_id block); 
static void nand_begin_erase (fs_ptable_t pt, block_id block); 
static void nand_mark_alloc (fs_ptable_t pt, page_id page); 
static void nand_flush (fs_ptable_t pt); 
static int nand_do_gc (fs_ptable_t pt); 
static void nand_post_init (fs_ptable_t pt); 
static void nand_super_update (fs_ptable_t pt); 
 
/********************************************************************** 
 * Rtable cache operations.  The RTABLE cache is a write cache of changes 
 * to the rtable.  These operations cannot be written to flash until the 
 * log page describing the change has been written (so that they can be 
 * completed).  Also, they cannot be flushed in the middle of a 
 * transaction.  This queue is kept sorted to allow fast lookups. 
 */ 
 
static uint32 internal_lookup (fs_ptable_nand_t npt, uint32 index, 
    int table, int level); 
 
 
/********************************************************************** 
 * Public interface. 
 */ 
 
 
/********************************************************************** 
 * NAND stub implementation.  Track everything in memory. */ 
 
/* Within a given table (at a given level), determine the index of a 
 * particular value within that table. */ 
#define PTABLE_INDEX(npt, id, level) \ 
    (((id) & (npt)->masks[level]) >> ((npt)->shifts[level])) 
 
#ifdef FS_DEBUG_TABLE_CHECK 
static uint32 *static_ftable = NULL; 
static uint32 *static_rtable = NULL; 
static uint32 static_table_size = 0; 
#endif 
 
static void init_shifts_and_masks (fs_ptable_nand_t npt); 
 
static page_id pt_get_reverse (void *priv, page_id page); 
static void pt_set_reverse (void *priv, page_id page, page_id state); 
static int pt_erase (void *priv, block_id block); 
static void pt_mark_alloc (void *priv, page_id page); 
static void pt_page_move (void *priv, page_id state, 
    page_id old_place, page_id new_place); 
static uint32 *pcache_lookup (fs_ptable_nand_t npt, page_id page, 
    int is_to_write); 
static void pcache_write (fs_ptable_nand_t npt, page_id old_page, 
    page_id new_page); 
static void pcache_init (fs_ptable_nand_t npt); 
static void pcache_unstick (fs_ptable_nand_t npt); 
 
void record_table_movement ( 
    fs_ptable_nand_t    npt, 
    page_id             state, 
    page_id             old_place, 
    page_id             new_place); 
 
void fs_ptable_init_nand ( 
    fs_ptable_t         pt, 
    fs_log_t            log, 
    int                *fresh_start) 
{ 
  fs_ptable_nand_t npt = (fs_ptable_nand_t) pt; 
 
  npt->log = log; 
 
  init_shifts_and_masks (npt); 
 
  pt->get_forward = nand_get_forward; 
  pt->get_reverse = nand_get_reverse; 
  pt->set_forward = nand_set_forward; 
  pt->set_reverse = nand_set_reverse; 
  pt->erase = nand_erase; 
  pt->begin_erase = nand_begin_erase; 
  pt->mark_alloc = nand_mark_alloc; 
  pt->flush = nand_flush; 
  pt->log_iterate = nand_log_iterate; 
  pt->do_gc = nand_do_gc; 
  pt->post_init = nand_post_init; 
  pt->super_update = nand_super_update; 
  pt->mid_init = NULL; 
 
  fs_journal_init (&npt->journal); 
 
  npt->last_level = pt->super.data.u.nand.page_depth - 1; 
 
#ifdef FS_DEBUG_TABLE_CHECK 
  if (static_table_size != 0 && 
      static_table_size == pt->super.total_pages) 
  { 
  } else { 
    if (static_ftable != NULL) 
      free (static_ftable); 
    if (static_rtable != NULL) 
      free (static_rtable); 
 
    static_ftable = malloc (pt->super.total_pages * sizeof (page_id)); 
    ASSERT (static_ftable != NULL); 
 
    static_rtable = malloc (pt->super.total_pages * sizeof (page_id)); 
    ASSERT (static_rtable != NULL); 
 
    static_table_size = pt->super.total_pages; 
  } 
  npt->ftable = static_ftable; 
  npt->rtable = static_rtable; 
#endif 
 
  if (*fresh_start) { 
#ifdef FS_DEBUG_TABLE_CHECK 
    page_id i; 
    for (i = 0; i < pt->super.total_pages; i++) { 
      npt->ftable[i] = INVALID_PAGE_ID; 
      npt->rtable[i] = FS_RMAP_BLOCK_UNKNOWN; 
    } 
#endif 
  } else { 
    /* Extract the GC parameters out of the superblock. */ 
    npt->ptable_gc.alloc_next = pt->super.data.alloc_next[1]; 
    npt->ptable_gc.gc_next = pt->super.data.gc_next[1]; 
  } 
 
  npt->ptable_gc.min_page = pt->super.data.u.nand.regions[0] 
    << pt->super.block_shift; 
  npt->ptable_gc.max_page = pt->super.data.u.nand.regions[1] 
    << pt->super.block_shift; 
  npt->ptable_gc.super = &pt->super; 
  npt->ptable_gc.priv = (void *) npt; 
  npt->ptable_gc.dev = pt->dev; 
  npt->ptable_gc.log = npt->log; 
 
  npt->ptable_gc.get_reverse = pt_get_reverse; 
  npt->ptable_gc.set_reverse = pt_set_reverse; 
  npt->ptable_gc.erase = pt_erase; 
  npt->ptable_gc.mark_alloc = pt_mark_alloc; 
  npt->ptable_gc.page_move = pt_page_move; 
 
  npt->logging = 0; 
 
  pcache_init (npt); 
 
  if (*fresh_start) 
    npt->logging = 1; 
 
  fs_gc_init (&npt->ptable_gc, *fresh_start); 
} 
 
static void 
init_shifts_and_masks (fs_ptable_nand_t npt) 
{ 
  int level; 
  uint8         shift = 0; 
  uint32        mask = FS_PTE_PAGE_MASK; 
  uint32        mask_off = 0xFFFFFFFF; 
 
  for (level = npt->parent.super.data.u.nand.page_depth - 1; 
      level >= 0; 
      level--) 
  { 
    mask_off &= ~mask; 
 
    npt->shifts[level] = shift; 
    shift += FS_PTE_SHIFT; 
    npt->masks[level] = mask; 
    mask <<= FS_PTE_SHIFT; 
    npt->mask_off[level] = mask_off; 
  } 
} 
 
void 
fs_ptable_nand_log_visit (fs_ptable_t pt, fs_log_code_t code, uint32 *args) 
{ 
  fs_ptable_nand_t npt = (fs_ptable_nand_t) pt; 
 
  ASSERT (!npt->logging); 
 
  switch (code) { 
    case LOG_CODE_GROUP_END: 
      break; 
 
    case FS_LOG_ENTRY_UPPER_DATA: 
      if (args[0] < FS_UPPER_DATA_COUNT) 
        npt->parent.super.data.upper_data[args[0]] = args[1]; 
      break; 
 
    case FS_LOG_ENTRY_ERASE_FINISH: 
    case FS_LOG_ENTRY_XACT_START: 
    case FS_LOG_ENTRY_XACT_END: 
    case FS_LOG_ENTRY_ERASE_FAIL: 
      break; 
 
    case FS_LOG_ENTRY_PTABLE_MOVE: 
      /* printf ("ptable move: 0x%08x, 0x%04x -> 0x%04x\n", 
          args[0], args[1], args[2]); */ 
      record_table_movement (npt, 
          args[0], 
          args[1], 
          args[2]); 
      break; 
 
    case FS_LOG_ENTRY_GC_MOVE: 
    case FS_LOG_ENTRY_PAGE_MOVE: 
      /* args[0] = cluster. 
       * args[1] = old page, 
       * args[2] = new page */ 
      /* printf ("page_move 0x%08x 0x%08x 0x%08x\n", 
          args[0], args[1], args[2]); */ 
      if (args[1] != INVALID_PAGE_ID) { 
        nand_set_reverse (&npt->parent, args[1], FS_RMAP_PAGE_GARBAGE); 
      } 
 
      /* Mark the new location. */ 
      nand_set_reverse (&npt->parent, args[2], args[0]); 
 
      /* Set the forward table entry. */ 
      nand_set_forward (&npt->parent, args[0], args[2]); 
      break; 
 
    case FS_LOG_ENTRY_NEW_DATA: 
      /* args[0] = cluster. 
       * args[1] = page. */ 
 
      /* Set the reverse table entry. */ 
      nand_set_reverse (&npt->parent, args[1], args[0]); 
 
      /* Set the forward table entry. */ 
      nand_set_forward (&npt->parent, args[0], args[1]); 
      break; 
 
    case FS_LOG_ENTRY_KEYRANGE: 
      /* args[0] = encoded journal for key_type. 
       * args[1] = update_index. */ 
      /* For keyrange walks that get logged, just go through and wipe out 
       * journal entries, just like the code did before. */ 
      /* printf ("keyrange walk: %d, 0x%04x - 0x%04x\n", 
          args[0], 
          args[1], 
          args[1] | (~npt->mask_off[FS_JOURNAL_LEVELOF (args[0])])); */ 
      fs_journal_setup_key_range_walk (&npt->journal, 
          args[0], 
          args[1], 
          args[1] | (~npt->mask_off[FS_JOURNAL_LEVELOF (args[0])])); 
      while (fs_journal_key_range_advance (&npt->journal, TRUE)) 
        ; 
      break; 
 
    case FS_LOG_ENTRY_GC_DEALLOC: 
      /* args[0] = cluster 
       * args[1] = page. */ 
      nand_set_forward (&npt->parent, args[0], INVALID_PAGE_ID); 
      nand_set_reverse (&npt->parent, args[1], FS_RMAP_PAGE_GARBAGE); 
      break; 
 
    case FS_LOG_ENTRY_GARBAGE: 
      /* args[0] = page. */ 
      nand_set_reverse (&npt->parent, args[0], FS_RMAP_PAGE_GARBAGE); 
      break; 
 
    case FS_LOG_ENTRY_LOG_FLUSH: 
      /* args[0] = log page to flush. */ 
      if (fs_log_written_count (npt->log) > 0 
          && fs_log_peek_written (npt->log) == args[0]) 
      { 
        (void) fs_log_get_written (npt->log); 
      } 
      break; 
 
    default: 
      FS_ERR_FATAL ("Unknown log code: 0x%02x", code, 0, 0); 
      break; 
  } 
 
  fs_gc_visit_node (&npt->ptable_gc, code, args); 
} 
 
static void 
nand_log_iterate (fs_ptable_t pt, fs_log_t log, 
    void (*visit) (void *priv, fs_log_code_t code, uint32 *args), 
    void *priv) 
{ 
  (void) pt; 
  (void) log; 
  (void) visit; 
  (void) priv; 
 
  FS_ERR_FATAL ("nand_log_iterate should never be called", 0, 0, 0); 
} 
 
static void 
nand_post_init (fs_ptable_t pt) 
{ 
  fs_ptable_nand_t npt = (fs_ptable_nand_t) pt; 
 
  fs_gc_init_finish (&npt->ptable_gc); 
 
  npt->logging = 1; 
 
#ifdef FS_DEBUG_JOURNAL_CHECK 
  fs_journal_compare (&npt->old_journal, &npt->journal); 
#endif 
} 
 
static void 
nand_super_update (fs_ptable_t pt) 
{ 
  fs_ptable_nand_t npt = (fs_ptable_nand_t) pt; 
 
  /* Fill in fields in the superblock based on our garbage collectors. */ 
  pt->super.data.alloc_next[1] = npt->ptable_gc.alloc_next; 
  pt->super.data.gc_next[1] = npt->ptable_gc.gc_next; 
} 
 
static page_id 
nand_get_forward (fs_ptable_t pt, cluster_id cluster) 
{ 
  fs_ptable_nand_t npt = (fs_ptable_nand_t) pt; 
 
#ifdef FS_DEBUG_TABLE_CHECK 
  page_id a, b; 
 
  b = internal_lookup (npt, cluster, FS_PTABLE, npt->last_level); 
 
  a = npt->ftable[cluster]; 
 
  ASSERT (a == b); 
  return b; 
#else 
  return internal_lookup (npt, cluster, FS_PTABLE, npt->last_level); 
#endif 
} 
 
static cluster_id 
nand_get_reverse (fs_ptable_t pt, page_id page) 
{ 
  fs_ptable_nand_t npt = (fs_ptable_nand_t) pt; 
 
#ifdef FS_DEBUG_TABLE_CHECK 
  cluster_id a, b; 
 
  a = npt->rtable[page]; 
  b = internal_lookup (npt, page, FS_RTABLE, npt->last_level); 
 
  if (a != b) { 
    b = internal_lookup (npt, page, FS_RTABLE, npt->last_level); 
  } 
 
  ASSERT (a == b); 
  return b; 
#else 
    return internal_lookup (npt, page, FS_RTABLE, npt->last_level); 
#endif 
} 
 
static void 
nand_set_forward (fs_ptable_t pt, cluster_id cluster, 
    page_id page) 
{ 
  fs_ptable_nand_t npt = (fs_ptable_nand_t) pt; 
  page_id log_page; 
 
#ifdef FS_DEBUG_TABLE_CHECK 
  npt->ftable[cluster] = page; 
#endif 
 
  if (npt->logging) 
    log_page = fs_log_peek_page (npt->log); 
  else 
    log_page = fs_logr_peek_alloc (npt->parent.logr); 
 
  fs_journal_add (&npt->journal, 
      cluster, FS_JOURNAL_ENCODE (FS_PTABLE, npt->last_level), 
      page, 
      log_page & 0xFF); 
} 
 
static void 
nand_set_reverse (fs_ptable_t pt, page_id page, 
    page_id state) 
{ 
  fs_ptable_nand_t npt = (fs_ptable_nand_t) pt; 
  page_id log_page; 
 
#ifdef FS_UNIT_TEST 
#error code not present 
#endif 
 
#ifdef FS_DEBUG_TABLE_CHECK 
  npt->rtable[page] = state; 
#endif 
 
  if (npt->logging) 
    log_page = fs_log_peek_page (npt->log); 
  else 
    log_page = fs_logr_peek_alloc (npt->parent.logr); 
 
  fs_journal_add (&npt->journal, 
      page, 
      FS_JOURNAL_ENCODE (FS_RTABLE, npt->last_level), 
      state, log_page & 0xFF); 
} 
 
static void nand_begin_erase (fs_ptable_t pt, block_id block) 
{ 
  (void) pt; 
  (void) block; 
  /* Nothing to be done for NAND.  It has hardware CRC checks for 
     page validity */ 
} 
 
static int 
nand_erase (fs_ptable_t pt, block_id block) 
{ 
#ifdef FS_UNIT_TEST 
#error code not present 
#endif 
 
  nand_set_reverse (pt, block << pt->super.block_shift, 
      FS_RMAP_BLOCK_ERASED); 
 
  return pt->super.data.block_size; 
} 
 
static void 
nand_mark_alloc (fs_ptable_t pt, page_id page) 
{ 
  /* If this is not the last page of a block, then advance the erase 
   * marker. */ 
  if (((page + 1) & ~pt->super.block_mask) != 0) { 
    nand_set_reverse (pt, page + 1, FS_RMAP_BLOCK_ERASED); 
  } 
} 
 
/* This does nothing, at least yet. */ 
static void 
nand_flush (fs_ptable_t pt) 
{ 
  fs_ptable_nand_t npt = (fs_ptable_nand_t) pt; 
 
  fs_log_flush (npt->log); 
} 
 
static int 
nand_do_gc (fs_ptable_t pt) 
{ 
  fs_ptable_nand_t npt = (fs_ptable_nand_t) pt; 
  page_id log_page; 
  unsigned age, key_type; 
  uint32 key, value; 
  int table, level, update_table, update_level; 
  uint32 masked_index, update_index; 
  page_id new_location, old_location; 
  uint32        log_args[3]; 
  int did_something = 0; 
  int advancing; 
  uint32        *update_buffer; 
 
  if (fs_journal_free_count (&npt->journal) < 50) 
    FS_ERR_FATAL ("Journal needs to be dealt with", 0, 0, 0); 
 
  /* Do some GC if necessary. */ 
  fs_gc_do_urgent (&npt->ptable_gc); 
 
  if (fs_log_written_count (npt->log) > 20 
      || fs_journal_free_count (&npt->journal) < (FS_JOURNAL_SIZE / 2)) 
  { 
    /* printf ("[lrc]<<<"); fflush (stdout); */ 
    log_page = fs_log_get_written (npt->log); 
    age = log_page & 0xFF; 
 
    /* Setup an age walk. */ 
    fs_journal_setup_age_walk (&npt->journal, age); 
 
    /* XXXX: This will perform "chunkier" than necessary, since lots of 
     * iteration must occur.  This should be made into a state and 
     * performed incrementally as necessary (scheduled with the GC). */ 
    advancing = fs_journal_age_advance (&npt->journal, TRUE); 
    while (advancing) { 
      fs_journal_age_info (&npt->journal, &key, &key_type, &value, &age); 
      table = FS_JOURNAL_TABLEOF (key_type); 
      level = FS_JOURNAL_LEVELOF (key_type); 
 
      ASSERT (level != 0); 
      masked_index = key & npt->mask_off[level]; 
 
      /* If this table entry was previously written, we have to read in the 
       * old contents. */ 
      old_location = internal_lookup (npt, key, table, level-1); 
      update_buffer = pcache_lookup (npt, old_location, 1); 
 
      update_index = masked_index; 
      update_table = table; 
      update_level = level; 
 
      /* Update this entry. */ 
      update_buffer[PTABLE_INDEX (npt, key, level)] = value; 
 
      /* key range walk. */ 
 
      /* NOTE: It is important that this allocation not invoke the garbage 
       * collector. */ 
      new_location = fs_gc_alloc (&npt->ptable_gc); 
 
      fs_journal_setup_key_range_walk (&npt->journal, 
          FS_JOURNAL_ENCODE (update_table, update_level), 
          update_index, 
          update_index | (~npt->mask_off[update_level])); 
      while (fs_journal_key_range_advance (&npt->journal, TRUE)) { 
        fs_journal_key_range_info (&npt->journal, &key, &key_type, 
            &value, &age); 
 
        table = FS_JOURNAL_TABLEOF (key_type); 
        level = FS_JOURNAL_LEVELOF (key_type); 
        ASSERT (level != 0); 
        masked_index = key & npt->mask_off[level]; 
 
        ASSERT (update_index == masked_index); 
        ASSERT (update_level == level); 
        ASSERT (update_table == table); 
 
        update_buffer[PTABLE_INDEX (npt, key, level)] = value; 
      } 
 
      /* Write out the newly updated page table. */ 
      pcache_write (npt, old_location, new_location); 
#if 0 
      result = fs_flash_write_page (pt->dev, 
          new_location, npt->update_buffer, FS_FOP_UNKNOWN); 
      if (result != FS_DEVICE_OK) 
        FS_ERR_FATAL ("Unable to recover from write error", 0, 0, 0); 
#endif 
 
      /* The keyrange log and the table movement must both be recorded in 
       * the log atomically.  record_table_movement uses either 
       * FS_LOG_ENTRY_PAGE_MOVE or FS_LOG_ENTRY_PTABLE_MOVE to record the 
       * movement.  They are both the same size, so it doesn't matter which 
       * we use. */ 
 
      fs_log_group (npt->log, FS_LOG_ENTRY_KEYRANGE, 
          FS_LOG_ENTRY_PTABLE_MOVE); 
 
      /* Log the keyrange walk. */ 
      log_args[0] = FS_JOURNAL_ENCODE (update_table, update_level); 
      log_args[1] = update_index; 
      fs_log_add (npt->log, FS_LOG_ENTRY_KEYRANGE, log_args); 
 
      /* Record the new page table movement. */ 
      record_table_movement (npt, 
          FS_RMAP_PAGE_TABLE (update_table, update_level, 
            update_index), 
          old_location, new_location); 
 
      pcache_unstick (npt); 
 
      advancing = fs_journal_age_advance (&npt->journal, TRUE); 
    } 
 
    /* Log that this log page is no longer needed. */ 
    log_args[0] = log_page; 
    fs_log_add (npt->log, FS_LOG_ENTRY_LOG_FLUSH, log_args); 
 
    did_something = 1; 
  } 
 
  return did_something; 
} 
 
/* This function is the universal lookup.  Index is either a page or a 
 * cluster id (depends on the table).  [table] should be either FS_PTABLE 
 * or FS_RTABLE.  The level is which level of the page tree to look that 
 * value up in.  0 is the top level (looks up in the superblock).  This 
 * always checks the journal first, and also knows how to look at the 
 * currently updated page table entry. */ 
static uint32 
internal_lookup (fs_ptable_nand_t npt, uint32 index, 
    int table, int level) 
{ 
  int result; 
  uint32 value; 
  unsigned age; 
  static uint32 *buf; 
  uint32 masked_index; 
 
  ASSERT (level >= 0); 
 
  /* Top level is always in the superblock. */ 
  if (level == 0) { 
    return npt->parent.super.data.u.nand.tables[table] 
      [PTABLE_INDEX (npt, index, 0)]; 
  } 
 
  masked_index = index; 
  if (level < npt->last_level) { 
    masked_index &= npt->mask_off[level+1]; 
  } 
 
  /* Check for a journal entry. */ 
  result = fs_journal_lookup (&npt->journal, 
      masked_index, FS_JOURNAL_ENCODE (table, level), &value, &age); 
  if (result) { 
    return value; 
  } 
 
  /* Do we have a table to lookup in? */ 
  value = internal_lookup (npt, index, table, level-1); 
 
  if (value == INVALID_PAGE_ID) { 
    return value; 
  } 
 
  /* Read in the page and get the value. 
   * XXX: caching some of these pages might be useful. */ 
  buf = pcache_lookup (npt, value, 0); 
#if 0 
  result = fs_flash_ead_page (npt->parent.dev, value, buf); 
  if (result != FS_DEVICE_OK) 
    FS_ERR_FATAL ("Read error, not sure how to recover", 0, 0, 0); 
#endif 
 
  return buf[PTABLE_INDEX (npt, index, level)]; 
} 
 
/* Called by the gc/other things to record movement of a page table.  There 
 * must be room in the journal for the entries. */ 
void 
record_table_movement ( 
    fs_ptable_nand_t    npt, 
    page_id             state, 
    page_id             old_place, 
    page_id             new_place) 
{ 
  page_id real_index; 
  uint32  log_args[3]; 
  page_id log_page; 
 
  /* The old one is garbage. */ 
  if (old_place != INVALID_PAGE_ID) { 
    nand_set_reverse (&npt->parent, old_place, FS_RMAP_PAGE_GARBAGE); 
  } 
 
  nand_set_reverse (&npt->parent, new_place, state); 
 
  if (npt->logging) 
    log_page = fs_log_peek_page (npt->log); 
  else 
    log_page = fs_logr_peek_alloc (npt->parent.logr); 
 
  /* Update where this table is. */ 
  real_index = FS_RMAP_GET_INDEX (state); 
  if (FS_RMAP_GET_LEVEL (state) > 1) { 
    /* XXX: can nand_set_forward be made generic enough to do this? */ 
    fs_journal_add (&npt->journal, 
        real_index, 
        FS_JOURNAL_ENCODE (FS_RMAP_GET_TABLE (state), 
          FS_RMAP_GET_LEVEL (state) - 1), 
        new_place, 
        log_page & 0xFF); 
  } else { 
    /* level 0 is in the superblock. */ 
    npt->parent.super.data.u.nand.tables 
      [FS_RMAP_GET_TABLE (state)] 
      [PTABLE_INDEX (npt, real_index, 0)] 
      = new_place; 
  } 
 
  /* Log the change. */ 
  if (npt->logging) { 
    log_args[0] = state; 
    log_args[1] = old_place; 
    log_args[2] = new_place; 
    fs_log_add (npt->log, FS_LOG_ENTRY_PTABLE_MOVE, log_args); 
    /* printf ("log move: %x %x %x \n", 
        log_args[0], 
        log_args[1], 
        log_args[2]); */ 
  } 
} 
 
/* Entry points for the GC interface. */ 
 
static void 
pt_page_move (void *priv, 
    page_id     state, 
    page_id     old_place, 
    page_id     new_place) 
{ 
  fs_ptable_nand_t npt = (fs_ptable_nand_t) priv; 
 
  record_table_movement (npt, state, old_place, new_place); 
} 
 
static page_id 
pt_get_reverse (void *priv, page_id page) 
{ 
  fs_ptable_nand_t npt = (fs_ptable_nand_t) priv; 
 
  return nand_get_reverse (&npt->parent, page); 
} 
 
static void 
pt_set_reverse (void *priv, page_id page, page_id state) 
{ 
  fs_ptable_nand_t npt = (fs_ptable_nand_t) priv; 
 
  nand_set_reverse (&npt->parent, page, state); 
} 
 
static int 
pt_erase (void *priv, block_id block) 
{ 
  fs_ptable_nand_t npt = (fs_ptable_nand_t) priv; 
 
  return nand_erase (&npt->parent, block); 
} 
 
static void 
pt_mark_alloc (void *priv, page_id page) 
{ 
  fs_ptable_nand_t npt = (fs_ptable_nand_t) priv; 
 
  nand_mark_alloc (&npt->parent, page); 
} 
 
#ifdef FS_UNIT_TEST 
#error code not present 
#endif /* FS_UNIT_TEST */ 
 
/********************************************************************** 
 * Page table cache.  These routines buffer, and cache lookups to page 
 * table entries.  [is_to_write] is used to indicate if this page will 
 * soon be written to another page (it should be marked sticky). 
 * 
 * Currently, this is rather hard-coded to two pages, but the API shouldn't 
 * change if we use more pages. */ 
 
/* Initialize the pcache. */ 
static void 
pcache_init (fs_ptable_nand_t npt) 
{ 
  int i; 
 
  npt->pcache_recent = 0; 
  npt->pcache_sticky = -1; 
  npt->pcache_page[0] = INVALID_PAGE_ID; 
  npt->pcache_page[1] = INVALID_PAGE_ID; 
  npt->pcache_alias_page = INVALID_PAGE_ID; 
 
  for (i = 0; i < FS_PTE_PER_PAGE; i++) { 
    npt->pcache_data[0][i] = INVALID_PAGE_ID; 
    npt->pcache_data[1][i] = INVALID_PAGE_ID; 
  } 
} 
 
/* Lookup a page table page (by physical address) and return a buffer to 
 * it.  The data is read in if necessary.  If [is_to_write] is true, then 
 * it is valid for [page] to be INVALID_PAGE_ID, in which case the page is 
 * simply filled with all FF. 
 */ 
static uint32 * 
pcache_lookup (fs_ptable_nand_t npt, page_id page, int is_to_write) 
{ 
  int use; 
  int i, result; 
  int doread = 0; 
 
  /* It is not permissible to stick more than one page. */ 
  ASSERT (!is_to_write || npt->pcache_sticky < 0); 
 
  /* Determine if we already have the data in question. */ 
  if (npt->pcache_page[0] == page) { 
    use = 0; 
  } else if (npt->pcache_page[1] == page) { 
    use = 1; 
  } else if (page != INVALID_PAGE_ID && npt->pcache_alias_page == page) { 
    use = npt->pcache_sticky; 
  } else { 
    /* Figure out which page to use for the data. */ 
    if (npt->pcache_sticky >= 0) 
      use = 1 - npt->pcache_sticky; 
    else 
      use = 1 - npt->pcache_recent; 
 
    doread = 1; 
  } 
 
  if (doread) 
    fs_flash_pcache_miss_counter++; 
  else if (use) 
    fs_flash_pcache_hit_counter++; 
 
  npt->pcache_recent = use; 
  npt->pcache_page[use] = page; 
  if (is_to_write) { 
    npt->pcache_sticky = use; 
  } 
 
  if (doread) { 
    if (page == INVALID_PAGE_ID) { 
      for (i = 0; i < FS_PTE_PER_PAGE; i++) 
        npt->pcache_data[use][i] = INVALID_PAGE_ID; 
    } else { 
      result = fs_flash_read_page (npt->parent.dev, page, 
          npt->pcache_data[use], 
          FS_FOP_PTABLE); 
      if (result != FS_DEVICE_OK) 
        FS_ERR_FATAL ("Read error reading page table entry", 0, 0, 0); 
    } 
  } 
 
  return npt->pcache_data[use]; 
} 
 
static void 
pcache_write (fs_ptable_nand_t npt, page_id old_page, page_id new_page) 
{ 
  int use; 
  int result; 
 
  use = npt->pcache_sticky; 
  ASSERT (use >= 0); 
  ASSERT (old_page == npt->pcache_page[use]); 
 
  result = fs_flash_write_page (npt->parent.dev, new_page, 
      npt->pcache_data[use], FS_FOP_PTABLE); 
  if (result != FS_DEVICE_OK) 
    FS_ERR_FATAL ("Write error writing page table entry", 0, 0, 0); 
 
  npt->pcache_page[use] = new_page; 
  npt->pcache_recent = use; 
  npt->pcache_alias_page = old_page; 
  /* npt->pcache_sticky = -1; */ 
} 
 
static void 
pcache_unstick (fs_ptable_nand_t npt) 
{ 
  npt->pcache_sticky = -1; 
  npt->pcache_alias_page = INVALID_PAGE_ID; 
} 
 
#endif /* FEATURE_EFS_EFS2_ON_NAND */