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


/***********************************************************************
 * fs_factory.c
 *
 * Factory image construction/deconstruction.
 * Copyright (C) 2004, 2005, 2006 QUALCOMM, Inc.
 *
 * Implementation of the factory gang image.
 * Note that that the nature of this task necessitates having some
 * knowledge of internal structures of other parts of the filesystem.  The
 * following (non-exclusive) list gives some of these dependencies:
 *
 *   - Storage of the freemap in the buffer code (specifically, how the
 *     bitmap is represented, and that it starts in cluster #1).
 *   - Format and meaning of the superblocks.
 *   - Layout of the regions in the filesystem.
 *
 * In addition, since the factory image startup is an infrequent event, we
 * avoid, as much as possible, requiring RAM dedicated to this purpose.  As
 * such, the following data structures are used in a somewhat unnatural way:
 *
 *   - The fs_buffer buffer pool.
 *
 ***********************************************************************/

/*===========================================================================

                        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_factory.c#15 $ $DateTime: 2006/09/18 15:07:36 $ $Author: davidb $

when         who   what, where, why
----------   ---   ---------------------------------------------------------
2006-09-11   dlb   Use flash driver wrappers.
2006-05-08   sh    Fixed RVCT warnings
2006-05-07   sh    Removed extra 'break;' statements
2006-04-17   dlb   Add CRC to superblock.
2006-03-23   dlb   Allow capture from non-512 byte page devices.
2006-03-17   dlb   Page order discipline fixes.
2005-10-30   sh    Lint cleanup.
2005-08-03   sh    Print a Diag MSG when Factory Start code finds an image.
2005-05-26   sd    Compilation fixes for L4.
2005-04-26   dlb   Add 2K page support.
2005-02-24   dlb   Fix restore of large images.
2005-02-15   dlb   Disallow different sized partition.
2005-01-27   dlb   Allow standalone builds.
2005-01-17   dlb   Cleanup from code review.
2005-01-04   dlb   Allow factory image restore in different sized partition.
2004-12-14   dlb   Create

===========================================================================*/

#include "fs_sys_types.h"
#include "fs_err.h"
#include "fs_errno.h"
#include "fs_buffer.h"
#include "msg.h"
#ifdef FEATURE_IG_EFS_EXT_SERVER
  #include "amssassert.h"
#else
  #include "assert.h"
#endif

#include "crc.h"

#include "fs_factory.h"

/* The factory image consists of single page header followed by the active
 * clusters of the filesystem.  This code makes some assumptions about how
 * the freemap is stored by the buffer code in order to determine which
 * clusters are actually active. */
typedef struct factory_header
{
  uint32    magic1;
  uint32    magic2;
  uint16    fact_version;           /* Version of this cluster. */

  /* Fields needed for the superblock. */
  uint16    version;                /* Superblock version. */
  uint32    block_size;             /* Pages per block. */
  uint32    page_size;              /* Page size in bytes. */
  uint32    block_count;            /* Total blocks in device. */
  uint32    space_limit;            /* Total number of used pages (defines
                                       the size of the map). */
  uint32    upper_data[FS_UPPER_DATA_COUNT];
} *factory_header_t;

/* There are two magic numbers in the factory image information cluster
 * These are to help recognize the first non-bad block containing the
 * information cluster and cluster map pages*/
#define EFS_FACTIMAGE_MAGIC1    0x34856787u
#define EFS_FACTIMAGE_MAGIC2    0x92347759u

#define EFS_FACTIMAGE_VERSION   3

/* The factory images are produced in fixed sized chunks, defined by the
 * diag protocol. */
#define EFS_FACTIMAGE_PSIZE     512

/* Define various values so that the capture code doesn't need to be as
 * aware of the page size difference.  These values masks and shifts
 * manipulate the various EFS_FACTIMAGE_PSIZE chunks of data contained
 * within a single page. */
#if EFS_PAGE_SIZE == 512
  #define SPLIT_SUB_COUNT 1
  #define SPLIT_SHIFT     0
#elif EFS_PAGE_SIZE == 2048
  #define MUST_SPLIT_PAGES
  #define SPLIT_SUB_COUNT 4
  #define SPLIT_SHIFT     2
  #define SPLIT_MASK      3
#else
  #error "Unsupported page size"
