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


/****************************************************************************** 
 * fs_ftl.c 
 * 
 * This file implements the NAND Flash Translation Layer to present the NAND 
 *   flash as logical sectors. 
 * 
 * Copyright (C) 2006 Qualcomm, Inc.  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_ftl.c#6 $ 
  $DateTime: 2006/10/12 21:20:47 $ 
  $Author: davidb $ 
 
when         who     what, where, why 
--------     ---     ---------------------------------------------------------- 
2006-10-09   yg      Added device layer and counters support. 
2006-10-02   yg      Add Partition support to FTL. 
2006-09-20   dlb     Lint fixes. 
2006-08-28   yg      Added 2k page device and Cache layer support. 
2006-06-12   yg      Lint fixes. 
2006-06-07   yg      Removed block_count correction 
2006-06-07   yg      Removed MMC stub functions 
2006-06-02   yg      Rename var, release func clean, bad power down review fix 
2006-06-02   sh      Message on ftl_init() 
2006-05-28   sh      Cosmetic changes 
2006-05-24   yg      Changed to use unsigned integers. 
2006-05-21   yg      Cleanup 
2006-04-10   yg      Initial file for FTL layer 
=============================================================================*/ 
 
#include "comdef.h" 
#include "customer.h" 
 
#ifdef FEATURE_EFS_FTL 
 
#include  
#include "flashi.h" 
#include "fs_ftl.h" 
#include "fs_ftl_i.h" 
#include "flash_nand.h" 
#include "crc.h" 
#include "msg.h" 
#include "err.h" 
#include "jzap.h" 
 
#include "fs_ftl_device.h" 
 
// #include "tt_public.h" 
#include "fs_timetest2.h" 
 
#ifndef FS_UNIT_TEST 
/* Some flash driver related data needed for probe */ 
static struct fsi_device_data ftl_priv_device; 
#else 
#  include "utflash.h" 
#endif 
 
#define FTL_PARTITION_NAME "0:FTL" 
 
/* This is the interface externally available from Flash layer */ 
fs_device_t ftl_flash_dev; 
 
/* Buffer to hold the data to write */ 
static byte dev_page_buffer[MAX_DATA_PAGE_SIZE]; 
 
/* An extra page buffer to avoid doing memset everytime */ 
static byte erased_buffer[MAX_DATA_PAGE_SIZE]; 
 
/* 
 *  This is the map to translate logical block to physical block, 
 *  will also have 2 bit generation along with. So its a tradeoff 
 *  between code complexity and memory usage to keep Generation 
 *  in a seperate array. 
 * 
 *  Storage format is: 
 *  GG PPPPPP PPPPPPPP 
 *  G(2)   : 2 bit generation 
 *  P(14)  : 14 bit Physical block number 
 *  Offset : LBN Logical block number 
*/ 
static uint16 lbn_to_phy_blk_map[MAX_BLOCK_COUNT]; 
 
/* 
 *  This is the state of the physical blocks to pick a free block. 
 *  At any given point of time this gives the status of the physical 
 *  block. 
 */ 
static uint8 phy_blk_status[MAX_BLOCK_COUNT]; 
 
/* Just keep track of how many free blocks are available */ 
static uint32 free_count; 
 
/* A running location pointer to pick next free block */ 
static uint32 free_blk_pos; 
 
/* This is the maximum number of "logical sectors", which 
 * are actually physical flash PAGES for 512-byte flash. */ 
static uint32 max_logical_page_cnt; 
 
/* Number of blocks in the FTL area of flash */ 
static uint32 ftl_block_count; 
 
/* FTL page size in bytes */ 
static uint32 ftl_flash_page_size; 
 
/* Number of pages (logical blocks) in one flash block */ 
static uint32 ftl_pages_per_block; 
 
/* In case we want to erase the whole flash, just set this true from T32 */ 
uint32 ftl_fresh_start = 0; 
 
/* This is the number of logical sub pages in a device page 
 * for a 2k page device we find 4, 512 byte logical pages. 
 */ 
static uint32 ftl_logical_pages_per_dev_page; 
 
/* 
 *  These variables denote what is the mask for observing each entity 
 *  given the sector number. Shift is used in the conversions. In case 
 *  a device which has odd number which makes these masks not possible 
 *  will have to use division method which will be slower than this. 
 * 
 */ 
static uint32 logical_page_sector_mask; 
static uint32 physical_page_sector_mask; 
 
