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


/*=========================================================================== 
 
 fs_pm_super.c 
 
 Superblock management 
 
 Copyright (c) 2002, 2003, 2004, 2005, 2006 by QUALCOMM Incorporated. 
 All Rights Reserved. 
 
 =========================================================================== 
 
                        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_super.c#16 $ $DateTime: 2006/10/12 11:25:53 $ $Author: davidb $ 
 
when          who     what, where, why 
--------      ---     ------------------------------------------------------ 
2006-10-09    dlb     Fix superblock version on fresh starts. 
2006-09-11    dlb     Use flash driver wrappers. 
2006-09-06    sh      Lint cleanup 
2006-08-31    dlb     Add db gid upgrade support. 
2006-08-31    dlb     Only upgrade version after upgrade is finished. 
2006-08-17    dlb     Add log CRC upgrade support. 
2006-08-07    dlb     Rename log2 to avoid C library conflicts. 
2006-04-18    dlb     Add forward-updates of superblock. 
2006-03-31    sh      Lint fixes. 
2006-02-08    dlb     Fix erroneous dependency on write style. 
2006-01-27    dlb     Use encapsulated write style query. 
2006-01-17    dlb     Support multiple NOR write styles. 
2005-12-14    dlb     Extra asserts for configuration sanity. 
2005-10-30     sh     Lint cleanup. 
2005-04-20    dlb     Support 2k pages. 
2005-02-18    dlb     Lint fix. 
2005-01-17    dlb     Cleanup from code review. 
2005-01-04    dlb     Update copyright line. 
2004-12-27    dlb     Replace factory image code. 
2004-10-15    dlb     Update copyright header. 
2004-10-08    dlb     Repair NOR and NAND fresh start. 
2004-10-07    dlb     Whitespace cleanup. 
2003-11-17    dlb     Check partition size on startup. 
2003-11-13    dlb     Reduce R0 region to 0. 
2003-09-09    dlb     Factory image works with bitmap and tree. 
2003-06-18     gr     Fixed a bug in the factory start code. Also cleaned up 
                      the formatting of some code. 
2003-06-17    adm     Featurized factory start code. 
2003-06-12    adm     Add support for factory start. 
2003-05-03     gr     Added support for invalidating all superblocks. This 
                      forces a fresh start on the next reboot. 
2003-03-21     gr     Code cleanup. 
2003-03-17     gr     Added a crc to the superblock. 
2003-03-07    dlb     Initialize the superblock tables. 
2002-08-08    drh     Created by dlb.  Added history header.  Change #if 1 
                      surrounding printf() calls to #if 0 
===========================================================================*/ 
 
 
#include  
#include  
#include  
 
#include "customer.h" 
#include "fs_sys_types.h" 
#include "fs_err.h" 
 
#include "fs_upgrade.h" 
 
#include "fs_pm_super.h" 
#include "fs_device.h" 
#include "assert.h" 
 
#include "crc.h" 
 
#ifdef FEATURE_EFS_NAND_FACTORY_START 
 #include "fs_factory.h" 
#endif 
 
/* By setting this value in a debugger, a fresh start can be forced.  To do 
 * this from jtag, set a breakpoint at the start of fs_super_init, then set 
 * fs_fresh_start to a non-zero value, and continue.  EFS will startup 
 * blowing away the contents of the flash. */ 
int fs_fresh_start = 0; 
 
/* Superblock and flash memory layout. 
 * 
 * Flash (at least NAND) is divided into a set of one or more regions.  The 
 * first region (region 0) is always used to store user data, as well as 
 * superblocks and log pages. 
 * 
 * This region 0 contains 4 copies of the superblock.  One of these will 
 * always be the newest version. 
 * 
 * To make finding the superblocks easier, the locations are determined as 
 * if there were no regions.  If the device is inefficient, and the forth 
 * superblock doesn't fit in the last region, we will only use three. 
 * More likely, though, would be that there would only be one region in 
 * this case. 
 * 
 * The superblock code also computes the initial region boundaries when 
 * there are no superblocks present.  Once defined, the regions will be 
 * static for the remainder of the life of the filesystem on that device. 
 * The relative sizes of the regions should be determined through 
 * profiling. */ 
 
/* A pointer to the superblock data structure. Used for master reset 
 * functionality. 
 * 
 * XXX: This needs to be removed to support multiple NAND 
 * devices/partitions. 
 */ 
static fs_super_t sb_ptr; 
 
/* Return the smallest m, such taht 2^m >= value. */ 
static int fs_log2 (int value); 
 