#endif

#if (EFS_FACTIMAGE_PSIZE << SPLIT_SHIFT) != EFS_PAGE_SIZE
  #error "Split variables are incorrectly defined"
#endif

#if SPLIT_SUB_COUNT != (1 << SPLIT_SHIFT)
  #error "Split variables are incorrectly defined"
#endif

#ifdef MUST_SPLIT_PAGES
/* Split pages need a separate buffer to read the entire page into, since
 * the driver doesn't support partial page read. */
static byte fs_factory_page_buffer[EFS_PAGE_SIZE];
#endif

/* Generate the factory header, based on information from the current
 * superblock. */
static int
fs_make_factory_header (struct fs_factimage_read_info *info,
    byte *page_data,
    struct fsb_nand_data *buf,
    struct fs_pm_flash_data *pm)
{
  int i;
  factory_header_t head = (factory_header_t) page_data;

  memset (page_data, 0, EFS_FACTIMAGE_PSIZE);

  head->magic1 = EFS_FACTIMAGE_MAGIC1;
  head->magic2 = EFS_FACTIMAGE_MAGIC2;
  head->fact_version = EFS_FACTIMAGE_VERSION;

  head->version = pm->ptable->super.data.version;
  head->block_size = pm->ptable->super.data.block_size;
  head->page_size = pm->ptable->super.data.page_size;
  head->block_count = pm->ptable->super.data.block_count;
  head->space_limit = buf->space_limit;

  for (i = 0; i < FS_UPPER_DATA_COUNT; i++) {
    head->upper_data[i] = buf->fields[i];
  }

  /* Transition to outputting the freemap. */
#ifdef MUST_SPLIT_PAGES
  info->stream_state = 1;
#else
  info->stream_state = 2;
#endif
  info->cluster_data_seqno = 1;

  return 0;
}

#ifdef MUST_SPLIT_PAGES
static int
fs_send_factory_adjunct (struct fs_factimage_read_info *info,
    byte *page_data)
{
  memset (page_data, 0, EFS_FACTIMAGE_PSIZE);

  info->cluster_data_seqno++;
  if (info->cluster_data_seqno == SPLIT_SUB_COUNT) {
    info->stream_state = 2;
    info->cluster_data_seqno = 1 << SPLIT_SHIFT;
  }

  return 0;
}
#endif

/* Output the pages of the data.  We rely on the buffer code telling us
 * that pages in the freemap are allocated. */
static int
fs_output_data (struct fs_factimage_read_info *info,
    byte *page_data,
    struct fsb_nand_data *buf,
    struct fs_pm_flash_data *pm)
{
  cluster_id cluster;

  while (1) {
    cluster = info->cluster_data_seqno >> SPLIT_SHIFT;

    if (cluster >= buf->space_limit) {
      info->stream_state = 0;
      return 0;
    }

    if (fs_buf_is_allocated (buf, cluster)) {
#ifdef MUST_SPLIT_PAGES
      pm->parent.ops.page_read (&pm->parent, cluster, fs_factory_page_buffer);
      memcpy (page_data,
          fs_factory_page_buffer +
            (info->cluster_data_seqno & SPLIT_MASK) * EFS_FACTIMAGE_PSIZE,
          EFS_FACTIMAGE_PSIZE);
#else
      pm->parent.ops.page_read (&pm->parent, cluster, page_data);
#endif
      info->cluster_data_seqno++;
      return 0;
    }

    /* Otherwise, keep trying. */
    info->cluster_data_seqno++;
  }
}

/* External interface for extracting images.  The stream_state must start
 * at 0, and will be returned to 0 to indicate the transfer is finished.
 *
 * The state machine is indicated as follows:
 *   0 -> Initial (and final state).  Transmits the first piece of the
 *   header.
 *   1 -> Empty pages of the header page.
 *   2 -> Data pages.
 */
int
fs_fact_get_chunk (struct fs_factimage_read_info *info,
    byte *page_data,
    struct fsb_nand_data *buf)
{
  struct fs_pm_flash_data *pm = (struct fs_pm_flash_data *) buf->gc;

