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


/********************************************************************** 
 * fs_pm_ptable_nor.c 
 * 
 * Copyright (C) 2002, 2003, 2004, 2005, 2006, Qualcomm, Inc. 
 * Page table management. 
 */ 
 
/*=========================================================================== 
 
                        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_nor.c#14 $ $DateTime: 2006/09/20 14:10:00 $ $Author: davidb $ 
 
when          who     what, where, why 
--------      ---     ------------------------------------------------------ 
2006-09-20    dlb     Lint cleanup. 
2006-09-11    dlb     Use flash driver wrappers. 
2006-08-23    dlb     Only use sequence number when log page is valid. 
2006-08-22    dlb     Allow logging of garbagified pages. 
2006-08-17    dlb     Add ability to upgrade logs on FS upgrade. 
2006-07-03    sh      Moved dog & lcd_message prototypes to fs_pm.c 
2006-06-26     yg     Memory reduction effort 
2006-03-31    sh      Lint cleanup 
2006-02-17    sh      Silence compiler warning for uninitialized field_size 
2006-01-31    dlb     Cleanup from review. 
2006-01-24    dlb     Assure values read from paired-bit NOR are correct. 
2006-01-17    dlb     Support multiple NOR write styles. 
2006-01-13    dlb     Broken handling of some corrupt log pages. 
2005-12-14    dlb     Handle NOR partial writes on bad powerdown. 
2005-05-26    sd      Compilation fixes for L4. 
2005-04-26    dlb     Allow magic obliteration failures. 
2005-01-27    dlb     Allow standalone builds. 
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-08    dlb     Fix NOR fresh start. 
2004-10-07    dlb     Whitespace cleanup. 
2004-08-14    dlb     Faster initial start on NOR. 
2004-05-14     gr     Suppress lcd message if FEATURE_UI_CORE_REMOVED is on. 
2003-06-17    jkl     Clean up code. 
2003-06-12    adm     Add support for factory start. 
2003-04-24    bgc     Added support for partial erase.  pt->begin_erase() 
                      is used to invalidate the page table and mark it as 
                      garbage.  pt->erase() marks the ptable page as valid. 
2003-04-22     gr     Split off NOR and NAND page managers 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.  Also displays an initialization message. 
2003-02-28    jkl     Clean up code. 
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  
#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_pm_log.h" 
#include "fs_logr.h" 
#include "fs_upgrade.h" 
 
#ifdef FS_STANDALONE 
  /* Spurious defines coming from system includes. */ 
  #undef minor 
  #undef major 
#endif 
 
#ifdef FEATURE_EFS_EFS2_ON_NOR 
 
/* The marker that a ptable is valid is a magic number in the final 
   ptable entry.  It needs to be a value that will never appear 
   during an erase */ 
#define FS_NOR_PTABLE_MAGIC 0xF0F0E1E1 
 
/* Similar value for the paired-bits flash devices.  This must be the same 
 * as the constant FS_NOR_PTABLE_MAGIC, after going through the spreading 
 * operation described in ptable_write_entry_paired. */ 
#define FS_NOR_PTABLE_PAIRED_MAGIC1     0xFC03FC03 
#define FS_NOR_PTABLE_PAIRED_MAGIC2     0xFF00FF00 
 
#ifdef FS_UNIT_TEST 
#error code not present 
#endif 
 
/* static void pcache_test (fs_ptable_t pt); */ 
static void setup_ptables (fs_ptable_nor_t npt); 
static void restart_ptables (fs_ptable_t pt); 
static void find_log_pages (fs_ptable_nor_t npt); 
 
 
/********************************************************************** 
 * Private declarations.  These are the entry points for this 
 * implementation of ptable. */ 
static void ptable_write_entry_simple (fs_ptable_nor_t npt, page_id page, 
                                page_id state, int ignore_error); 
static void ptable_write_entry_paired (fs_ptable_nor_t npt, page_id page, 
                                page_id state, int ignore_error); 
 
static void nor_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 nor_get_forward (fs_ptable_t pt, cluster_id cluster); 
static cluster_id nor_get_reverse_simple (fs_ptable_t pt, page_id page); 
static cluster_id nor_get_reverse_paired (fs_ptable_t pt, page_id page); 
static void nor_set_forward (fs_ptable_t pt, cluster_id cluster, 
    page_id page); 
static void nor_set_reverse (fs_ptable_t pt, page_id page, 
    page_id state); 
static int nor_erase (fs_ptable_t pt, block_id block); 
static void nor_begin_erase (fs_ptable_t pt, block_id block); 
static void nor_mark_alloc (fs_ptable_t pt, page_id page); 
static void nor_flush (fs_ptable_t pt); 
static int nor_do_gc (fs_ptable_t pt); 
static void nor_post_init (fs_ptable_t pt); 
static void nor_super_update (fs_ptable_t pt); 
 