/* Compute the offsets where we would expect to find superblocks.  Bad 
 * blocks may cause these to be advances, but this gives us our initial 
 * guesses. */ 
/* static void compute_initial_superblock_offsets (fs_super_t super); */ 
 
/* Scan through the device looking for the most recent superblock.  Returns 
 * the page this superblock is located in, or INVALID_PAGE_ID if there was 
 * no superblock found. */ 
static page_id find_superblock (fs_super_t super); 
 
/* Determine if this superblock version is compatible.  Returns non-zero if 
 * it is. */ 
static int superblock_version_compatible (int is_nand, uint32 version); 
 
static void compute_page_tables (fs_super_t super); 
 
/* Initial scan for the superblock.  This is done once on powerup.  It also 
 * will compute and write out a clean superblock if one could not be found. 
 */ 
void 
fs_super_init ( 
    fs_super_t  super, 
    fs_device_t dev, 
    int *fresh_start) 
{ 
  int block_size, page_size; 
  int offset; 
 
  sb_ptr = super; 
 
  block_size = fs_flash_block_size (dev); 
  page_size = fs_flash_page_size (dev); 
 
  /* Make sure the compiled page size matches the device page size. */ 
  if (page_size != EFS_PAGE_SIZE) { 
    FS_ERR_FATAL ("Device page size different than configured", 0, 0 ,0); 
  } 
 
  /* Assert that our size is correct, just a coding sanity check. */ 
  if (sizeof (struct superblock_data) != EFS_PAGE_SIZE) { 
    FS_ERR_FATAL ("Superblock does not match page size: super=%d, page=%d", 
        sizeof (struct superblock_data), EFS_PAGE_SIZE, 0); 
  } 
 
  /* EFS2 uses shifts for block and page arithmetic, so make sure they are 
   * both powers of two. */ 
  ASSERT ((block_size & (block_size - 1)) == 0); 
  ASSERT ((page_size & (page_size - 1)) == 0); 
 
  /* Initialize the obvious fields of the superblock data. */ 
  super->block_shift = fs_log2 (block_size); 
  super->block_mask = 0xFFFFFFFF << (super->block_shift); 
  super->dev = dev; 
 
  /* compute_initial_superblock_offsets (super); */ 
 
  offset = find_superblock (super); 
 
#ifdef FEATURE_EFS_NAND_FACTORY_START 
  /* If we didn't find a superblock, and we know this is a NAND device 
   * (only NOR allows partial writes), then search for a factory image. */ 
  if (offset == -1 && super->dev->partial_write == NULL) { 
    offset = fs_fact_startup_check (super, dev); 
  } 
#endif 
 
  /* Also, if someone with a debugger is requesting a fresh start, do so. 
   */ 
  if (fs_fresh_start) 
    offset = -1; 
 
  /* printf ("Use superblock at: %d (%d)\n", offset, 
      super->segment_offsets[offset]); */ 
 
  /* If we didn't find any superblocks, create an initial one. */ 
  if (offset == -1) { 
    /* XXX: Fix to pass argument to support multiple devices. */ 
 
    /* Always use the latest version when creating a superblock.  Don't 
     * want to ever see an old version in this case. */ 
    super->data.version = super->desired_version; 
 
    fs_pm_super_invalidate_superblocks (); 
    fs_super_create_superblock (super); 
    *fresh_start = 1; 
  } else { 
    /* The NOR specific superblock data was originally initialized to zero, 
     * which happens to be the same as FS_DEVICE_WRITES_SIMPLE.  This check 
     * here allows us to support the other flash devices without having to 
     * bump the superblock version.  Simple devices will continue to work, 
     * with the zero matching the enum. */ 
    if (FS_ISNOR (super) && 
        super->data.u.nor.style != FS_GET_WRITE_STYLE (super->dev)) 
    { 
      /* This fatal error indicates that we have detected a previous 
       * filesystem that didn't have proper support for multi-bit flash 
       * devices.  The filesystem must be wiped manually to create a fresh 
       * filesystem. */ 
      FS_ERR_FATAL ("Cannot transition from incorrect multi-bit support", 
          0, 0, 0); 
    } 
    *fresh_start = 0; 
  } 
 
  super->total_pages = super->data.block_count * super->data.block_size; 
} 
 
/* Compute the smallest m, such that 2^m >= value.  This could be 
 * optimized, but isn't done too much. */ 
static int 
fs_log2 (int value) 
{ 
  int m = 0; 
  while (1) { 
    if ((1 << m) >= value) 
      return m; 
    m++; 
  } 
} 
 
/* Determine if the first argument is greater or less than the second, in 
 * terms of "age" which is a uint16. */ 