  switch (info->stream_state) {
    case 0:
      return fs_make_factory_header (info, page_data, buf, pm);

#ifdef MUST_SPLIT_PAGES
    case 1:
      return fs_send_factory_adjunct (info, page_data);
#endif

    case 2:
      return fs_output_data (info, page_data, buf, pm);

    default:
      MSG_ERROR ("Invalid stream state: %d", 0, 0, 0);
      return -EINVAL;
  }
}

/***********************************************************************
 * Freemap caching.
 ***********************************************************************/

/* The freemap should always be accessed sequentially, so a single page
 * cache of freemap pages should be sufficient.  We assume that the freemap
 * begins with the page immediately following the factory image header.
 * The code that skips bad blocks is specifically implemented so that the
 * sequential access will be faster. */
struct freemap_cache {
  uint8 *data;

  cluster_id last_offset;
  page_id cur_page;
  cluster_id page_bit_mask;
  fs_device_t dev;
  unsigned block_size;
};

static struct freemap_cache fcache;

/* Initialize the fcache, header_page is the page number where the factory
 * image header is. */
static void
fcache_init (fs_device_t dev, uint8 *buffer, page_id header_page)
{
  int result;

  fcache.data = buffer;
  fcache.cur_page = header_page + 1;
  fcache.last_offset = 0;
  fcache.dev = dev;

  result = fs_flash_read_page (dev, header_page + 1, fcache.data,
      FS_FOP_FACTORY_RECOVERY);
  if (result != FS_DEVICE_OK) {
    FS_ERR_FATAL ("Error reading freemap from factory image", 0, 0, 0);
  }

  fcache.page_bit_mask = (FS_BUFFER_NAND_PAGE_SIZE*8) - 1;
  fcache.block_size = fs_flash_block_size (dev);
}

/* Returns if a given cluster is marked as free.  Can only be called with a
 * cluster number of one greater than the cluster number used on the last
 * call. */
static int
fcache_is_free (cluster_id cluster)
{
  int result;
  unsigned offset, bit;

  ASSERT (cluster == fcache.last_offset + 1);

  if ((cluster & fcache.page_bit_mask) == 0) {
    fcache.cur_page++;

    while ((fcache.cur_page % fcache.block_size) == 0 &&
        fs_flash_bad_block_check (fcache.dev,
          fcache.cur_page / fcache.block_size))
    {
      fcache.cur_page += fcache.block_size;
    }

    result = fs_flash_read_page (fcache.dev, fcache.cur_page, fcache.data,
        FS_FOP_FACTORY_RECOVERY);
    if (result != FS_DEVICE_OK) {
      FS_ERR_FATAL ("Error reading freemap from factory image(2)", 0, 0, 0);
    }
  }

  fcache.last_offset++;

  offset = (cluster & fcache.page_bit_mask) >> 3;
  bit = cluster & 7;

  return (fcache.data[offset] & (1 << bit)) != 0;
}

/***********************************************************************
 * Simple page table manager.
 ***********************************************************************/

/* Management information for a RAM copy of a single page table. */
struct single_page {
  unsigned      table;
        /* Which table is this (0 - forward, 1 - reverse). */

  page_id       location;
        /* Ultimate destination for this page table. */

  page_id       index;
        /* Page index of the first page covered by this table. */

  unsigned      level;
        /* Page table level.  0 indicates superblock (which won't be
         * covered here). */

  unsigned      ref;
        /* Reference count.  Since tables can be shared. */

  uint32        *buf;
        /* Specific page buffer. */
};

/* Table cache information.  One for each level of each of the 3 active
 * tables. */
struct table_cache {
  struct single_page *pg;
        /* Reference to the 'single_page' containing the appropriate
         * buffer. */
};

/* Maximum number of page table levels supported by this code.  Since the
 * level-0 page table is stored directly in the superblock, the page_depth
 * value in the superblock can be 1 greater than this.  3 times this many
 * buffers will be needed. */
#define FS_FACTORY_MAX_LEVELS   (FS_MAX_LEVELS-1)

#define FS_FACTORY_TOTAL_TABLES (FS_FACTORY_MAX_LEVELS*3)

/* Tables used by the simple page table manager.  See spm_table_set for
 * more info. */
#define SPM_TABLE_FORWARD    0
#define SPM_TABLE_REV        1
#define SPM_TABLE_PTABLE_REV 2
#define SPM_NUMBER_TABLES    3