/*============================================================================= 
 
          F T L   I m p l e m e n t a t i o n    s t a r t 
 
=============================================================================*/ 
 
/****************************************************************************** 
FUNCTION      get_unique_free_block_search_pos 
 
DESCRIPTION   Use CRC 16 algorithm on the existing map and get a unique 
              (pseudo random) location to search for the free block. This 
              will make sure we don't start from a known physical block 
              always and wearing them out the first. 
 
RETURN VALUE 
              Block number to start search for a free block. 
******************************************************************************/ 
static uint32 
get_unique_free_block_search_pos (void) 
{ 
  uint32 pseudo_random_pos, data_bit_len; 
 
  /* Number of bits to compute */ 
  data_bit_len = ((max_logical_page_cnt / 
                   (ftl_logical_pages_per_dev_page * ftl_pages_per_block)) 
                  * sizeof (*lbn_to_phy_blk_map) * BITS_PER_BYTE); 
 
  /* Correct it, if it overflows to more than CRC routine can handle */ 
  if (data_bit_len > 0xFFF0) 
    data_bit_len = 0xFFF0; 
 
  pseudo_random_pos = crc_16_calc ((byte*)lbn_to_phy_blk_map, data_bit_len); 
 
  return pseudo_random_pos % ftl_block_count; 
} 
 
/****************************************************************************** 
FUNCTION      erase_block_and_update_status 
 
DESCRIPTION   This function erases the given block. 
              Also attempts to mark the block bad on failure to erase. 
 
RETURN VALUE  Flash status 
******************************************************************************/ 
static int 
erase_block_and_update_status (uint32 block_num) 
{ 
  int result; 
 
  result = ftl_dev_erase_block (ftl_flash_dev, block_num); 
  if (result != FS_DEVICE_OK) 
  { 
    /* No use in checking error return for this function, we can't do much */ 
    (void) ftl_dev_mark_block_bad (ftl_flash_dev, block_num); 
    phy_blk_status[block_num] = BLK_STATE_BAD; 
  } 
  else 
  { 
    phy_blk_status[block_num] = BLK_STATE_FREE; 
    ++free_count; 
  } 
 
  return result; 
} 
 