static int 
age_less (uint16 a, uint16 b) 
{ 
  if (a < 16384 && b >= 49152) 
    return 0; 
  if (b < 16384 && a >= 49152) 
    return 1; 
  return a < b; 
} 
 
/* Return 1 if the superblock passed in is valid and 0 if not. 
 */ 
static int 
check_superblock (struct superblock_data *sd, int is_nand, 
    int block_count, int block_size) 
{ 
  if (sd == NULL 
      || sd->magic1 != FS_SUPER_MAGIC1 
      || sd->magic2 != FS_SUPER_MAGIC2 
      || !superblock_version_compatible (is_nand, sd->version)) 
    return 0; 
 
  if (sd->block_size != (unsigned) block_size || 
      sd->block_count != (unsigned) block_count) 
    return 0; 
 
  /* Check 30 bit crc. */ 
  if (crc_30_calc ((byte *) sd, sizeof(struct superblock_data) * 8 - 32) 
      != sd->crc32) 
    return 0; 
  else 
    return 1; 
} 
 
/* Scan the appropriate region for a superblock.  For each valid superblock 
 * we find, look at the age field.  Determine from this field which 
 * superblock is the most recent. */ 
static page_id 
find_real_superblock (fs_super_t super, int is_nand) 
{ 
  int result; 
  int block_size = fs_flash_block_size (super->dev); 
  int block_count = fs_flash_block_count (super->dev); 
  int total_pages = block_size * block_count; 
  int page; 
  struct superblock_data *sd; 
  uint16        max_age  = 0; 
  page_id       max_page = INVALID_PAGE_ID; 
 
  /* Scan the entire NOR flash for a superblock, but only the last several 
   * blocks of a NAND flash. */ 
 
  if (is_nand) 
    page = total_pages - (FS_NAND_LOG_REGION_BLOCKS << super->block_shift); 
  else 
    page = 0; 
 
  for (; page < total_pages; page += block_size) { 
    if (is_nand) { 
      /* For NAND, we must read each page out. */ 
      result = fs_flash_read_page (super->dev, page, 
          (void *) &super->data, 
          FS_FOP_SUPER); 
      if (result == FS_DEVICE_OK) 
        sd = &super->data; 
      else 
        sd = NULL; 
    } else { 
      /* NOR lets us have pointers directly into the flash. */ 
      sd = fs_flash_read_pointer (super->dev, page); 
    } 
 
    /* XXX: Verify checksum. */ 
    if (check_superblock (sd, is_nand, block_count, block_size)) 
    { 
      if (max_page == INVALID_PAGE_ID 
          || (age_less (max_age, sd->age))) 
      { 
        max_age = sd->age; 
        max_page = page; 
      } 
    } 
  } 
 
  if (max_page != INVALID_PAGE_ID) { 
    result = fs_flash_read_page (super->dev, max_page, 
        (void *) &super->data, FS_FOP_SUPER); 
    if (result != 0) { 
      FS_ERR_FATAL ("Unable to read contents of superblock", 0, 0, 0); 
    } 
 
    /* Remember the desired superblock version number.  fs_super_update() 
     * will start using this once any updates are finished. */ 
    if (is_nand) 
      super->desired_version = FS_SUPER_VERSION_NAND; 
    else 
      super->desired_version = FS_SUPER_VERSION_NOR; 
  } else { 
    /* Set the version field in the superblock so that the 
     * create_superblocks can create the correct type. */ 
    if (is_nand) 
      super->data.version = FS_SUPER_VERSION_NAND; 
    else 
      super->data.version = FS_SUPER_VERSION_NOR; 
    super->desired_version = super->data.version; 
  } 
 
  return max_page; 
} 
 
/* Scan each block of the appropriate region for superblock. */ 
static page_id 
find_superblock (fs_super_t super) 
{ 
  int is_nand = (super->dev->partial_write == NULL); 
 
  return find_real_superblock (super, is_nand); 
} 
 
/* Create the initial superblock.  It is always written in the first 
 * segment.  We assume that the device is unitialized.  The 
 * find_superblocks function will have previously set the 
 * super->data.version field to the correct version type to use (either 
 * NAND or NOR).  We will base some of our initialization decisions on this 
 * information. */ 