/* Entries for recording the reallocations.  Please see comments on
 * spm_tmp_alloc for more information. */
struct reloc_entry {
  page_id actual;
  page_id desired;
};
typedef struct reloc_entry *reloc_t;

#define RELOCS_PER_PAGE \
   (spm.super->data.page_size / sizeof (struct reloc_entry))

struct simple_pm {
  fs_device_t dev;
  fs_super_t  super;

  page_id     next;
  unsigned    last_level;

  page_id     tmp_next;

  uint32      shifts[FS_MAX_LEVELS];
  uint32      masks[FS_MAX_LEVELS];
  uint32      mask_off[FS_MAX_LEVELS];

  struct single_page tables[FS_FACTORY_TOTAL_TABLES];
  struct table_cache cache[SPM_NUMBER_TABLES][FS_FACTORY_MAX_LEVELS];

  reloc_t     relocs;
  unsigned    reloc_count;

  page_id    *reloc_pages;
  unsigned    reloc_page_count;

  void       *tmp_buf;
};
static struct simple_pm spm;

/* Within a given table, at a given level, determine the index of a
 * particular value within that table. */
#define PTABLE_INDEX(spm, id, level) \
   (((id) & (spm).masks[level]) >> ((spm).shifts[level]))

/* This is a clone of 'init_shifts_and_masks' from the fs_pm_ptable_nand.c
 * code. */
static void
spm_init_shifts_and_masks (void)
{
  int level;
  uint8         shift = 0;
  uint32        mask = FS_PTE_PAGE_MASK;
  uint32        mask_off = 0xFFFFFFFFu;

  for (level = spm.super->data.u.nand.page_depth - 1;
      level >= 0;
      level--)
  {
    mask_off &= ~mask;

    spm.shifts[level] = shift;
    shift += FS_PTE_SHIFT;
    spm.masks[level] = mask;
    mask <<= FS_PTE_SHIFT;
    spm.mask_off[level] = mask_off;
  }
}

/* Initialize the simple page manager. */
void
spm_init (fs_super_t super, fs_device_t dev,
    struct fsb_nand_data *buf,
    int start_buffer)
{
  int i, j;

  spm.dev = dev;
  spm.super = super;

  spm.next = super->data.u.nand.regions[0] * super->data.block_size;
  spm.tmp_next = super->data.u.nand.regions[1] * super->data.block_size;

  spm.last_level = super->data.u.nand.page_depth - 1;

  spm.relocs = (reloc_t) buf->buffers[start_buffer];
  memset (spm.relocs, 0xFF, spm.super->data.page_size);
  spm.reloc_count = 0;
  start_buffer++;

  spm.reloc_pages = (page_id *) buf->buffers[start_buffer];
  spm.reloc_page_count = 0;
  start_buffer++;

  for (i = 0; i < FS_FACTORY_TOTAL_TABLES; i++) {
    spm.tables[i].ref = 0;
    spm.tables[i].buf = (uint32 *) buf->buffers[start_buffer + i];
    if (start_buffer + i >= FS_BUFFER_NUMBER_BUFFERS)
      FS_ERR_FATAL ("Config error: not enough buffers for factory image",
          0, 0, 0);
  }

  /* The tmp_buf is used later, just set to the first of the buffers. */
  spm.tmp_buf = buf->buffers[start_buffer];

  for (i = 0; i < SPM_NUMBER_TABLES; i++) {
    for (j = 0; j < FS_FACTORY_MAX_LEVELS; j++) {
      spm.cache[i][j].pg = NULL;
    }
  }

  spm_init_shifts_and_masks ();
}

/* Allocate a page for use by the simple page manager. */
static page_id
spm_alloc (void)
{
  page_id res;
  int result;

  if ((spm.next & ~spm.super->block_mask) == 0) {
    /* Skip any necessary bad blocks. */
    while (fs_flash_bad_block_check (spm.dev,
          spm.next >> spm.super->block_shift))
    {
      /* It isn't necessary to mark these as bad at this time, since the
       * garbage collector will notice them as still in state 'Unknown' and
       * redo the bad block check later. */
      spm.next += spm.super->data.block_size;
    }
    if (spm.next >= (spm.tmp_next & spm.super->block_mask))
      FS_ERR_FATAL ("Not enough space to compute page tables (1)", 0, 0, 0);

    /* Erase this block. */
    result = fs_flash_erase_block (spm.dev,
        spm.next / spm.super->data.block_size, FS_FOP_FACTORY_RECOVERY);
    if (result != FS_DEVICE_OK) {
      FS_ERR_FATAL ("Erase failure in ptable region", 0, 0, 0);
    }
  }

  res = spm.next;
  spm.next++;
  /* XXX: Do a bounds check, in case there are too many bad blocks. */

  return res;
}