/****************************************************************************** 
FUNCTION      ftl_raw_init 
 
DESCRIPTION   Initialize the FTL layer 
 
RETURN VALUE 
              0 : Success 
     Error code : Failure 
******************************************************************************/ 
int 
ftl_raw_init (void) 
{ 
  uint32 corrected_spare, pg_to_read, first_page_written, last_page_written; 
  uint32 stored_spare, block_num; 
 
  ZAP ("ftl_init()"); 
 
  /* If we fail to initialize FTL layer, this count would make sure we 
   * return from other externally available API's without doing anything. */ 
  max_logical_page_cnt = 0; 
 
#ifndef FS_UNIT_TEST 
  /* Probe flash and get its parameters */ 
  if (flash_nand_device_probe (&ftl_priv_device, 0) != FS_DEVICE_OK) { 
    ftl_flash_dev = (fs_device_t)FS_NO_DEVICE; 
  } else { 
    ftl_flash_dev = (fs_device_t)&ftl_priv_device; 
  } 
#endif 
 
  if (ftl_flash_dev == (fs_device_t)FS_NO_DEVICE) { 
    return FTL_FAILURE; 
  } 
 
  /* Enable this feature in cust file to use partition support */ 
#ifdef FEATURE_FTL_USE_PARTITION 
  if (ftl_flash_dev->open_partition != 0) 
  { 
    if (ftl_flash_dev->open_partition (ftl_flash_dev, FTL_PARTITION_NAME) 
        != FS_DEVICE_OK) 
    { 
      /* If the partition is not defined, then hotplug would know that 
       * the FTL device is not existing (since we return fail to init) 
       * so it would not allow FTL device to be mounted. To have FTL 
       * working, define the partion of needed size. */ 
      return FTL_FAILURE; 
    } 
  } 
#endif 
 
  /* Since we have small number of states, compiler should not allocate more 
   * than a byte for the enum. If it does we just waste a lot of memory space 
   * for no better use, so correct something to instruct compiler to allocate 
   * just 1 byte for this enum */ 
  if (sizeof (phy_blk_status) > MAX_BLOCK_COUNT) { 
     ERR_FATAL("Compiler allocated size for enum bigger than a byte", 0, 0, 0); 
  } 
 
  /* Get basic flash device layout parameters */ 
  ftl_block_count =     ftl_dev_block_count (ftl_flash_dev); 
  ftl_flash_page_size = ftl_dev_page_size (ftl_flash_dev); 
  ftl_pages_per_block = ftl_dev_block_size (ftl_flash_dev); 
 
  /* The maximum block (sector) count is reduced by our reserved space 
     to allow spare pages for bad blocks. */ 
  max_logical_page_cnt = (ftl_block_count 
                         * ftl_pages_per_block 
                         * (ftl_flash_page_size / DEFAULT_MS_PAGE_SIZE) 
                         * (100 - PERCENT_BAD_BLOCK_BACKUP) / 100); 
 
  /* Limit the number to something which is rounded off to nearest 256 */ 
  max_logical_page_cnt &= 0xFFFFFF00; 
 
  ftl_logical_pages_per_dev_page = ftl_flash_page_size / DEFAULT_MS_PAGE_SIZE; 
 
  physical_page_sector_mask = 
        (ftl_pages_per_block * ftl_logical_pages_per_dev_page) - 1; 
 
  logical_page_sector_mask = ftl_logical_pages_per_dev_page - 1; 
 
  if ((physical_page_sector_mask 
       & (ftl_pages_per_block * ftl_logical_pages_per_dev_page)) 
      || (ftl_logical_pages_per_dev_page & logical_page_sector_mask)) 
  { 
    ERR_FATAL("Odd device configuration for this software to handle", 0, 0, 0); 
  } 
 
  /* If the device is bigger than we can handle just quit */ 
  if ((ftl_block_count > MAX_BLOCK_COUNT) 
      || (ftl_flash_page_size > MAX_DATA_PAGE_SIZE)) 
  { 
    /*  Here the arrays block map and page buffers are defined to be 
     *  the smallest possible units for a given flash size, to save 
     *  memory. If support for a bigger flash size is needed, then 
     *  these sizes should be increased to accomodate these increased sizes. */ 
    ERR_FATAL ("Mis configured size constants, Increase the size...", 0, 0, 0); 
  } 
 
  /* Check if we have enough Spare data bytes for storing FTL Meta data */ 
  if (ftl_dev_get_spare_corrected_bytes (ftl_flash_dev) < 2) 
  { 
    ERR_FATAL("Flash Device doesn't have enough Spare bytes for FTL", 0, 0, 0); 
  } 
 
  /* Initialize the block map with invalid values */ 
  memset (lbn_to_phy_blk_map, INVALID_GEN_PHY_BLK, sizeof(lbn_to_phy_blk_map)); 
 
  /* Fill our erased buffer */ 
  memset (erased_buffer, ERASED_BUFFER_VAL, sizeof(erased_buffer)); 
 
  /* A count of free (erased and useable) blocks is maintained 
   * for debug purposes. */ 
  free_count = 0; 
 
  corrected_spare = 0; 
 
  /* Walk through each physical block to assemble our map */ 
  for (block_num = 0; block_num < ftl_block_count; ++block_num) 
  { 
    /* If this block is bad, skip it and never touch it again */ 
    if (ftl_dev_bad_block_check (ftl_flash_dev, block_num) 
        != FS_DEVICE_OK) 
    { 
      phy_blk_status[block_num] = BLK_STATE_BAD; /* Avoid */ 
      continue; 
    } 
 
    /* If this was set TRUE for testing, erase each good block of the flash */ 
    if (ftl_fresh_start) { 
      erase_block_and_update_status (block_num); 
      continue; 
    } 
 
    /* ------------------------------------------------------------ 
     * Check the first and last page of this block to see if it has 
     * been written. 
     * ------------------------------------------------------------ */ 
 
    /* Check if the first page of this block is written */ 
    pg_to_read = BLOCK_TO_PAGE_NUM (block_num); 
    first_page_written = 
        ftl_dev_is_erased (ftl_flash_dev, pg_to_read) == FALSE; 
 
    /* Now read the last page of this block without ECC */ 
    pg_to_read += (ftl_pages_per_block - 1); 
 
    last_page_written = 
       (ftl_dev_read_spare_udata (ftl_flash_dev, pg_to_read, 
                                  &corrected_spare, 2) == FS_DEVICE_OK); 
 
    /* If nothing is written in first and last page, then this block is free */ 
    if (!first_page_written && !last_page_written) 
    { 
      phy_blk_status[block_num] = BLK_STATE_FREE; 
      free_count++; 
      continue; 
    } 
 
    /* If any one of the first or last page is not written, or ECC decode 
     * fails, then erase the block and continue, since power might have 
     * failed either while writing or during erase */ 
    if ( (first_page_written && !last_page_written)   || 
         (!first_page_written && last_page_written)   || 
         ((corrected_spare & LBN_MASK) >= ftl_block_count)) 
    { 
      erase_block_and_update_status (block_num); 
      continue; 
    } 
 
    /* ------------------------------------------------------------ 
     * This block has been fully written with valid data.  Find out if 
     * it's the newest version of this logical block and update our 
     * maps accordingly. 
     * ------------------------------------------------------------ */ 
 
    /* Check if this LBN has been seen in any other block */ 
    stored_spare = lbn_to_phy_blk_map[corrected_spare & LBN_MASK]; 
 
    if (stored_spare == 0xFFFF) { 
      /* Nothing is assigned, so just map this LBN to this physical block */ 
      lbn_to_phy_blk_map[corrected_spare & LBN_MASK] = 
        block_num | (corrected_spare & GENERATION_MASK); 
      phy_blk_status[block_num] = BLK_STATE_IN_USE; 
    } 
    else 
    { 
      /* We found duplicate blocks claiming to be the same LBN. 
       * Decide which is newest and discard the old copy (erase it). */ 
 
      /* Combine the two generations into a four-bit value for testing: */ 
      #define GENERATION_PAIR(a, b) (((a) << 2) | b) 
 
      switch (GENERATION_PAIR (GET_GENERATION (stored_spare), 
                               GET_GENERATION (corrected_spare))) 
      { 
        /* Left (previously-seen) generation is newer: */ 
        case GENERATION_PAIR(1, 0): 
        case GENERATION_PAIR(2, 1): 
        case GENERATION_PAIR(3, 2): 
        case GENERATION_PAIR(0, 3): 
          /* Erase and forget the current block, which is outdated */ 
          erase_block_and_update_status (block_num); 
          break; 
 
          /* Right (current block) generation is newer: */ 
        case GENERATION_PAIR(0, 1): 
        case GENERATION_PAIR(1, 2): 
        case GENERATION_PAIR(2, 3): 
        case GENERATION_PAIR(3, 0): 
          /* Erase the previously-seen block that is outdated */ 
          erase_block_and_update_status (stored_spare & LBN_MASK); 
 
          /* Note this one as the current block */ 
          lbn_to_phy_blk_map[corrected_spare & LBN_MASK] = 
            block_num | (corrected_spare & GENERATION_MASK); 
          break; 
 
          /* Ambiguous cases: identical generations: */ 
        case GENERATION_PAIR(0, 0): 
        case GENERATION_PAIR(1, 1): 
        case GENERATION_PAIR(2, 2): 
        case GENERATION_PAIR(3, 3): 
          /* Ambiguous cases: too far (2) apart: */ 
        case GENERATION_PAIR(0, 2): 
        case GENERATION_PAIR(1, 3): 
        case GENERATION_PAIR(2, 0): 
        case GENERATION_PAIR(3, 1): 
        default: 
          /* The two generation values are ambiguous, and it is not 
           * possible to determine which one is newer. This should 
           * never happen.  For now we can ERR_FATAL, but if it ever 
           * actually happens, we should probably just delete an 
           * arbitrary block (or even both blocks) to recover. */ 
          ERR_FATAL("Generation fields are corrupted...", 0, 0, 0); 
      } 
    } 
  } 
 
  /* Compute the location where we will begin looking for a free 
   * block.  While this doesn't have to be random, it should be 
   * well-distributed so that we don't unevenly wear flash by favoring 
   * some physical blocks over others.  Once we pick a starting block, 
   * we will rotate through the blocks in a simple linear walk.  */ 
  free_blk_pos = get_unique_free_block_search_pos (); 
 
  return FTL_SUCCESS; 
} 
 