/* Initialize the page table structures.  Call the superblock 
 * initialization.  If this is the first time running on this flash, 
 * initialize ourselves afresh.  Otherwise read in the log and build up a 
 * journal based on what the last operations were. */ 
void 
fs_ptable_init_nor ( 
    fs_ptable_t         pt, 
    fs_log_t            log, 
    int                *fresh_start) 
{ 
  fs_ptable_nor_t npt = (fs_ptable_nor_t) pt; 
  unsigned tmp; 
  unsigned field_size = 0; /* Initialize to avoid compiler warning */ 
 
  (void) log; 
 
  /* Initialize the methods. */ 
  pt->get_forward = nor_get_forward; 
  pt->set_forward = nor_set_forward; 
  pt->set_reverse = nor_set_reverse; 
  pt->erase = nor_erase; 
  pt->begin_erase = nor_begin_erase; 
  pt->mark_alloc = nor_mark_alloc; 
  pt->flush = nor_flush; 
  pt->log_iterate = nor_log_iterate; 
  pt->do_gc = nor_do_gc; 
  pt->post_init = nor_post_init; 
  pt->mid_init = restart_ptables; 
  pt->super_update = nor_super_update; 
 
  npt->rcache.count = 0; 
 
  /* Determine operations specialized to each flash type. */ 
  switch (pt->super.data.u.nor.style) { 
    case FS_DEVICE_WRITES_SIMPLE: 
      field_size = pt->super.data.page_size >> 2; 
      pt->get_reverse = nor_get_reverse_simple; 
      npt->ptable_write_entry = ptable_write_entry_simple; 
      break; 
 
    case FS_DEVICE_WRITES_PAIRED_BITS: 
      field_size = pt->super.data.page_size >> 3; 
      pt->get_reverse = nor_get_reverse_paired; 
      npt->ptable_write_entry = ptable_write_entry_paired; 
      break; 
 
    default: 
      FS_ERR_FATAL ("Unsupported NOR flash device write style", 0, 0, 0); 
  } 
 
  /* pcache_test (pt); */ 
  /* XXX: Move into NOR specific function. */ 
 
  /* Mask to mask of the low bits for the number of entries that will fit 
   * in a page. */ 
  npt->minor_mask = field_size - 1; 
 
  /* Compute a shift to determine which page a table entry belongs in. */ 
  tmp = npt->minor_mask; 
  npt->major_shift = 0; 
  while (tmp != 0) { 
    tmp >>= 1; 
    npt->major_shift++; 
  } 
 
  /* How far into each block does the table (the reserved part) start. */ 
  npt->reserved_offset = 
    pt->super.data.block_size - 
    ((pt->super.data.block_size + npt->minor_mask) >> npt->major_shift); 
 
  /* end XXX */ 
 
#ifndef FS_STANDALONE 
#endif 
 
  npt->in_startup_scan = 1; 
 
  /* Setup properly if this is a fresh start. */ 
  if (*fresh_start) { 
    setup_ptables (npt); 
  } else { 
    find_log_pages (npt); 
  } 
 
#ifdef FS_UNIT_TEST 
#error code not present 
#endif 
} 
 
/* Tacky, that this is here, but it is only used by startup, and can be 
 * shared between different invocations of the FS.  As long as the 
 * different devices are started sequentially, then this won't hurt. */ 
#define MAX_REPLAY_LOGS     (256 + 32) 
static page_id replay_log_pages[MAX_REPLAY_LOGS]; 
static unsigned replay_log_count; 
static uint32 replay_highest_seqno; 
 
/* Setup the initial ptable for a fresh NOR flash.  Construct the proper 
 * tables. */ 
static void 
setup_ptables (fs_ptable_nor_t npt) 
{ 
  fs_ptable_t pt = &npt->parent; 
  page_id page; 
 
  if (pt->super.total_pages > FS_MAX_NOR_PAGES) { 
    FS_ERR_FATAL ("EFS configured too small for flash size", 0, 0, 0); 
  } 
 
  /* Fill in the entire forward page table to be empty. */ 
  for (page = 0; page < pt->super.total_pages; page++) { 
    npt->ptable[page] = INVALID_PAGE_ID; 
  } 
} 
 
/* Compare two sequence numbers.  Divides them into segments and handles 
 * the wrap of segments properly.  Returns true if [a] < [b] logically. */ 
static int 
seqno_is_lesseq (uint32 a, uint32 b) 
{ 
  if (a < 0x40000000 && b >= 0xc0000000) 
    return 0; 
  if (b < 0x40000000 && a >= 0xc0000000) 
    return 1; 
  return a <= b; 
} 
 
/* Preliminary scan of the ptable.  A bad NOR powerdown can cause ptable 
 * entries to be partially written.  This first scan only looks for log 
 * entries so that we can scan through the log to be able to fix up any 
 * partially written ptable entries. */ 