/* Allocate, and record, a temporary page.  Because the devices require all
 * pages within a block to be written in order, write the pages in the
 * order we get them in the temporary area, recording this location, and
 * then move them all once we have computed all of the data. */
static page_id
spm_tmp_alloc (void)
{
  page_id res;
  int result;

  if ((spm.tmp_next & ~spm.super->block_mask) == 0) {
    spm.tmp_next -= spm.super->data.block_size;
    if (spm.tmp_next <= (spm.next & spm.super->block_mask))
      FS_ERR_FATAL ("Not enough space to compute page tables (2)", 0, 0, 0);
    while (fs_flash_bad_block_check (spm.dev,
          spm.tmp_next >> spm.super->block_shift))
    {
      spm.tmp_next -= spm.super->data.block_size;
      if (spm.tmp_next <= (spm.next & spm.super->block_mask))
        FS_ERR_FATAL ("Not enough space to compute page tables (3)", 0, 0, 0);
    }

    /* Erase this block. */
    result = fs_flash_erase_block (spm.dev,
        spm.tmp_next / spm.super->data.block_size, FS_FOP_FACTORY_RECOVERY);
    if (result != FS_DEVICE_OK) {
      FS_ERR_FATAL ("Erase failure (2) in ptable region", 0, 0, 0);
    }
  }

  res = spm.tmp_next;

  /* Advance.  If it goes off of the end of the block, wrap it back to the
   * beginning, and the next time this function is called, it will be moved
   * back to a previous block. */
  spm.tmp_next = ((spm.tmp_next & spm.super->block_mask) |
      ((spm.tmp_next + 1) & ~spm.super->block_mask));

  return res;
}

/* Flush relocation page. */
static void
spm_reloc_flush (void)
{
  page_id rel_page;
  int result;

  rel_page = spm_tmp_alloc ();
  result = fs_flash_write_page (spm.dev, rel_page, spm.relocs,
      FS_FOP_FACTORY_RECOVERY);
  if (result != FS_DEVICE_OK) {
    FS_ERR_FATAL ("Unable to write page during recovery (2)", 0, 0, 0);
  }

  if (spm.reloc_page_count >=
      spm.super->data.page_size / sizeof (page_id))
  {
    /* If there is enough data that this happens, we need to find another
     * place or way to store these.  On a 512-byte device, this value is
     * sufficient to handle a 256MB flash allowing for a few blocks to go
     * bad.  If we encounter larger 512-byte devices, this will have to be
     * changed. */
    FS_ERR_FATAL ("Too many reloc pages", 0, 0, 0);
  }
  /* Record this. */
  spm.reloc_pages[spm.reloc_page_count++] = rel_page;

  // printf ("Reloc: 0x%05x\n", rel_page);
  spm.reloc_count = 0;
  memset (spm.relocs, 0xFF, spm.super->data.page_size);
}

/* Record the movement of a relocated page. */
static void
spm_record_location (page_id actual, page_id desired)
{
  if (spm.reloc_count >= RELOCS_PER_PAGE) {
    spm_reloc_flush ();
  }

  spm.relocs[spm.reloc_count].actual = actual;
  spm.relocs[spm.reloc_count].desired = desired;
  spm.reloc_count++;
}

/* Flush out a single page entry.  Writes the page into a temporary
 * location, and the pages will be copied later. */
static void
spm_flush (struct single_page *pg)
{
  int result;
  page_id real_dest;

  real_dest = spm_tmp_alloc ();
  // printf ("W 0x%05x (at 0x%05x)\n", pg->location, real_dest);
  result = fs_flash_write_page (spm.dev, real_dest, pg->buf,
      FS_FOP_FACTORY_RECOVERY);
  if (result != FS_DEVICE_OK) {
    FS_ERR_FATAL ("Unable to write page table during recovery", 0, 0, 0);
  }
  spm_record_location (real_dest, pg->location);
}