/****************************************************************************** 
FUNCTION      ftl_raw_read 
 
DESCRIPTION   Read specified number of sectors from mentioned sector onwards. 
 
    Now with 2k page support we have too many variables in the picture. 
 
    Logical page : 512 byte page (or simply sector) 
    Logical unit : 2k page, its the actual page where logical page is present. 
                   for 512 page device Logical page == Logical unit. 
 
RETURN VALUE 
              0 : Success 
     Error code : Failure 
******************************************************************************/ 
int 
ftl_raw_read (uint16 drive_num, uint32 sector, byte* buffer, uint16 count) 
{ 
  uint32 phy_blk, phy_src_page, device_page, last_page_to_read; 
  byte* page_buff_ptr = erased_buffer; 
 
  (void)drive_num; 
 
  if (sector >= max_logical_page_cnt) { 
    return FTL_FAILURE;         /* Out of range */ 
  } 
 
  ftl_device_read_counter (sector, count, FTL_OP_RAW); 
 
  last_page_to_read = sector + count; 
 
  device_page = INVALID_PAGE; 
 
  /* Loop for as many pages we need to read */ 
  for ( ; sector < last_page_to_read; ++sector) 
  { 
    if (device_page != LOGICAL_TO_DEVICE_PAGE(sector)) 
    { 
      device_page = LOGICAL_TO_DEVICE_PAGE(sector); 
 
      phy_blk = lbn_to_phy_blk_map[BLOCK_NUM_OF_PAGE (device_page)]; 
 
      phy_src_page = (BLOCK_TO_PAGE_NUM (phy_blk & LBN_MASK) 
                      + PAGE_OFFSET_IN_BLOCK (device_page)); 
 
      page_buff_ptr = dev_page_buffer; 
 
      if ((phy_blk != INVALID_GEN_PHY_BLK) 
          && (phy_blk_status[phy_blk & LBN_MASK] == BLK_STATE_BAD)) 
      { 
        /* This should never happen, a bad block is assigned to 
         * a usable sector */ 
        ERR_FATAL ("Bad block is assigned to a useable block: %X %d", 
                   phy_blk, free_count, 0); 
      } 
 
      if ((phy_blk == INVALID_GEN_PHY_BLK) 
          || (phy_blk_status[phy_blk & LBN_MASK] == BLK_STATE_FREE) 
          || (ftl_dev_read_page (ftl_flash_dev, phy_src_page, 
                                 page_buff_ptr) 
              != FS_DEVICE_OK)) 
      { 
        page_buff_ptr = erased_buffer; 
      } 
 
      page_buff_ptr += (DEFAULT_MS_PAGE_SIZE * LOGICAL_PAGE_OFFSET(sector)); 
 
    } 
 
    /*   This double memory copy can be avoided later by adding a bit more 
     *   complexity 
     */ 
    memcpy(buffer, page_buff_ptr, DEFAULT_MS_PAGE_SIZE); 
 
    /* Point to next page */ 
    buffer += DEFAULT_MS_PAGE_SIZE; 
    page_buff_ptr += DEFAULT_MS_PAGE_SIZE; 
  } 
 
  return FTL_SUCCESS; 
} 
 