static void 
find_log_pages (fs_ptable_nor_t npt) 
{ 
  fs_ptable_t pt = &npt->parent; 
  uint32 *buf; 
  page_id page, state; 
  uint32 initial_seqno = 0; 
  uint32 highest_seqno; 
  int no_log = 0; 
  int use_log_page; 
  int is_valid; 
 
  if (pt->super.data.log_head == INVALID_PAGE_ID) { 
    FS_ERR_FATAL ("Superblock doesn't contain a log page", 0, 0, 0); 
    /* Not enough initialization happened before and no log page was 
     * written. */ 
  } 
 
  replay_log_count = 0; 
 
  buf = fs_flash_read_pointer (pt->dev, pt->super.data.log_head); 
 
  if (*buf == INVALID_PAGE_ID || 
      !fs_log_is_valid ((void *) buf, pt->dev, pt->super.data.log_head)) 
  { 
    no_log = 1; 
    highest_seqno = INVALID_PAGE_ID; 
  } else { 
    initial_seqno = *buf; 
    highest_seqno = initial_seqno; 
  } 
 
  for (page = 0; page < pt->super.total_pages; page++) { 
    state = pt->get_reverse (pt, page); 
    if (state == FS_RMAP_PAGE_LOG) { 
      /* This is indicated as a log.  As long as the sequence number is in 
       * the right range, include it in the list.  Also include those that 
       * haven't been written, so that they can be used this time up. */ 
      buf = fs_flash_read_pointer (pt->dev, page); 
 
      is_valid = fs_log_is_valid ((void *) buf, pt->dev, page); 
 
      /* Be sure to only add log page if the entire log is erased, not 
       * partially written. */ 
      use_log_page = 0; 
      if (*buf == INVALID_PAGE_ID) { 
        if (fs_flash_is_erased (pt->dev, page)) { 
          use_log_page = 1; 
        } 
      } else if (!no_log && seqno_is_lesseq (initial_seqno, *buf)) { 
        if (is_valid) { 
          use_log_page = 1; 
        } 
      } 
 
      if (use_log_page) 
      { 
        if (replay_log_count == MAX_REPLAY_LOGS) { 
          FS_ERR_FATAL ("Too many logs to replay", 0, 0, 0); 
        } 
        replay_log_pages[replay_log_count] = page; 
        replay_log_count++; 
      } 
      if (is_valid && *buf != INVALID_PAGE_ID && 
          (highest_seqno == INVALID_PAGE_ID || 
           seqno_is_lesseq (highest_seqno, *buf))) 
        highest_seqno = *buf; 
    } 
  } 
 
  replay_highest_seqno = highest_seqno; 
 
  fs_upgrade_finished (FS_UPGRADE_LOG_ZERO_AFTER_CRC); 
} 
 
/* Reload the ptables from the contents of flash.  The superblock has been 
 * loaded from flash.  The first log replay should have found the partially 
 * written values, and corrected them. */ 
static void 
restart_ptables (fs_ptable_t pt) 
{ 
  fs_ptable_nor_t npt = (fs_ptable_nor_t) pt; 
  page_id page, state; 
 
  npt->in_startup_scan = 0; 
 
  /* printf ("Log start = 0x%x\n", pt->super.data.log_head); */ 
 
  /* TODO: Redundant. */ 
  if (pt->super.data.log_head == INVALID_PAGE_ID) { 
    /* "Not enough initialization happened to write a log page." */ 
    FS_ERR_FATAL ("Superblock doesn't contain a log page", 0, 0, 0); 
  } 
 
  /* XXX: This can be sped up quite a bit by directly reading the flash 
   * here. */ 
 
  /* First, zero out all of the existing page tables. */ 
  if (pt->super.total_pages > FS_MAX_NOR_PAGES) { 
    FS_ERR_FATAL ("EFS configured too small for flash size", 0, 0, 0); 
  } 
  memset (npt->ptable, 0xFF, pt->super.total_pages * sizeof (page_id)); 
 
  /* Scan through each rtable entry, and insert the appropriate ones into 
   * the ptable. */ 
  for (page = 0; page < pt->super.total_pages; page++) { 
    state = pt->get_reverse (pt, page); 
    if (!FS_RMAP_IS_SPECIAL (state)) { 
      ASSERT (state < FS_MAX_NOR_PAGES); 
      if (npt->ptable[state] != INVALID_PAGE_ID) { 
        FS_ERR_FATAL ("XXX: Handle duplicate page", 0, 0, 0); 
      } 
 
      if (state >= pt->super.total_pages) 
        FS_ERR_FATAL ("Out of bounds ptable entry", 0, 0, 0); 
 
      npt->ptable[state] = page; 
    } 
  } 
} 
 