/* Update a single entry in the given page table.  The table indexes are as
 * follows:
 *   0 - Forward table for data region.
 *   1 - Reverse table for data region.
 *   2 - Reverse table for ptable region.
 * Each table is assumed to be written consecutively, from the start.  The
 * higher level tables from the second two may overlap. */
static void
spm_table_set (uint32 key, uint32 value, unsigned table, unsigned level)
{
  uint32 table_index = key & spm.mask_off[level];
  uint32 true_table = (table == SPM_TABLE_FORWARD) ? FS_PTABLE : FS_RTABLE;
  struct single_page *pg;
  int i;

  if (level == 0) {
    spm.super->data.u.nand.tables[true_table]
      [PTABLE_INDEX (spm, key, level)] = value;
    return;
  }

  pg = spm.cache[table][level-1].pg;

  /* Find the appropriate entry. */
  if (pg == NULL ||
      pg->index != table_index)
  {
    if (pg != NULL) {
      if (pg->ref == 1) {
        spm_flush (pg);
        pg->ref = 0;
      } else {
        pg->ref--;
      }
    }

    /* First, scan for an entry that matches. */
    for (i = 0; i < FS_FACTORY_TOTAL_TABLES; i++) {
      if (spm.tables[i].table == true_table &&
          spm.tables[i].index == table_index &&
          spm.tables[i].level == level)
        break;
    }

    if (i < FS_FACTORY_TOTAL_TABLES) {
      spm.tables[i].ref++;
      pg = &spm.tables[i];
    } else {
      /* Otherwise, just find an empty table. */
      for (i = 0; i < FS_FACTORY_TOTAL_TABLES; i++) {
        if (spm.tables[i].ref == 0)
          break;
      }

      if (i == FS_FACTORY_TOTAL_TABLES)
        FS_ERR_FATAL ("Internal error, not enough tables available", 0, 0, 0);
      pg = &spm.tables[i];

      pg->ref = 1;
      pg->table = true_table;
      pg->index = table_index;
      pg->level = level;
      pg->location = spm_alloc ();

      memset (pg->buf, 0xFF, spm.super->data.page_size);

      /* Record this in the previous table.  Note this is recursive. */
      spm_table_set (key & spm.mask_off[level], pg->location,
          table, level - 1);

      /* Also, record a reverse entry. */
      spm_table_set (pg->location,
          FS_RMAP_PAGE_TABLE (true_table, level, table_index),
          SPM_TABLE_PTABLE_REV,
          spm.last_level);

    }

    spm.cache[table][level-1].pg = pg;
  }

  pg->buf[PTABLE_INDEX (spm, key, level)] = value;
}

void
spm_set_reverse (page_id page, cluster_id cluster)
{
  spm_table_set (page, cluster, SPM_TABLE_REV, spm.last_level);
}

void
spm_set_forward (cluster_id cluster, page_id page)
{
  spm_table_set (cluster, page, SPM_TABLE_FORWARD, spm.last_level);
}

/* Go through the entire page table data (relocations), and copy the data,
 * in destination order, into the proper place.
 */