/****************************************************************************** 
FUNCTION      get_free_block 
 
DESCRIPTION   Search for a free block in the free map and return block number. 
              Mark this block as "IN_USE" and decrement the free_count. 
              NOTE: this is physical Block number. 
 
RETURN VALUE 
              0 : Failure 
     Free Blk# : Success 
******************************************************************************/ 
uint32 
get_free_block (void) 
{ 
  uint32 block_num; 
 
  /* At the most we have search ftl_block_count times */ 
  for (block_num = 0; block_num < ftl_block_count; ++block_num) 
  { 
    if (phy_blk_status[free_blk_pos] == BLK_STATE_FREE) 
    { 
      phy_blk_status[free_blk_pos] = BLK_STATE_IN_USE; 
      --free_count; 
      return free_blk_pos; 
    } 
    ++free_blk_pos; 
    if (free_blk_pos >= ftl_block_count) 
      free_blk_pos = 0; 
  } 
 
  return INVALID_GEN_PHY_BLK; 
} 
 
/****************************************************************************** 
FUNCTION      get_spare_data_to_write 
 
DESCRIPTION   Calculate the spare data to write with new generation num. 
 
RETURN VALUE  Spare data to write into the last page spare area. 
******************************************************************************/ 
uint32 
get_spare_data_to_write (uint32 sector) 
{ 
  uint32 spare_val; 
 
  uint32 temp_lbn = BLOCK_NUM_OF_PAGE (sector); 
 
  /* Have new generation ready in the spare data */ 
  spare_val = GET_GENERATION (lbn_to_phy_blk_map[temp_lbn]); 
 
  /* Increment the Generation value and mask off. After max it wraps around */ 
  spare_val = ((spare_val + 1) << GENERATION_SHIFT) & GENERATION_MASK; 
 
  /* Get the LBN combined with Generation to write into the spare area */ 
  spare_val |= temp_lbn; 
 
  return spare_val; 
} 
 