void 
fs_super_create_superblock (fs_super_t super) 
{ 
  int i; 
  uint32 old_version = super->data.version; 
 
  memset (&super->data, 0, sizeof (struct superblock_data)); 
 
  /* Fill in all of the obvious information for the superblock. */ 
  super->data.page_header = 0x53000000; 
  super->data.version = old_version; 
  super->data.age = 0; 
  super->data.magic1 = FS_SUPER_MAGIC1; 
  super->data.magic2 = FS_SUPER_MAGIC2; 
  super->data.block_size = fs_flash_block_size (super->dev); 
  super->data.page_size = fs_flash_page_size (super->dev); 
  super->data.block_count = fs_flash_block_count (super->dev); 
 
  /* Clear out the tables. */ 
  super->data.log_head = INVALID_PAGE_ID; 
 
  for (i = 0; i < FS_UPPER_DATA_COUNT; i++) { 
    super->data.upper_data[i] = INVALID_PAGE_ID; 
  } 
 
  if (FS_ISNOR (super)) { 
    super->data.u.nor.style = FS_GET_WRITE_STYLE (super->dev); 
  } else { 
    /* Compute the NAND specific information. */ 
    compute_page_tables (super); 
 
    for (i = 0; i < SUPERBLOCK_PAGE_ENTRIES; i++) { 
      super->data.u.nand.tables[0][i] = INVALID_PAGE_ID; 
      super->data.u.nand.tables[1][i] = INVALID_PAGE_ID; 
    } 
  } 
} 
 
/* Compute the sizes of the page tables. */ 
/* XXX: Handle the case where the last chunk of nodes doesn't fit in the 
 * superblock. */ 
/* XXX: Do we need to compute based on potential blocks, or is this number 
 * adequate. */ 
static void 
compute_page_tables (fs_super_t super) 
{ 
  int low_shift; 
  long page_number; 
  int levels = 0; 
  long page_total, page_tmp, tmp; 
 
  super->data.u.nand.nodes_per_page = super->data.page_size / 4; 
  low_shift = fs_log2 (super->data.u.nand.nodes_per_page); 
 
  /* Starting with the last possible page. */ 
  page_number = super->data.block_count * super->data.block_size - 1; 
 
  while (page_number + 1 > SUPERBLOCK_PAGE_ENTRIES) { 
    levels++; 
    page_number >>= low_shift; 
  } 
 
  levels++; 
 
  super->data.u.nand.page_depth = levels; 
  super->data.u.nand.super_nodes = page_number + 1; 
 
  /* Compute the total number of pages needed for page tables. */ 
  page_total = 0; 
  page_tmp = super->data.block_count * super->data.block_size; 
 
  while (page_tmp >= SUPERBLOCK_PAGE_ENTRIES) { 
    page_tmp = (page_tmp + super->data.u.nand.nodes_per_page - 1) / 
      super->data.u.nand.nodes_per_page; 
    page_total += page_tmp; 
  } 
  page_total *= 2; 
 
  /* Compute the boundaries of the various regions. */ 
  super->data.u.nand.num_regions = FS_NAND_REGION_COUNT; 
 
  /* The log ends at the end of the device. */ 
  super->data.u.nand.regions[3] = super->data.block_count; 
 
  /* The log and superblocks are the last region. */ 
  tmp = FS_NAND_LOG_REGION_BLOCKS; 
 
  super->data.u.nand.regions[2] = super->data.u.nand.regions[3] - tmp; 
 
  /* R0 region is a placeholder for possible future use. */ 
  tmp = 0; 
  super->data.u.nand.regions[1] = super->data.u.nand.regions[2] - tmp; 
 
  /* Compute space needed for the page tables.  The multiplication factor 
   * here is mostly a performance tradeoff. */ 
  tmp = ((page_total + super->data.block_size - 1) 
    / super->data.block_size) * 2 + 3; 
 
  super->data.u.nand.regions[0] = super->data.u.nand.regions[1] - tmp; 
 
  if (super->segment_offsets[SUPER_SEGMENTS - 1] 
      >= super->data.u.nand.regions[0]) { 
    FS_ERR_FATAL ("Regions are too large to be useful here.", 0, 0, 0); 
  } 
 
#ifdef FS_UNIT_TEST_NOT 
#error code not present 
#endif 
} 
 
/* Do the actual update of the superblock. */ 
void 
fs_super_update ( 
    fs_super_t  super, 
    page_id page) 
{ 
  int result; 
 
  super->data.age++; 
 
  /* If all of the updates are finished, bounce us to the newest version. 
   */ 
  if (super->data.version != super->desired_version && 
      fs_upgrades_all_finished()) 
  { 
    super->data.version = super->desired_version; 
  } 
 
  /* Compute and store a 30-bit CRC. */ 
  super->data.crc32 = crc_30_calc ((byte *) &super->data, 
      sizeof (struct superblock_data) * 8 - 32); 
 
  result = fs_flash_write_page (super->dev, page, (void *) &super->data, 
      FS_FOP_SUPER); 
  /* XXX: Error recovery. */ 
  if (result != FS_DEVICE_DONE) 
    FS_ERR_FATAL ("Unable to write updated superblock", 0, 0, 0); 
} 
 