/* Called by the upper layer to replay the log.  This is here, because we 
 * are the ones that found where the logs are in the first place.  This is 
 * fairly complex, but needed to handle all of the special cases that the 
 * log ends up in. */ 
static void 
nor_log_iterate (fs_ptable_t pt, fs_log_t log, 
    void (*visit) (void *priv, fs_log_code_t code, uint32 *args), 
    void *priv) 
{ 
  fs_ptable_nor_t npt = (fs_ptable_nor_t) pt; 
  unsigned a, b; 
  int result; 
  uint32 header[2]; 
  fs_log_code_t code; 
  uint32 args[4]; 
  int done; 
  int xact_depth = 0; 
  uint32 state; 
  unsigned tmp; 
 
  /* Setup the log playing to start with the right sequence number. */ 
  fs_log_set_sequence (log, replay_highest_seqno); 
 
  for (a = 0; a < replay_log_count; a++) { 
    if (replay_log_pages[a] == pt->super.data.log_head) 
      break; 
  } 
 
  /* If the log isn't found something is seriously wrong.  This also 
   * catches zero logs being found. */ 
  if (a == replay_log_count) { 
    /* This happens with a bunch of consecutive bad powerdowns.  The GC 
     * doesn't make much progress, usually just copying data and then 
     * erasing it. */ 
    return; 
    /* FS_ERR_FATAL ("Unable to find log for replay", 0, 0, 0); */ 
  } 
 
  /* Now iterate through each of these pages. */ 
  b = a; 
  do { 
    header[0] = INVALID_PAGE_ID; 
    result = fs_log_iter_set_page (log, replay_log_pages[b], header); 
    /* printf ("Log: 0x%04x, result = %d, header[0] = 0x%x, " 
               "header[1] = 0x%x\n", 
        replay_log_pages[b], result, header[0], header[1]); */ 
 
    if (result == FS_LOG_ITER_GOOD) { 
      /* Loop through the contents of the log. */ 
      done = 0; 
      while (!done) { 
        fs_log_iter_step (log, &code, args); 
 
        switch (code) { 
          case LOG_CODE_GROUP_END: 
            done = 1; 
            break; 
 
          case FS_LOG_ENTRY_GC_DEALLOC: 
            /* Mark this page as garbage */ 
            if (header[1] == 0xFFFFFFFF) { 
              npt->ptable[args[0]] = INVALID_PAGE_ID; 
              if (pt->get_reverse (pt, args[1]) != FS_RMAP_PAGE_GARBAGE) 
                pt->set_reverse (pt, args[1], FS_RMAP_PAGE_GARBAGE); 
            } 
            break; 
 
          case FS_LOG_ENTRY_GC_MOVE: 
            /* Hmmm. */ 
            if (header[1] == 0xFFFFFFFF) { 
              npt->ptable[args[0]] = args[2]; 
              if (pt->get_reverse (pt, args[1]) != FS_RMAP_PAGE_GARBAGE) 
                pt->set_reverse (pt, args[1], FS_RMAP_PAGE_GARBAGE); 
              if (pt->get_reverse (pt, args[2]) != args[0]) { 
                pt->set_reverse (pt, args[2], args[0]); 
              } 
            } 
            /*printf ("gc move: %04x: %04x -> %04x\n", 
                args[0], args[1], args[2]);*/ 
            break; 
 
          case FS_LOG_ENTRY_ERASE_FINISH: 
            /* printf ("Erase finish: %d (%04x)\n", 
                args[0], args[0] << pt->super.block_shift); */ 
            break; 
 
          case FS_LOG_ENTRY_XACT_START: 
            xact_depth++; 
            /* printf ("< start transaction\n"); */ 
            break; 
 
          case FS_LOG_ENTRY_XACT_END: 
            xact_depth--; 
            /* printf ("> end transaction>\n"); */ 
            break; 
 
          case FS_LOG_ENTRY_NEW_DATA: 
            if (header[1] == 0xFFFFFFFF) { 
              npt->ptable[args[0]] = args[1]; 
              if (pt->get_reverse (pt, args[1]) != args[0]) 
                pt->set_reverse (pt, args[1], args[0]); 
            } 
            /* printf (" new data: %04x: %04x\n", args[0], args[1]); */ 
            break; 
 
          case FS_LOG_ENTRY_PAGE_MOVE: 
            if (header[1] == 0xFFFFFFFF) { 
              npt->ptable[args[0]] = args[2]; 
              if (pt->get_reverse (pt, args[1]) != FS_RMAP_PAGE_GARBAGE) 
                pt->set_reverse (pt, args[1], FS_RMAP_PAGE_GARBAGE); 
              if (pt->get_reverse (pt, args[2]) != args[0]) 
                pt->set_reverse (pt, args[2], args[0]); 
            } 
            /* printf (" page move: %04x: %04x -> %04x\n", 
                args[0], args[1], args[2]); */ 
            break; 
 
          case FS_LOG_ENTRY_UPPER_DATA: 
            if (args[0] < FS_UPPER_DATA_COUNT) 
              pt->super.data.upper_data[args[0]] = args[1]; 
            break; 
 
          case FS_LOG_ENTRY_LOG_ALLOC: 
            /* Detect partially written log ptable entries.  If we have 
             * already missed this log in the startup scan, then turn the 
             * page into garbage, otherwise into a log.  It is important to 
             * actually check the log list, since partial writes can cause 
             * differing reads. */ 
            state = pt->get_reverse (pt, args[0]); 
            switch (state) { 
              case FS_RMAP_PAGE_GARBAGE: 
              case FS_RMAP_PAGE_LOG: 
                break; 
 
              default: 
                for (tmp = 0; tmp < replay_log_count; tmp++) { 
                  if (replay_log_pages[tmp] == args[0]) 
                    break; 
                } 
                if (tmp < replay_log_count) { 
                  pt->set_reverse (pt, args[0], FS_RMAP_PAGE_LOG); 
                } else { 
                  pt->set_reverse (pt, args[0], FS_RMAP_PAGE_GARBAGE); 
                } 
            } 
            break; 
 
          case FS_LOG_ENTRY_GARBAGE: 
            /* Garbage detected by the startup code.  Make sure the ptable 
             * entry was written correctly. */ 
            if (pt->get_reverse (pt, args[0]) != FS_RMAP_PAGE_GARBAGE) { 
              pt->set_reverse (pt, args[0], FS_RMAP_PAGE_GARBAGE); 
            } 
            break; 
 
          default: 
            FS_ERR_FATAL ("Unknown log code encountered: 0x%02x", 
                code, 0, 0); 
        } 
 
        if (code != LOG_CODE_GROUP_END) 
          visit (priv, code, args); 
      } 
 
      /* If it has not had its header changed, then add it to the queue, so 
       * that it will get flushed properly. */ 
      if (header[1] == 0xFFFFFFFF) { 
        fs_log_add_written_page (log, replay_log_pages[b]); 
      } 
    } else if (result == FS_LOG_ITER_CORRUPT) { 
      /* It is effectively now garbage. */ 
    } else /* result == FS_LOG_ITER_ERASED */ { 
      /* It is marked as a log already, so just add it to the queue.  We're 
       * doing them in the right order, so things should be OK. */ 
      fs_log_add_page (log, replay_log_pages[b]); 
    } 
 
    /* Increment mod the count. */ 
    b++; 
    if (b == replay_log_count) 
      b = 0; 
  } while (b != a); 
 
  if (xact_depth != 0) { 
    FS_ERR_FATAL ("Unterminated transaction detected", 0, 0, 0); 
  } 
} 
 