/****************************************************************************** 
FUNCTION      convert_absolute_to_relative_sectors 
 
DESCRIPTION   Convert all absolute sector numbers to relative sector numbers 
                within physical block. 
 
RETURN VALUE  Success / Failure 
******************************************************************************/ 
 
static void 
convert_absolute_to_relative_sectors (cached_buffer_list_type* data_buffer_ary) 
{ 
  uint32 conversion_mask; 
 
  conversion_mask = ((ftl_logical_pages_per_dev_page * ftl_pages_per_block) 
                     - 1); 
  /*   Place only the offset numbers in the location, instead of sector 
   *   number. This will help just make one calculation for mapping 
   *   and rest of the code needs only the offsets 
   */ 
  while (data_buffer_ary->data_ptr != 0) 
  { 
    data_buffer_ary->sector_num 
        = conversion_mask & data_buffer_ary->sector_num; 
 
    data_buffer_ary++; 
  } 
} 
 
int 
find_and_copy_new_data (uint32 dest_page, 
                        cached_buffer_list_type * src_data_buffer, 
                        byte * dest_page_buffer) 
{ 
  uint32 logical_page_start, logical_page_end; 
  uint32 matched_page_cnt = 0; 
 
  logical_page_start = dest_page * ftl_logical_pages_per_dev_page; 
 
  /*   Remove the block component out of the sector values, since we 
   *   don't have block information in the buffers. It has only the 
   *   physical page and logical part in the page as sector number. 
   */ 
  logical_page_start = logical_page_start & physical_page_sector_mask; 
  logical_page_end = logical_page_start + ftl_logical_pages_per_dev_page; 
 
  while (src_data_buffer) 
  { 
    if ((src_data_buffer->data_ptr == 0) 
        || (src_data_buffer->sector_num == INVALID_PAGE) 
        || (src_data_buffer->sector_num >= logical_page_end)) 
    { 
      break; 
    } 
 
    if ((src_data_buffer->sector_num >= logical_page_start) 
        && (src_data_buffer->sector_num < logical_page_end)) 
    { 
      ++matched_page_cnt; 
 
      if (dest_page_buffer != 0) 
      { 
        byte* dst_ptr = dest_page_buffer; 
 
        dst_ptr += ((src_data_buffer->sector_num & logical_page_sector_mask) 
                    * DEFAULT_MS_PAGE_SIZE); 
 
        memcpy (dst_ptr, src_data_buffer->data_ptr, DEFAULT_MS_PAGE_SIZE); 
      } 
 
      if (matched_page_cnt == ftl_logical_pages_per_dev_page) 
      { 
        break; 
      } 
    } 
    src_data_buffer++; 
  } 
 
  return matched_page_cnt; 
} 
 
int 
write_device_page (uint32 dest_page, 
                   uint32 old_src_page, 
                   cached_buffer_list_type * src_data_buffer, 
                   uint32 spare_val) 
{ 
 
  uint32 matched_page_cnt, read_source_page_needed; 
  uint8 * data_ptr; 
  uint32 spare_data = spare_val; 
  int result = FS_DEVICE_OK; 
 
  data_ptr = dev_page_buffer; 
 
  /* Just search how many new pages we got, Don't copy yet. */ 
  matched_page_cnt = find_and_copy_new_data (dest_page, src_data_buffer, 0); 
 
  read_source_page_needed = 0; 
 
  /*   If we don't have src data available, but this page is either first or 
   *   last page then we have to write the page data, so need to read the 
   *   old src page. 
   */ 
  if ((matched_page_cnt == 0) && 
      ((PAGE_OFFSET_IN_BLOCK (dest_page) == 0) || 
       (PAGE_OFFSET_IN_BLOCK (dest_page) == (ftl_pages_per_block - 1)))) 
  { 
    read_source_page_needed = 1; 
  } 
  else if ((matched_page_cnt != 0) 
           && (matched_page_cnt != ftl_logical_pages_per_dev_page)) 
  { 
    read_source_page_needed = 1; 
  } 
 
  if (read_source_page_needed) 
  { 
    if (PAGE_IS_NOT_VALID (old_src_page) || 
        (ftl_dev_read_page (ftl_flash_dev, old_src_page, data_ptr) 
         != FS_DEVICE_OK)) 
    { 
      memset (data_ptr, ERASED_BUFFER_VAL, ftl_flash_page_size); 
    } 
  } 
 
  if (matched_page_cnt) 
  { 
    /* This will overwrite the buffer with new data if available */ 
    find_and_copy_new_data (dest_page, src_data_buffer, data_ptr); 
  } 
 
  /* Now we have source data ready if we need to write, or copy page */ 
 
  if (PAGE_OFFSET_IN_BLOCK (dest_page) == (ftl_pages_per_block - 1)) 
  { 
    result = ftl_dev_write_page_and_udata (ftl_flash_dev, dest_page, 
                                           data_ptr, &spare_data, 2); 
  } 
  else if ((PAGE_OFFSET_IN_BLOCK (dest_page) == 0) || (matched_page_cnt != 0)) 
  { 
    result = ftl_dev_write_page (ftl_flash_dev, dest_page, data_ptr); 
  } 
  else if (PAGE_IS_VALID (old_src_page)) 
  { 
    result = ftl_dev_copy_page (ftl_flash_dev, old_src_page, dest_page); 
  } 
 
  return result; 
 
/* DONE */ 
} 
 