/* 
 * Erase all the blocks that contain superblocks. NAND-specific. 
 */ 
static void 
erase_nand_superblocks (fs_super_t super) 
{ 
  block_id block; 
  block_id block_count = fs_flash_block_count (super->dev); 
 
  block = block_count - FS_NAND_LOG_REGION_BLOCKS; 
  for (; block < block_count; block++) { 
    if (!fs_flash_bad_block_check (super->dev, block)) 
      fs_flash_erase_block (super->dev, block, FS_FOP_UNKNOWN); 
  } 
} 
 
/* 
 * Set the magic number in all superblocks to zero. NOR-specific. 
 */ 
static void 
invalidate_nor_superblocks (fs_super_t super) 
{ 
  int page; 
  block_id block; 
  int block_size = fs_flash_block_size (super->dev); 
  int block_count = fs_flash_block_count (super->dev); 
  int page_size = fs_flash_page_size (super->dev); 
  int total_pages = block_size * block_count; 
  int magic1_offset = FPOS (struct fs_super_data, data.magic1); 
  int magic_reset_val = 0; 
 
  page = 0; 
  block = 0; 
  for (; page < total_pages; page += block_size, block++) { 
    /* Invalidate the superblock, as well as the last word of the last page 
     * in the block (used for the ptables). */ 
    fs_flash_partial_write (super->dev, page, &magic_reset_val, 
        magic1_offset, 4); 
    fs_flash_partial_write (super->dev, page + block_size - 1, 
        &magic_reset_val, page_size - 4, 4); 
  } 
} 
 
/* Invalidate all the superblocks in the flash. On NOR flash, we do this by 
 * setting one of the magic numbers in the superblock to zero. On NAND, we 
 * erase all the blocks that contain superblocks. 
 */ 
void 
fs_pm_super_invalidate_superblocks (void) 
{ 
  int is_nand; 
 
  if (sb_ptr) { 
    is_nand = (sb_ptr->dev->partial_write == NULL); 
    if (is_nand) 
      erase_nand_superblocks (sb_ptr); 
    else 
      invalidate_nor_superblocks (sb_ptr); 
  } 
  else { 
    FS_ERR_FATAL ("Invalid superblock pointer, master reset failed", 0, 0, 0); 
  } 
} 
 
/* Determine if the detected superblock is compatible with this filesystem 
 * code.  'find_real_superblock' will update the version field to the current 
 * version.  This handles the case where there is a simple forward 
 * compatibility issue, such as new log codes.  If any effort is needed to 
 * convert data structures that will need to be added. */ 
static int 
superblock_version_compatible (int is_nand, uint32 version) 
{ 
  if (version < FS_SUPER_LOWEST_ENCODED_VERSION) { 
    /* No versions before the encoding were supported. */ 
    return 0; 
  } 
 
  /* If the flash type mismatches, something is rather wrong. */ 
  if (is_nand != FS_SUPER_VERSION_IS_NAND (version)) { 
    FS_ERR_FATAL ("Flash changed major types.", 0, 0, 0); 
  } 
 
  /* Now figure out what versions we support.  This case statement 
   * intentionally has fall-throughs, since older versions will require 
   * multiple upgrades.  The cases should be listed oldest first. */ 
  switch (FS_SUPER_VERSIONOF (version)) { 
 
    case FS_SUPER_VERSION_QANDR: 
      /* Long names added since then.  This is a forward-compatible change. */ 
    case FS_SUPER_VERSION_LONGNAME: 
      /* Log CRC added.  This is a forward-compatible change. */ 
 
      /* Upgrades from these versions require in-place log updates. */ 
      fs_upgrade_add (FS_UPGRADE_LOG_ZERO_AFTER_CRC); 
      //lint -fallthrough 
 
    case FS_SUPER_VERSION_LOGCRC: 
      /* Fix GID problem in database nodes. */ 
      fs_upgrade_add (FS_UPGRADE_DBGID_FIX); 
      //lint -fallthrough 
 
    case FS_SUPER_VERSION_DBGID: 
      /* Native version. */ 
      break; 
 
    default: 
      /* Unknown previous version (probably newer).  Emit warning, and 
       * indicate it is old. */ 
      FS_ERR ("Unsupported superblock version: 0x%04x", 
          version, 0, 0); 
      return 0; 
  } 
 
  return 1; 
}