static void 
nor_post_init (fs_ptable_t pt) 
{ 
  (void) pt; 
} 
 
static void 
nor_super_update (fs_ptable_t pt) 
{ 
  (void) 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. 
 */ 
 
/* Lookup the entry in the queue.  Returns the place in the queue where the 
 * entry should be inserted. */ 
static int 
rcache_lookup (struct fs_ptable_rcache *rc, page_id entry) 
{ 
  int a, b, c; 
  page_id tmp; 
 
  a = 0; 
  b = rc->count; 
 
  while (b > a) { 
    c = (a + b) >> 1; 
    tmp = rc->data[c].page; 
    if (entry == tmp) 
      return c; 
    if (entry < tmp) 
      b = c; 
    else 
      a = c + 1; 
  } 
 
  return b; 
} 
 
/********************************************************************** 
 * Public interface. 
 */ 
static page_id 
nor_get_forward (fs_ptable_t pt, cluster_id cluster) 
{ 
  fs_ptable_nor_t npt = (fs_ptable_nor_t) pt; 
  return npt->ptable[cluster]; 
} 
 
/* Reverse page table reading for simple flash devices. */ 
static cluster_id 
nor_get_reverse_simple (fs_ptable_t pt, page_id page) 
{ 
  fs_ptable_nor_t npt = (fs_ptable_nor_t) pt; 
  int index = rcache_lookup (&npt->rcache, page); 
  page_id *pdata, *pt_pdata; 
 
  page_id block_base; 
  unsigned page_offset; 
  unsigned last_page_offset; 
  unsigned minor, pt_minor; 
  unsigned major, pt_major; 
  page_id tmp; 
 
  if (index < npt->rcache.count && npt->rcache.data[index].page == page) 
    return npt->rcache.data[index].state; 
 
  block_base = page & pt->super.block_mask; 
 
  page_offset = page - block_base; 
  last_page_offset = pt->super.data.block_size-1; 
 
  if (page_offset >= npt->reserved_offset) 
    return FS_RMAP_PAGE_RESERVED; 
 
  /* Major is the offset in pages from the beginning of the block to the 
     ptable page that contains the page_offset */ 
  major = npt->reserved_offset +(page_offset >> npt->major_shift); 
  pt_major = npt->reserved_offset + (last_page_offset >> npt->major_shift); 
 
  /* Minor is the offset within that major page of the ptable entry 
     for page_offset. */ 
  minor = page_offset & npt->minor_mask; 
  pt_minor = last_page_offset & npt->minor_mask; 
 
  pdata = (page_id *) fs_flash_read_pointer (pt->dev, block_base + major); 
  pt_pdata = pdata; 
 
  if(major != pt_major) { 
    pt_pdata = (page_id *) fs_flash_read_pointer (pt->dev, 
                                                  block_base + pt_major); 
  } 
 
  /* If the ptable is not blessed with the proper value then this 
     block is garbage */ 
  if(pt_pdata[pt_minor] != FS_NOR_PTABLE_MAGIC) 
    return FS_RMAP_PAGE_GARBAGE; 
 
  tmp = pdata[minor]; 
  if (tmp == 0) 
    return FS_RMAP_PAGE_GARBAGE; 
  else if (tmp == 0xFFFFFFFF) 
    return FS_RMAP_BLOCK_ERASED; 
  else 
    return tmp; 
} 
 
/* Reverse page table reading for paired-bit flash devices. */ 
static cluster_id 
nor_get_reverse_paired (fs_ptable_t pt, page_id page) 
{ 
  fs_ptable_nor_t npt = (fs_ptable_nor_t) pt; 
  int index = rcache_lookup (&npt->rcache, page); 
  page_id *pdata, *pt_pdata; 
 
  page_id block_base; 
  unsigned page_offset; 
  unsigned last_page_offset; 
  unsigned minor, pt_minor; 
  unsigned major, pt_major; 
  page_id tmp, tmp2; 
 
  if (index < npt->rcache.count && npt->rcache.data[index].page == page) 
    return npt->rcache.data[index].state; 
 
  block_base = page & pt->super.block_mask; 
 
  page_offset = page - block_base; 
  last_page_offset = pt->super.data.block_size - 1; 
 
  if (page_offset >= npt->reserved_offset) 
    return FS_RMAP_PAGE_RESERVED; 
 
  /* Major is the offset in pages from the beginning of the block to the 
     ptable page that contains the page_offset */ 
  major = npt->reserved_offset +(page_offset >> npt->major_shift); 
  pt_major = npt->reserved_offset + (last_page_offset >> npt->major_shift); 
 
  /* Minor is the offset within that major page of the ptable entry 
     for page_offset. */ 
  minor = page_offset & npt->minor_mask; 
  pt_minor = last_page_offset & npt->minor_mask; 
 
  pdata = (page_id *) fs_flash_read_pointer (pt->dev, block_base + major); 
  pt_pdata = pdata; 
 
  if(major != pt_major) { 
    pt_pdata = (page_id *) fs_flash_read_pointer (pt->dev, 
                                                  block_base + pt_major); 
  } 
 
  /* If the ptable is not blessed with the proper value then this 
     block is garbage */ 
  if (pt_pdata[2*pt_minor]   != FS_NOR_PTABLE_PAIRED_MAGIC1 || 
      pt_pdata[2*pt_minor+1] != FS_NOR_PTABLE_PAIRED_MAGIC2) 
  { 
    /* printf ("%08x %08x\n", pt_pdata[2*pt_minor+1], pt_pdata[2*pt_minor]); */ 
    return FS_RMAP_PAGE_GARBAGE; 
  } 
 
  /* Please see the full description of this code in 
   * ptable_write_entry_paired.  Take the 64-bit 'spread' number, and 
   * compress it back into a sane result. */ 
  tmp = pdata[2*minor]; 
  ASSERT (npt->in_startup_scan || ((tmp ^ (tmp >> 1)) & 0x55555555U) == 0); 
  tmp = ((tmp & 0x44444444) >> 1) | (tmp & 0x11111111); 
  tmp = ((tmp & 0x30303030) >> 2) | (tmp & 0x03030303); 
  tmp = ((tmp & 0x0f000f00) >> 4) | (tmp & 0x000f000f); 
  tmp = ((tmp & 0x00ff0000) >> 8) | (tmp & 0x000000ff); 
 
  tmp2 = pdata[2*minor+1]; 
  ASSERT (npt->in_startup_scan || ((tmp2 ^ (tmp2 >> 1)) & 0x55555555U) == 0); 
  tmp2 = ((tmp2 & 0x44444444) >> 1) | (tmp2 & 0x11111111); 
  tmp2 = ((tmp2 & 0x30303030) >> 2) | (tmp2 & 0x03030303); 
  tmp2 = ((tmp2 & 0x0f000f00) >> 4) | (tmp2 & 0x000f000f); 
  tmp2 = ((tmp2 & 0x00ff0000) >> 8) | (tmp2 & 0x000000ff); 
 
  tmp |= tmp2 << 16; 
 
  if (tmp == 0) 
    return FS_RMAP_PAGE_GARBAGE; 
  else if (tmp == 0xFFFFFFFF) 
    return FS_RMAP_BLOCK_ERASED; 
  else 
    return tmp; 
} 
 
static void 
nor_set_forward (fs_ptable_t pt, cluster_id cluster, page_id page) 
{ 
  fs_ptable_nor_t npt = (fs_ptable_nor_t) pt; 
 
  npt->ptable[cluster] = page; 
} 
 
static void 
nor_set_reverse (fs_ptable_t pt, page_id page, page_id state) 
{ 
  fs_ptable_nor_t npt = (fs_ptable_nor_t) pt; 
  struct fs_ptable_rcache *rc = &npt->rcache; 
  int index; 
 
#ifdef FS_UNIT_TEST 
#error code not present 
#endif 
 
  index = rcache_lookup (rc, page); 
 
  ASSERT ((page & ~pt->super.block_mask) < npt->reserved_offset); 
  ASSERT (state != FS_RMAP_BLOCK_ERASED); 
 
  if (index < rc->count && rc->data[index].page == page) 
    rc->data[index].state = state; 
  else { 
    if (rc->count + 1 > FS_MAX_PTABLE_RCACHE) { 
      FS_ERR_FATAL ("Ptable rcache overflow", 0, 0, 0); 
    } 
 
    if (index < rc->count) { 
      memmove (&rc->data[index + 1], &rc->data[index], 
          (rc->count - index) * sizeof (struct fs_ptable_rcache_entry)); 
    } 
 
    rc->count++; 
    rc->data[index].page = page; 
    rc->data[index].state = state; 
  } 
} 
 
/* Mark the last page of this block with a page table entry that invalidates 
   the entire block.  This prevents a block that is being erased from being 
   confused as real data */ 
static void 
nor_begin_erase (fs_ptable_t pt, block_id block) 
{ 
  fs_ptable_nor_t npt = (fs_ptable_nor_t) pt; 
  page_id block_end = (block << pt->super.block_shift) 
    + pt->super.data.block_size; 
 
  npt->ptable_write_entry (npt, block_end-1, 0, TRUE); 
} 
 
/* Clean out rcache for pages in the erased block.  Write 
   the magic value to validate the page table. Returns the 
   number of erased pages. */ 
static int 
nor_erase (fs_ptable_t pt, block_id block) 
{ 
  fs_ptable_nor_t npt = (fs_ptable_nor_t) pt; 
  page_id block_start, block_end; 
  page_id tmp; 
  int i; 
 
#ifdef FS_UNIT_TEST 
#error code not present 
#endif 
 
  /* Scan through the rcache, and update any entries that are in this 
   * block. */ 
  block_start = block << pt->super.block_shift; 
  block_end = block_start + pt->super.data.block_size; 
 
  for (i = 0; i < npt->rcache.count; i++) { 
    tmp = npt->rcache.data[i].page; 
    if (tmp >= block_start && tmp < block_end) 
      npt->rcache.data[i].state = FS_RMAP_BLOCK_ERASED; 
  } 
 
  /* Initialize the ptable is valid entry */ 
  npt->ptable_write_entry (npt, block_end-1, FS_NOR_PTABLE_MAGIC, FALSE); 
 
  return npt->reserved_offset; 
} 
 
static void 
nor_mark_alloc (fs_ptable_t pt, page_id page) 
{ 
  (void) pt; 
  (void) page; 
  /* This doesn't do anything for NOR flash, since the rtable got erased 
   * automatically by the erase. */ 
} 
 
/* Flush a single page table entry. */ 
static void 
ptable_write_entry_simple (fs_ptable_nor_t npt, page_id page, page_id state, 
    int ignore_error) 
{ 
  page_id block_base, page_offset, minor, major; 
  int result; 
 
  block_base = page & npt->parent.super.block_mask; 
  page_offset = page - block_base; 
 
  minor = page_offset & npt->minor_mask; 
  major = npt->reserved_offset + (page_offset >> npt->major_shift); 
 
  if (state == FS_RMAP_PAGE_GARBAGE) 
    state = 0; 
 
  result = fs_flash_partial_write (npt->parent.dev, block_base + major, 
      &state, minor * sizeof (page_id), sizeof (page_id)); 
  if (!ignore_error && result != FS_DEVICE_OK) 
    FS_ERR_FATAL ("Unable to write to flash", 0, 0, 0); 
} 
 
/* Ptable writing for 'paired-bit' flash devices. 
 * 
 * These flash devices store each pair of bits in a single cell, with 4 
 * different charge levels representing the values of the two bits. 
 * Because of this, these devices typically aren't able to re-issue a write 
 * if there is a power-failure mid-write. 
 * 
 * To compensate for this, we "spread" the 32-bit entry across 64 bits, 
 * making each pair of bits always have the same value. */ 
static void 
ptable_write_entry_paired (fs_ptable_nor_t npt, page_id page, page_id state, 
    int ignore_error) 
{ 
  page_id block_base, page_offset, minor, major; 
  uint32 a, b; 
  int result; 
 
  block_base = page & npt->parent.super.block_mask; 
  page_offset = page - block_base; 
 
  minor = page_offset & npt->minor_mask; 
  major = npt->reserved_offset + (page_offset >> npt->major_shift); 
 
  if (state == FS_RMAP_PAGE_GARBAGE) 
    state = 0; 
 
  /* Spread data.  Does so with a divide and conquer approach.  This really 
   * does work.  The first line moves 8 bits into the other 16-bit part. 
   * Then we move 4 bits of each of those parts up by 4 bits.  Progressing 
   * all the way down to a single bit move ends will all of the bits spread 
   * out.  The final shift and or makes every pair of bits have the same 
   * value. */ 
  a = state; 
  a = ((a & 0x0000ff00) << 8) | (a & 0x000000ff); 
  a = ((a & 0x00f000f0) << 4) | (a & 0x000f000f); 
  a = ((a & 0x0c0c0c0c) << 2) | (a & 0x03030303); 
  a = ((a & 0x22222222) << 1) | (a & 0x11111111); 
  a |= a << 1; 
 
  b = state >> 16; 
  b = ((b & 0x0000ff00) << 8) | (b & 0x000000ff); 
  b = ((b & 0x00f000f0) << 4) | (b & 0x000f000f); 
  b = ((b & 0x0c0c0c0c) << 2) | (b & 0x03030303); 
  b = ((b & 0x22222222) << 1) | (b & 0x11111111); 
  b |= b << 1; 
 
  result = fs_flash_partial_write (npt->parent.dev, block_base + major, 
      &a, minor * 2 * sizeof (page_id), sizeof (page_id)); 
  if (!ignore_error && result != FS_DEVICE_OK) 
    FS_ERR_FATAL ("Unable to write to flash", 0, 0, 0); 
 
  result = fs_flash_partial_write (npt->parent.dev, block_base + major, 
      &b, (minor * 2 + 1) * sizeof (page_id), sizeof (page_id)); 
  if (!ignore_error && result != FS_DEVICE_OK) 
    FS_ERR_FATAL ("Unable to write to flash", 0, 0, 0); 
} 
 
/* Flush the rcache out to flash.  This is performed in three passes. 
 * First the entries for logs are flushed out.  This way, the other entries 
 * will always be finished, if partially done.  Then, the garbage changes 
 * are written.  Lastly the rest of the movement is written. 
 * 
 * There is a problem if power is lost while writing a log entry. 
 * This location could be randomly written to another value, causing 
 * collisions.  To solve this, we carefully choose the value of the log 
 * page type such that a partial write could never be confused with any 
 * other type of value. 
 * 
 * This routine should only be called if the buffered log is empty, 
 * otherwise entries will be flushed before their log pages are flushed. */ 
static void 
nor_flush (fs_ptable_t pt) 
{ 
  fs_ptable_nor_t npt = (fs_ptable_nor_t) pt; 
  page_id state; 
  int i; 
 
  /* First loop through, and write the log entries. */ 
  for (i = 0; i < npt->rcache.count; i++) { 
    state = npt->rcache.data[i].state; 
    if (state == FS_RMAP_PAGE_LOG) 
      npt->ptable_write_entry (npt, npt->rcache.data[i].page, state, FALSE); 
  } 
 
  /* Loop through all of the pcache, and write out all of the change to 
   * garbage entries. */ 
  for (i = 0; i < npt->rcache.count; i++) { 
    state = npt->rcache.data[i].state; 
    if (state == FS_RMAP_PAGE_GARBAGE) 
      npt->ptable_write_entry (npt, npt->rcache.data[i].page, state, FALSE); 
  } 
 
  /* Now go through all of the entries, and flush the rest out. */ 
  for (i = 0; i < npt->rcache.count; i++) { 
    state = npt->rcache.data[i].state; 
    if (state != FS_RMAP_BLOCK_ERASED && state != FS_RMAP_PAGE_GARBAGE && 
        state != FS_RMAP_PAGE_LOG) 
      npt->ptable_write_entry (npt, npt->rcache.data[i].page, state, FALSE); 
  } 
 
  npt->rcache.count = 0; 
} 
 
/* There is no GC to do in nor ptable. */ 
static int 
nor_do_gc (fs_ptable_t pt) 
{ 
  (void) pt; 
 
  return 0; 
} 
 
#endif  /* FEATURE_EFS_EFS2_ON_NOR */