/****************************************************************************** 
FUNCTION      single_small_block_write 
 
DESCRIPTION   Attempt to Write one block worth of data. 
 
RETURN VALUE  Success / Failure 
******************************************************************************/ 
static int 
single_block_write (uint32 sector, cached_buffer_list_type* data_buffer_ary, 
                    uint32 free_blk_to_write) 
{ 
  uint32 old_phy_src_page; 
  uint32 dst_page, dst_end_page; 
  uint32 old_phy_blk, spare_data; 
  uint32 device_page_num; 
  int result; 
 
  convert_absolute_to_relative_sectors (data_buffer_ary); 
 
  device_page_num = LOGICAL_TO_DEVICE_PAGE (sector); 
 
  spare_data = get_spare_data_to_write(device_page_num); 
 
  /* Do we have old block assigned to this LBN?  */ 
  old_phy_blk = lbn_to_phy_blk_map[BLOCK_NUM_OF_PAGE (device_page_num)] 
                & LBN_MASK; 
 
  /* Have the source page number for old block ready, if one is available */ 
  if ((old_phy_blk < ftl_block_count) &&              /* Within limit..?? */ 
      (phy_blk_status[old_phy_blk] == BLK_STATE_IN_USE)) /* Available..?? */ 
    old_phy_src_page = BLOCK_TO_PAGE_NUM (old_phy_blk); 
  else 
    old_phy_src_page = INVALID_PAGE; 
 
  /*   Our range of physical destination pages to loop through. 
   * The physical destination's: First page to last page. 
   */ 
  dst_page = BLOCK_TO_PAGE_NUM (free_blk_to_write); 
  dst_end_page = LAST_PAGE_OF_BLOCK (free_blk_to_write); 
 
  /* 
   *   Write this entire block page by page. Sequence to write the data 
   */ 
  while (dst_page <= dst_end_page) 
  { 
    /* Check if our current writing page, should write new data that we got */ 
    result = write_device_page (dst_page, old_phy_src_page, 
                                data_buffer_ary, spare_data); 
 
    if ((result != FS_DEVICE_OK) && (result != FS_DEVICE_CP_READ_FAIL)) 
      { 
        return FTL_FAILURE; 
      } 
 
    /* Pick next page */ 
    dst_page++; 
    if (PAGE_IS_VALID (old_phy_src_page)) 
      ++old_phy_src_page; 
 
  }  /* Handling of one block is done now */ 
 
  /* Delete the old block if we had it active */ 
  if ((old_phy_blk < ftl_block_count) && 
      (phy_blk_status[old_phy_blk] == BLK_STATE_IN_USE)) 
  { 
    erase_block_and_update_status (old_phy_blk); 
  } 
 
  /* Update our main map, use the spare data which already has the value */ 
  phy_blk_status[free_blk_to_write] = BLK_STATE_IN_USE; 
  lbn_to_phy_blk_map[spare_data & LBN_MASK] = free_blk_to_write | 
                                              (spare_data & GENERATION_MASK); 
  return FTL_SUCCESS; 
} 
 