static void
spm_move_data (void)
{
  page_id dest;
  unsigned reloc_page = 0;
  unsigned reloc_index = 0;
  unsigned wrap_count;
  reloc_t  reloc;
  int result;

  /* Loop through all of the physical pages. */
  for (dest = spm.super->data.u.nand.regions[0] * spm.super->data.block_size;
      dest < spm.next;
      dest++)
  {
    if (((dest & ~spm.super->block_mask) == 0) &&
        fs_flash_bad_block_check (spm.dev, spm.next >> spm.super->block_shift))
    {
      /* Skip this block.  Set to the last page, and let the increment in
       * the loop do the advance. */
      dest |= ~spm.super->block_mask;
      continue;
    }
    wrap_count = 0;

    /* Scan until we find the the proper reloc entry.  Most will already be
     * the next entry, but out of order data will require that we scan
     * through the entire table. */
    while (1) {
      if (reloc_index == 0) {
        result = fs_flash_read_page (spm.dev,
            spm.reloc_pages[reloc_page], spm.relocs,
            FS_FOP_FACTORY_RECOVERY);
        if (result != FS_DEVICE_OK)
          FS_ERR_FATAL ("Unable to read page in relocate", 0, 0, 0);
      }

      reloc = &spm.relocs[reloc_index];

      reloc_index++;
      if (reloc_index >= RELOCS_PER_PAGE) {
        reloc_index = 0;
        reloc_page++;
        if (reloc_page >= spm.reloc_page_count) {
          reloc_page = 0;
          wrap_count++;

          /* If the scan wraps around several times without finding the
           * desired page, the page cannot be found.  This indicates
           * something wrong with the previously run code, and trying to
           * continue would result in filesystem corruption. */
          if (wrap_count == 3)
            FS_ERR_FATAL ("Unable to find destination page", 0, 0, 0);
        }
      }

      if (reloc->desired == dest)
        break;
    }

    result = fs_flash_read_page (spm.dev, reloc->actual, spm.tmp_buf,
        FS_FOP_FACTORY_RECOVERY);
    if (result != FS_DEVICE_OK)
      FS_ERR_FATAL ("Unable to read reloc page", 0, 0, 0);
    result = fs_flash_write_page (spm.dev, reloc->desired, spm.tmp_buf,
        FS_FOP_FACTORY_RECOVERY);
    if (result != FS_DEVICE_OK)
      FS_ERR_FATAL ("Unable to write reloc page", 0, 0, 0);
  }
}

void
spm_full_flush (void)
{
  int i;

  for (i = 0; i < FS_FACTORY_TOTAL_TABLES; i++) {
    if (spm.tables[i].ref > 0)
      spm_flush (&spm.tables[i]);
  }

  if (spm.reloc_count > 0)
    spm_reloc_flush ();

  spm_move_data ();
}

/***********************************************************************
 * Factory startup code.
 ***********************************************************************/

/* Scan for the presence of a factory image.  The image must start in the
 * first non-bad block of the EFS partition and occupy consecutive
 * good-blocks.  Returns INVALID_PAGE_ID if there is no factory image, or
 * the page number of the start of the image. */
static page_id
factory_scan (fs_device_t dev, uint8 *buffer)
{
  int block_size = fs_flash_block_size (dev);
  page_id page;
  int result;
  factory_header_t head;

  page = 0;
  while (1) {
    if (fs_flash_bad_block_check (dev, page / block_size) != FS_DEVICE_OK) {
      page += block_size;
      continue;
    }

    result = fs_flash_read_page (dev, page, buffer, FS_FOP_FACTORY_RECOVERY);
    if (result != FS_DEVICE_OK)
      return INVALID_PAGE_ID;

    head = (factory_header_t) buffer;

    if (head->magic1 != EFS_FACTIMAGE_MAGIC1 ||
        head->magic2 != EFS_FACTIMAGE_MAGIC2)
      return INVALID_PAGE_ID;

    /* Separate checks with more helpful logs. */
    if (head->fact_version != EFS_FACTIMAGE_VERSION) {
      MSG_HIGH ("Factimage version mismatch: %d, %d",
          head->fact_version, EFS_FACTIMAGE_VERSION, 0);
      return INVALID_PAGE_ID;
    }

    if (head->version != FS_SUPER_VERSION_NAND) {
      MSG_HIGH ("Factimage super version mismatch: %d, %d",
          head->version, FS_SUPER_VERSION_NAND, 0);
      return INVALID_PAGE_ID;
    }

    if (head->page_size != (unsigned) fs_flash_page_size (dev) ||
        head->block_size != (unsigned) fs_flash_block_size (dev) ||
        head->block_count != (unsigned) fs_flash_block_count (dev))
    {
      MSG_HIGH ("Factimage device layout differs", 0, 0, 0);
      return INVALID_PAGE_ID;
    }

    /* At this point, the factory image appears to be valid. */
    return page;
  }
}