/****************************************************************************** 
FUNCTION      ftl_raw_write 
 
DESCRIPTION   Write multiple logical sectors starting from the specified one. 
 
RETURN VALUE  Success / Failure 
******************************************************************************/ 
int 
ftl_raw_write (uint16 drive_num, uint32 sector, 
               cached_buffer_list_type* buffer, uint16 count) 
{ 
  uint32 free_blk; 
  uint32 logical_device_page; 
 
  (void) drive_num; 
 
  if ((sector >= max_logical_page_cnt) || 
      ((sector + count) > max_logical_page_cnt)) { 
    return FTL_FAILURE; 
  } 
 
  ftl_device_write_counter (sector, count, FTL_OP_RAW); 
 
  /* We don't have a free block available handy now, later its possible */ 
  free_blk = INVALID_GEN_PHY_BLK; 
 
  while (count > 0) 
  { 
    /* Get a free block if we don't have one handy already */ 
    if (free_blk == INVALID_GEN_PHY_BLK) 
    { 
      free_blk = get_free_block (); 
      if (free_blk == INVALID_GEN_PHY_BLK) 
      { 
        ERR_FATAL ("Error, NO more free blocks available to write", 0, 0, 0); 
      } 
    } 
 
    /* Write 1 block with this new data in the free block that we got */ 
    if (single_block_write (sector, buffer, free_blk) == FTL_SUCCESS) 
    { 
      uint32 pages_written, pg_of_next_blk; 
 
      logical_device_page = LOGICAL_TO_DEVICE_PAGE(sector); 
 
      /* Calculate how many pages of required data we have in this 1 block */ 
      pg_of_next_blk = 
        BLOCK_TO_PAGE_NUM (BLOCK_NUM_OF_PAGE (logical_device_page) + 1); 
      pages_written = ((pg_of_next_blk - logical_device_page) 
                        * ftl_logical_pages_per_dev_page); 
 
      pages_written -= LOGICAL_PAGE_OFFSET(sector); 
 
      pages_written = (pages_written > count) ? count : pages_written; 
 
      /* Update our params */ 
      count -= pages_written; 
      sector += pages_written; 
 
      /* Get new block to write */ 
      free_blk = INVALID_GEN_PHY_BLK; 
    } else { 
        if (erase_block_and_update_status (free_blk) != FS_DEVICE_OK) { 
          /* Retry writing the data by allocating new block */ 
          free_blk = INVALID_GEN_PHY_BLK; 
        } 
    } /* Single block write */ 
  }  /* While */ 
 
  return FTL_SUCCESS; 
} 
 
/****************************************************************************** 
FUNCTION      ftl_raw_format 
 
DESCRIPTION   Low level formatting 
 
RETURN VALUE  0 
******************************************************************************/ 
int 
ftl_raw_format (fs_device_t dev) 
{ 
  uint32 block_num; 
 
  if (dev == (fs_device_t)FS_NO_DEVICE) 
  { 
    return -1; 
  } 
 
  for (block_num = 0; block_num < ftl_block_count; ++block_num) 
  { 
    if (ftl_dev_bad_block_check (ftl_flash_dev, block_num) 
        != FS_DEVICE_BAD_BLOCK) 
    { 
      (void) ftl_dev_erase_block (ftl_flash_dev, block_num); 
      phy_blk_status[block_num] = BLK_STATE_FREE; 
      ++free_count; 
    } 
    else 
    { 
      phy_blk_status[block_num] = BLK_STATE_BAD; 
    } 
 
  } 
 
  memset (lbn_to_phy_blk_map, INVALID_GEN_PHY_BLK, sizeof(lbn_to_phy_blk_map)); 
 
  return 0; 
} 
 
/****************************************************************************** 
FUNCTION      ftl_get_raw_dev_params 
 
DESCRIPTION   Get physical flash device parameters. 
 
RETURN VALUE  0 
******************************************************************************/ 
int 
ftl_get_raw_dev_params (uint32 * pblock_count, uint32 * ppg_per_block, 
                       uint16 * pg_size, uint32 *psector_cnt) 
{ 
  if (ftl_flash_dev == (fs_device_t)FS_NO_DEVICE) 
  { 
    return FTL_FAILURE; 
  } 
 
  if (ppg_per_block) 
  { 
    *ppg_per_block = ftl_pages_per_block; 
  } 
 
  if (pblock_count) 
  { 
    *pblock_count = ftl_block_count; 
  } 
 
  if (pg_size) 
  { 
    *pg_size = ftl_flash_page_size; 
  } 
 
  if (psector_cnt) 
  { 
    *psector_cnt = max_logical_page_cnt; 
  } 
 
  return FTL_SUCCESS; 
} 
 
#ifdef FS_UNIT_TEST 
#error code not present 
#endif 
 
#else 
extern int __dont_complain_about_empty_file; 
#endif /* FEATURE_FS_FTL */