/* Output the superblock into the appropriate region. */
static int
fs_fact_output_superblock (fs_super_t super)
{
  uint32 block, eblock;
  page_id page, good_page;
  int result;
  unsigned bit;

  block = super->data.u.nand.regions[2];
  eblock = super->data.u.nand.regions[3];
  page = block << super->block_shift;

  /* Scan all of the blocks in the log region, and build the freemap. */
  super->data.u.nand.logr_badmap = 0;

  good_page = INVALID_PAGE_ID;
  for (bit = 1;
      block < eblock;
      block++, page += super->data.block_size, bit <<= 1)
  {
    if (fs_flash_bad_block_check (super->dev, block)) {
      super->data.u.nand.logr_badmap |= bit;
    } else if (good_page == INVALID_PAGE_ID) {
      good_page = page;
    }
  }

  if (good_page == INVALID_PAGE_ID)
    FS_ERR_FATAL ("No good blocks in log region", 0, 0, 0);

  result = fs_flash_erase_block (super->dev,
      good_page >> super->block_shift, FS_FOP_FACTORY_RECOVERY);
  if (result != FS_DEVICE_OK)
    FS_ERR_FATAL ("Error erasing block in log region", 0, 0, 0);

  super->data.log_head = good_page + 1;

  /* Add CRC. */
  super->data.crc32 = crc_30_calc ((byte *) &super->data,
      sizeof (struct superblock_data) * 8 - 32);

  result = fs_flash_write_page (super->dev, good_page, &super->data,
      FS_FOP_FACTORY_RECOVERY);
  if (result != FS_DEVICE_OK)
    FS_ERR_FATAL ("Error writing page in log region", 0, 0, 0);

  return 0;
}

/* Use the external fs buffer.  The buffers are used below, with buffer[0]
 * being used to scan for the factory header, buffer[1] used by the fcache,
 * and buffers 2 and on used by the simple page manager. */
extern fsb_t fs_buf;

int
fs_fact_startup_check (fs_super_t super, fs_device_t dev)
{
  struct fsb_nand_data *buf = (struct fsb_nand_data *) fs_buf;
  uint8 *buffer = buf->buffers[0];
  page_id fact_start;
  factory_header_t head = (factory_header_t) buffer;
  page_id page;
  cluster_id cluster;
  int block_size;
  int i;

  fact_start = factory_scan (dev, buffer);

  if (fact_start == INVALID_PAGE_ID)
    return -1;

  fcache_init (dev, buf->buffers[1], fact_start);

  fs_pm_super_invalidate_superblocks ();

  super->data.version = FS_SUPER_VERSION_NAND;
  fs_super_create_superblock (super);

  /* Initialize the simple page manager. */
  spm_init (super, dev, buf, 2);

  block_size = fs_flash_block_size (dev);

  /* Any blocks that preceed the factory header were detected as bad, so
   * mark them as such in the page tables. */
  for (page = 0; page < fact_start; page += block_size) {
    spm_set_reverse (page, FS_RMAP_BLOCK_BAD);
  }

  /* The factory header itself is just garbage, as far as the filesystem is
   * concerned. */
  spm_set_reverse (page, FS_RMAP_PAGE_GARBAGE);

  page = fact_start + 1;
  cluster = 1;

  while (cluster < head->space_limit) {
    /* When at a block boundary, make sure we skip to the next good block. */
    if ((page & ~super->block_mask) == 0) {
      while (fs_flash_bad_block_check (dev, page >> super->block_shift)) {
        spm_set_reverse (page, FS_RMAP_BLOCK_BAD);
        page += super->data.block_size;
      }
    }

    /* Advance the cluster number to the next allocated cluster (from the
     * freemap. */
    while (cluster < head->space_limit &&
        fcache_is_free (cluster))
      cluster++;

    if (cluster < head->space_limit) {
      spm_set_forward (cluster, page);
      spm_set_reverse (page, cluster);
      cluster++;
      page++;
    }
  }

  /* Flush simple page table manager. */
  spm_full_flush ();

  /* Finalize superblock and write it out. */
  super->data.alloc_next[0] = page;
  super->data.gc_next[0] = (page & super->block_mask) + super->data.block_size;

  super->data.alloc_next[1] = spm.next;
  super->data.gc_next[1] = (spm.next & super->block_mask) +
    super->data.block_size;

  for (i = 0; i < FS_UPPER_DATA_COUNT; i++) {
    super->data.upper_data[i] = head->upper_data[i];
  }

  MSG_HIGH ("EFS found and unpacked a Canonical EFS (CEFS) image.", 0,0,0);
  return fs_fact_output_superblock (super);
}