/******************************************************************************

 @file  nvocmp.c

 @brief NV driver for CC26x2 devices - On-Chip Multi-Page Flash Memory

 Group: CMCU, LPC
 Target Device: cc13xx_cc26xx

 ******************************************************************************
 
 Copyright (c) 2019-2021, Texas Instruments Incorporated
 All rights reserved.

 Redistribution and use in source and binary forms, with or without
 modification, are permitted provided that the following conditions
 are met:

 *  Redistributions of source code must retain the above copyright
    notice, this list of conditions and the following disclaimer.

 *  Redistributions in binary form must reproduce the above copyright
    notice, this list of conditions and the following disclaimer in the
    documentation and/or other materials provided with the distribution.

 *  Neither the name of Texas Instruments Incorporated nor the names of
    its contributors may be used to endorse or promote products derived
    from this software without specific prior written permission.

 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
 THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
 PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
 CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
 OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
 WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
 OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
 EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

 ******************************************************************************
 
 
 *****************************************************************************/

//*****************************************************************************
// Design Overview
//*****************************************************************************
/*
This driver implements a non-volatile (NV) memory system that utilizes multi pages
(consecutive) of on-chip Flash memory. After initialization, all pages except one
are ACTIVE and the remaining one page is available for "compaction" when the ACTIVE
pages do not have enough empty space for data write operation. Compaction can occur
'just in time' during a data write operation or 'on demand' by application request.
The compaction process is designed to survive a power cycle before it completes. It
will resume where it was interrupted and complete the process.

This driver makes the following assumptions and uses them to optimize the code
and data storage design: (1) Flash memory is addressable at individual, 1-byte
resolution so no padding or word-alignment is necessary (2) Flash has limited
number of writes per flash 'sector' between erases. To prevent going over this
limit, "small" items are written in one operation.

Each Flash page has a "page header" which indicates its current state ,
located at the first byte of the Flash page and "compact header" which indicates
its compaction state, located following "page header". The remainder of
the Flash page contains NV data items which are packed together following the
page header and compact header. Each NV data item has two parts, (1) a data block
which is stored first (lower memory address), (2) immediately followed by item header
(higher memory address). The item header contains information necessary to traverse the
packed data items, as well as, current status of each data item. Obsolete items
marked accordingly but a search for the newest instance of an item is sped up
by starting the search at the last entry in the page (higher memory address).

Each item is unique, addressed using three ID values (system ID, item ID, sub
ID). These three values are stored in the header along with a 'signature', a
CRC8 value, the length of the data block, and two status bits. The two status
bits indicate whether an item is still active and the health or validity of an
item. The signature byte is used by the driver to detect the presence of an
item, and is the same for all items as well as the page header and compact header.
The CRC8 value allows the driver to confirm the integrity of the items during
compaction and optionally when an item read operation is requested. The length
of the data block is used to jump from one item to the next. If this field is
corrupted, the driver is forced to search for items by signature and possibly
compute multiple CRC's to confirm it has found a valid item. Note that any
corruption event forces a compaction to recover.
*/
//*****************************************************************************
// Use / Configuration
//*****************************************************************************
/*
Since this is multi page NV driver, the number of NV pages is configurable.
NVOCMP_NVPAGES = 1 means 1 page storage and 0 compaction page.
NVOCMP_NVPAGES = 2 means 1 page storage and 1 compaction page.
NVOCMP_NVPAGES = 3 means 2 pages storage and 1 compaction page.
NVOCMP_NVPAGES = 4 means 3 pages storage and 1 compaction page.
NVOCMP_NVPAGES = 5 means 4 pages storage and 1 compaction page.
NVOCMP_NVPAGES can be configured from project option. If this flag is not
configured, NVOCMP_NVPAGES = 2 will be by default.
"nvintf.h" describes the generic NV interface which is used to access NVOCMP
after initialization. Initialization is done by passing a function pointer
struct to one of NVOCMP pointer loader functions. Once this is done, the
pointer struct (which is part of the nvintf interface) should be used to call
the nvintf initialization function, which will initialize NVOCMP. At this point
NVOCMP is ready and loaded API functions can be called through the pointer
structure. Note that some pointers may be NULL depending on which NVOCMP
loader function was called. For example NVOCMP_loadApiPtrsMin() loads only
the essential functions to reduce code size at link time.
A sample code block is shown below:

NVINTF_nvFuncts_t nvFps;
NVOCMP_loadApiPtrs(&nvFps);

nvFps.initNV(NULL);
// Do some NV operations
nvFps.compactNV(NULL);
status = nvFps.readItem(id, 0, len, buf);

Note: Each item operation results in a traversal of the page starting at
the most recently written item. This makes 'finding' items by 'trying' item IDs
in order extremely inefficient. The doNext() API call allows the user to find,
read, or delete items in one page traversal. However, this call requires the
user to lock access to NV until the operation is complete so it should be used
carefully and sparingly.

Note: The compile flag NVDEBUG can be passed to enable ASSERT and ALERT
macros which provide assert and logging functionality. When this flag is used,
a printf function of the form void nvprint(char * str) MUST be
provided to link the driver. The function need not be functional, but it must
exist. NVDEBUG also exposes driver global variables for debug and testing.

Not all user-defines (such as NVDEBUG) are supported when using NVOCMP in a
Linux envrionment. If debugging/logging functionality is required, the
"nv-debug" or "nv-rdwr" logging flags can be enabled in the cfg INI file.

Configuration:
NVOCMP_STATS - Places a protected item with driver stats
NVOCMP_CRCONREAD (on:1 off:0) - item crc is checked on read. Disabling this may
increase driver speed but safety is reduced.
NVOCMP_NVS_INDEX - The index of the NVS_Config structure which describes the
flash sector that NVOCMP should use. Default is 0.

Dependencies:
Requires NVS for NV access.
Requires TI-RTOS GateMutexPri or POSIX mutex to be enabled in configuration.
Requires API's in a crc.h to implement CRC functionality.
*/

//*****************************************************************************
// Includes
//*****************************************************************************

#include <string.h>
#ifdef NVOCMP_POSIX_MUTEX
#include <pthread.h>
#else
#include <ti/sysbios/gates/GateMutexPri.h>
#endif
#include "nvocmp.h"
#include "crc.h"
#ifndef NV_LINUX
#include <driverlib/vims.h>
#ifdef NVOCMP_MIN_VDD_FLASH_MV
#include <driverlib/aon_batmon.h>
#endif
#endif

#ifdef NV_LINUX
#include "nv_linux.h"
#endif

//*****************************************************************************
// Constants and Definitions
//*****************************************************************************
#ifndef NVOCMP_NVPAGES
#define NVOCMP_NVPAGES      2     //1 ~ 5 are supported
#endif

#if (NVOCMP_NVPAGES > 5)
#error "NVOCMP_NVPAGES should be in between 1 and 5"
#endif

#define NVOCMP_FASTCP       1           // Fast Compaction by Skipping All Active Item Pages
#define NVOCMP_COMPR        0           // Order Change When Compaction
#define NVOCMP_HDRLE        0           // Little Endian Format Item Header
#define NVOCMP_FASTOFF      1           // Fast Search Offset
#define NVOCMP_FASTITEM     0           // Fast Find Item

#ifndef NVOCMP_NWSAMEITEM
#define NVOCMP_NWSAMEITEM   0           // Not Write Same Item
#endif

#ifndef NVOCMP_MIGRATE_ENABLED
#define NVOCMP_MIGRATE_DISABLED         // Migration from old NVOCTP disabled by default
#endif

#define NVOCMP_NVONEP       1           // One Page NV
#define NVOCMP_NVTWOP       2           // Two Page NV

#define NVOCMP_NVSIZE       NVOCMP_size
#define NVOCMP_ADDPAGE(p,n) (((p) + (n)) % NVOCMP_NVSIZE)
#define NVOCMP_INCPAGE(p)   NVOCMP_ADDPAGE(p,1)
#define NVOCMP_DECPAGE(p)   NVOCMP_ADDPAGE(p,NVOCMP_NVSIZE - 1)
// Which NVS_config indice is used to initialize NVS.
#ifndef NVOCMP_NVS_INDEX
#define NVOCMP_NVS_INDEX    0
#endif // NVOCMP_NVS_INDEX

// Maximum ID parameters - must be coordinated with header format
#define NVOCMP_MAXSYSID     0x003F  //  6 bits
#define NVOCMP_MAXITEMID    0x03FF  // 10 bits
#define NVOCMP_MAXSUBID     0x03FF  // 10 bits
#define NVOCMP_MAXLEN       0x0FFF  // 12 bits
#define NVOCMP_INVALIDSUBID 0xFFFF  // Invalid Sub Id

// Contents of an erased Flash memory locations
#define NVOCMP_ERASEDBYTE   0xFF
#define NVOCMP_ERASEDWORD   0xFFFFFFFF

// Size of byte
#define NVOCMP_ONEBYTE      1

// Compressed ID bit spacing
#define NVOCMP_CMPSPACE     12

// Invalid NV page - if 0xFF is ever used, change this definition
#define NVOCMP_NULLPAGE     0xFF

// Block size for Flash-Flash XFER (Bytes)
#define NVOCMP_XFERBLKMAX   32

// Size in bytes of biggest item size that will be concatenated
// in RAM before write, instead of header/data written separately
#define NVOCMP_SMALLITEM    12

#if defined (NVOCMP_STATS)
// NV item ID for driver diagnostics
static const NVINTF_itemID_t diagId = NVOCMP_NVID_DIAG;
#endif  // NVOCMP_STATS

// CRC options
// When not NULL, reads will result in a CRC check before returning
#define NVOCMP_CRCONREAD    1

// findItem search types
// Find any item, item of spec'd sysid, item of spec'd sys and item id
// or find the exact item specified
enum {NVOCMP_FINDANY = 0x00, NVOCMP_FINDSYSID, NVOCMP_FINDITMID, NVOCMP_FINDSTRICT,
      NVOCMP_FINDCONTENT = 0x10};

#define NVOCMP_FINDLMASK    0x0F
#define NVOCMP_FINDHMASK    0xF0
//*****************************************************************************
// Macros
//*****************************************************************************

// Makes an NV Flash address (for 0x2000 page size)
#define NVOCMP_FLASHOFFSET(pg, ofs) ((uint32_t)(((pg) << PAGE_SIZE_LSHIFT) + (ofs)))

// Optional user provided function is called before writes/erases
// Intention is to check for sufficient voltage for operation
#ifndef NV_LINUX
#define NVOCMP_FLASHACCESS(err) {if (NVOCMP_voltCheckFptr)\
    { if (!NVOCMP_voltCheckFptr()) { err = NVINTF_LOWPOWER;}}}
#endif

#ifdef NVOCMP_POSIX_MUTEX
// Lock driver access via TI-RTOS gatemutex
#define NVOCMP_LOCK() pthread_mutex_lock(&NVOCMP_gPosixMutex);

// Unlock driver access via TI-RTOS gatemutex and return error code
#define NVOCMP_UNLOCK(err) { \
        pthread_mutex_unlock(&NVOCMP_gPosixMutex); return(err); }
#else
// Lock driver access via TI-RTOS gatemutex
#define NVOCMP_LOCK() int32_t key = GateMutexPri_enter(NVOCMP_gMutexPri);

// Unlock driver access via TI-RTOS gatemutex and return error code
#define NVOCMP_UNLOCK(err) { \
    GateMutexPri_leave(NVOCMP_gMutexPri, key); return(err); }
#endif

// Generate a compressed NV ID (NOTE: bit31 must be zero)
#define NVOCMP_CMPRID(s,i,b) ((uint32_t)((((((s) & NVOCMP_MAXSYSID) << NVOCMP_CMPSPACE)   | \
                                            ((i) & NVOCMP_MAXITEMID)) << NVOCMP_CMPSPACE) | \
                                            ((b) & NVOCMP_MAXSUBID)))

// NVOCMP Unit Test Assert Macro/Function
#ifdef NVDEBUG
extern void nvprint(char * message);
extern void Main_assertHandler(uint8_t assertReason);
static void NVOCMP_assert(bool cond, char *message, bool fatal)
{
    if (!cond)
    {
        nvprint("NVDEBUG: ");
        nvprint(message);
        if (fatal)
        {
            Main_assertHandler(0);
        }
    }
}
#define NVOCMP_ASSERT(cond, message)    NVOCMP_assert((cond), (message), true);
#define NVOCMP_ALERT(cond, message)     NVOCMP_assert((cond), (message), false);
#define NVOCMP_ASSERT1(cond)  {if(!cond)  while(1);}
#else
#ifdef NV_LINUX
#define NVOCMP_ASSERT1(cond)            NVOCMP_ASSERT((cond), "NVOCMP_ASSERT1")
#else
#define NVOCMP_ASSERT(cond, message)
#define NVOCMP_ALERT(cond, message)
#define NVOCMP_ASSERT1(cond)  {if(!cond)  while(1);}
#endif
#endif // NVDEBUG

//*****************************************************************************
// Page and Header Definitions
//*****************************************************************************
// CC26x2/CC13x2 devices flash page size is (1 << 13) or 0x2000
#define PAGE_SIZE_LSHIFT 13
#if !defined (FLASH_PAGE_SIZE)
#define FLASH_PAGE_SIZE  (1 << PAGE_SIZE_LSHIFT)
#endif // FLASH_PAGE_SIZE

#if !defined (NVOCMP_VERSION)
// Version of NV page format (do not use 0xFF)
#define NVOCMP_VERSION  0x03
#endif // NVOCMP_VERSION

#if !defined (NVOCMP_SIGNATURE)
// Page header validation byte (do not use 0xFF)
#define NVOCMP_SIGNATURE  0x96
#endif // NVOCMP_SIGNATURE

// Compact Memory
#ifndef NV_LINUX
#define NVOCMP_GPRAM
#endif

#ifdef NVOCMP_GPRAM
#define RAM_BUFFER_ADDRESS    (uint8_t *)0x11000000
#else
uint32_t tBuffer[FLASH_PAGE_SIZE >> 2];               // this is for debugging
#endif

// Page header structure
typedef struct
{
    uint32_t state:8;
    uint32_t cycle:8;       // Rolling page compaction count (0x00, 0xFF not used)
    uint32_t allActive:2;
    uint32_t version:6;     // Version of NV page format
    uint32_t signature:8;   // Signature for formatted NV page
} NVOCMP_pageHdr_t;

typedef struct
{
  uint16_t pageOffset;
  uint8_t page;
  uint8_t signature;
} NVOCMP_compactHdr_t;

// Page header size (bytes)
#define NVOCMP_PGHDRLEN  (sizeof(NVOCMP_pageHdr_t))
#define NVOCMP_COMPACTHDRLEN  (sizeof(NVOCMP_compactHdr_t))

// Page header offsets (from 1st byte of page)
#define NVOCMP_PGHDROFS  0
#define NVOCMP_PGHDRPST  0  // Page state
#define NVOCMP_PGHDRCYC  1  // Cycle count
#define NVOCMP_PGHDRVER  2  // Format version
#define NVOCMP_PGHDRSIG  3  // Page signature

// Compact header offsets
#define NVOCMP_COMPMODEOFS  6 // Compact Mode Offset

// Number of Compact headers
#define NVOCMP_NOCOMPHDR    3

// Page data size, offset into page
#define NVOCMP_PGDATAOFS  (NVOCMP_PGHDRLEN + NVOCMP_NOCOMPHDR*NVOCMP_COMPACTHDRLEN)
#define NVOCMP_PGDATAEND  (FLASH_PAGE_SIZE - 1)
#define NVOCMP_PGDATALEN  (FLASH_PAGE_SIZE - NVOCMP_PGDATAOFS)

// Page mode of operation
#define NVOCMP_PGNORMAL   0xFF  // normal operation
#define NVOCMP_PGCDST     0xFE  // used as compact destination
#define NVOCMP_PGCDONE    0xFC
#define NVOCMP_PGCSRC     0xF8  // used as compact source
#define NVOCMP_PGMODEBIT  0x04

// NVOCTP header defines
#define NVOCTP_PGACTIVE   0xA5  // Current active page
#define NVOCTP_PGXFER     0x24  // Active page being compacted
#define NVOCTP_PGDATAOFS  NVOCMP_PGHDRLEN
#define NVOCTP_VERSION    0x02
#define NVOCTP_SIGNATURE  0x96

// Page header state values - transitions change 1 bit in each nybble
typedef enum NVOCMP_pageState {
  NVOCMP_PGNACT = 0xFF,
  NVOCMP_PGXDST = 0xFE,
  NVOCMP_PGRDY  = 0x7E,
  NVOCMP_PGACT  = 0x7C,
  NVOCMP_PGFULL = 0x78,
  NVOCMP_PGXSRC = 0x70,
  NVOCMP_PGNDEF = 0x00,
} NVOCMP_pageState_t;

typedef enum NVOCMP_compactStatus {
  NVOCMP_COMPACT_SUCCESS = 0x00,
  NVOCMP_COMPACT_SRCDONE = 0x01,
  NVOCMP_COMPACT_DSTDONE = 0x02,
  NVOCMP_COMPACT_BOTHDOE = 0x03,
  NVOCMP_COMPACT_FAILURE = 0x10,
} NVOCMP_compactStatus_t;

// Page compaction cycle count limits (0x00 and 0xFF not used)
#define NVOCMP_MINCYCLE  0x01  // Minimum cycle count (after rollover)
#define NVOCMP_MAXCYCLE  0xFE  // Maximum cycle count (before rollover)

#define NVOCMP_ALLACTIVE      0x3 // All Items are active
#define NVOCMP_SOMEINACTIVE   0x0 // Some Items are inactive

//*****************************************************************************
// Item Header Definitions
//*****************************************************************************

// Item header structure
typedef struct
{
    uint32_t cmpid; // Compressed ID
    uint16_t subid; // Sub ID
    uint16_t itemid; // Item ID
    uint8_t  sysid; // System ID
    uint8_t  crc8;  // crc byte
    uint8_t  sig;   // signature byte
    uint8_t  stats; // Status 'marks'
    uint16_t hofs;  // Header offset
    uint16_t len;   // Data length
    uint8_t  hpage; // Header page
} NVOCMP_itemHdr_t;

// Length (bytes) of compressed header
#define NVOCMP_ITEMHDRLEN  7

// Offset from beginning (low address) of header to fields in the header
#define NVOCMP_HDRSIGOFS    6
#define NVOCMP_HDRVLDOFS    5

// Number of bytes in header to include in CRC calculation
#define NVOCMP_HDRCRCINC    5

// Compressed item header information <-- Lower Addr    Higher Addr-->
// Byte: [0]      [1]      [2]      [3]      [4]      [5]      [6]
// Item: SSSSSSII IIIIIIII SSSSSSSS SSLLLLLL LLLLLLCC CCCCCCAV SSSSSSSS
// LSB of field:         ^           ^            ^        ^          ^
// Bit:  0              15          25           37       45         55
//
// Bit(s)  Bit Field Description
// =============================
// 48-55:  Signature byte (NVOCMP_SIGNATURE)
//    47:  valid id mark (0=valid)
//    46:  active id mark (1=active)
// 38-45:  CRC8 value
// 26-37:  data length (0-4095)
// 16-25:  item sub id (0-1023)
//  6-15:  nv item id (0-1023)
//   0-5:  system id (0-63)

// Bit47 in compressed header - '1' indicates 'active' NV item
// A deleted item is 'inactive'
#define NVOCMP_ACTIVEIDBIT   0x2
// Bit46 in compressed header - '0' indicates 'valid' NV item
// A corrupted item is 'invalid'
#define NVOCMP_VALIDIDBIT    0x1
// This bit is NOT included in the NV item itself but is encoded
// the 'stats' field of the itemHdr_t struct when the item is read
#define NVOCMP_FOLLOWBIT     0x4

// Index of last item header byte
#define NVOCMP_ITEMHDREND (NVOCMP_ITEMHDRLEN - 1)

// Compressed item header byte array
typedef uint8_t cmpIH_t[NVOCMP_ITEMHDRLEN];

// Item write parameters
typedef struct
{
    NVOCMP_itemHdr_t *iHdr;   // Ptr to item header
    uint16_t          dOfs;   // Source data offset
    uint16_t          bOfs;   // Buffer data offset
    uint16_t          len;    // Buffer data length
    uint8_t          *pBuf;   // Ptr to data buffer
} NVOCMP_itemWrp_t;

typedef struct
{
  void *cBuf;                 // Pointer to content to search
  uint16_t clength;           // Length of content to search
  uint16_t coff;              // Offset content to search
  void *rBuf;                 // Pointer to content to read
  uint16_t rlength;           // Length content to read
} NVOCMP_itemInfo_t;

typedef enum NVOCMP_initAction {
  NVOCMP_NORMAL_INIT = 0,
  NVOCMP_NORMAL_RESUME,
  NVOCMP_RECOVER_COMPACT,
  NVOCMP_RECOVER_ERASE,
  NVOCMP_FORCE_CLEAN,
  NVOCMP_NORMAL_MIGRATE,
  NVOCMP_ERROR_UNKNOWN,
} NVOCMP_initAction_t;

typedef enum NVOCMP_writeMode {
  NVOCMP_WRITE = 0,
  NVOCMP_CREATE,
  NVOCMP_UPDATE,
} NVOCMP_writeMode_t;

typedef struct
{
  uint8_t state;    // page state
  uint8_t cycle;    // page compaction cycle count. Used to select the 'newest' active page
                    // at device reset, in the very unlikely scenario that both pages are active.
  uint8_t mode;     // compact mode
  uint8_t allActive;  //all items are active or not
  uint8_t sPage;
  uint8_t ePage;
  uint16_t offset;  // page offset
  uint16_t sOffset;
  uint16_t eOffset;
} NVOCMP_pageInfo_t;

typedef struct
{
  uint8_t xDstPage;         // xdst page
  uint8_t xSrcSPage;    // xsrc start page
  uint8_t xSrcEPage;      // xsrc end page
  uint8_t xSrcPages;    // no of xsrc pages
  uint16_t xDstOffset;      // xdst offset
  uint16_t xSrcSOffset;    // xsrc start offset
  uint16_t xSrcEOffset;    // xsrc end offset
} NVOCMP_compactInfo_t;

typedef struct
{
  uint8_t nvSize;       // no of NV pages
  uint8_t headPage;     // head active page
  uint8_t tailPage;     // transfer destination page
  uint8_t actPage;      // current active page
  uint8_t xsrcPage;     // transfer source page
  uint8_t forceCompact; // force compaction to happen
  uint16_t actOffset;   // active page offset
  uint16_t xsrcOffset;  // transfer source page offset
  uint16_t xdstOffset;  // transfer destination page offset
  NVOCMP_compactInfo_t compactInfo;
  NVOCMP_pageInfo_t pageInfo[NVOCMP_NVPAGES];
} NVOCMP_nvHandle_t;
//*****************************************************************************
// Local variables
//*****************************************************************************
#define NVOCMP_NULLOFFSET     0xFFFF
#define DEFAULT_COMPACTHDR   {NVOCMP_NULLOFFSET, NVOCMP_NULLPAGE, NVOCMP_SIGNATURE};
#define THISPAGEHDR           0
#define XSRCSTARTHDR          1
#define XSRCENDHDR            2
#define NVOCMP_COMPACTHDRLEN  (sizeof(NVOCMP_compactHdr_t))
// NVS Objects
#ifdef NVDEBUG
// Expose these in debug mode
NVS_Handle NVOCMP_nvsHandle;
NVS_Attrs NVOCMP_nvsAttrs;
NVOCMP_nvHandle_t NVOCMP_nvHandle;
#else
static NVS_Handle NVOCMP_nvsHandle;
static NVS_Attrs NVOCMP_nvsAttrs;
/*static*/ NVOCMP_nvHandle_t NVOCMP_nvHandle;
#endif // NVDEBUG

// Flag to indicate that a fatal error occurred while writing to or erasing the
// Flash memory. If flag is set, it's unsafe to attempt another write or erase.
// This flag locks writes to Flash until the next system reset.
static uint8_t NVOCMP_failF = NVINTF_NOTREADY;

// Flag to indicate that a non-fatal error occurred while writing to or erasing
// Flash memory. With flag set, it's still safe to attempt a write or erase.
// This flag is reset by any API calls that cause an erase/write to Flash.
static uint8_t NVOCMP_failW;

// TI-RTOS gateMutexPri for the NV driver API functions
#ifdef NVOCMP_POSIX_MUTEX
static pthread_mutex_t NVOCMP_gPosixMutex;
#else
static GateMutexPri_Handle NVOCMP_gMutexPri;
#endif

// Small NV Item Buffer, for item construction
static uint8_t NVOCMP_itemBuffer[NVOCMP_SMALLITEM];

// Function Pointer to an optional user provided voltage check function
static bool (*NVOCMP_voltCheckFptr)(void);
// Diagnostic counter for bad CRCs
#ifdef NVOCMP_STATS
static uint16_t NVOCMP_badCRCCount = 0;
#endif // NVOCMP_STATS

NVOCMP_initAction_t gAction;
uint8_t NVOCMP_size;

//*****************************************************************************
// NV API Function Prototypes
//*****************************************************************************

static uint8_t    NVOCMP_initNvApi(void *param);
static uint8_t    NVOCMP_compactNvApi(uint16_t min);
static uint8_t    NVOCMP_createItemApi(NVINTF_itemID_t id, uint32_t len, void *buf);
static uint8_t    NVOCMP_updateItemApi(NVINTF_itemID_t id, uint32_t len, void *buf);
static uint8_t    NVOCMP_deleteItemApi(NVINTF_itemID_t id);
static uint32_t   NVOCMP_getItemLenApi(NVINTF_itemID_t id);
static uint8_t    NVOCMP_readItemApi(NVINTF_itemID_t id, uint16_t ofs, uint16_t len,
                                     void *buf);
static uint8_t    NVOCMP_readContItemApi(NVINTF_itemID_t id, uint16_t ofs,
                                      uint16_t rlen, void *rBuf,
                                      uint16_t clen, uint16_t coff, void *cBuf, uint16_t *pSubId);
static uint8_t    NVOCMP_writeItemApi(NVINTF_itemID_t id, uint16_t len, void *buf);
static uint8_t    NVOCMP_doNextApi(NVINTF_nvProxy_t * prx);
static int32_t    NVOCMP_lockNvApi(void);
static void       NVOCMP_unlockNvApi(int32_t);
static bool       NVOCMP_expectCompApi(uint16_t len);
static uint8_t    NVOCMP_eraseNvApi(void);
static uint32_t   NVOCMP_getFreeNvApi(void);

//*****************************************************************************
// NV Local Function Prototypes
//*****************************************************************************

static void       NVOCMP_initNv(NVOCMP_nvHandle_t *pNvHandle);
static uint8_t    NVOCMP_scanPage(NVOCMP_nvHandle_t *pNvHandle, uint8_t pg,
                                  NVOCMP_pageInfo_t *pPageInfo);
static int8_t     NVOCMP_findItem(NVOCMP_nvHandle_t *pNvHandle, uint8_t pg, uint16_t ofs,
                                  NVOCMP_itemHdr_t *pHdr, int8_t flag, NVOCMP_itemInfo_t *pInfo);
static uint8_t    NVOCMP_addItem(NVOCMP_nvHandle_t *pNvHandle, NVOCMP_itemHdr_t *iHdr,
                                 uint8_t *pBuf, NVOCMP_writeMode_t wm);
static void       NVOCMP_writeItem(NVOCMP_nvHandle_t *pNvHandle, NVOCMP_itemHdr_t *pHdr,
                                   uint8_t dstPg, uint16_t dstOff, uint8_t *pBuf);
static uint8_t    NVOCMP_erase(NVOCMP_nvHandle_t *pNvHandle, uint8_t dstPg);
static int16_t    NVOCMP_compactPage(NVOCMP_nvHandle_t *pNvHandle, uint16_t nBytes);
static NVOCMP_compactStatus_t NVOCMP_compact(NVOCMP_nvHandle_t *pNvHandle);
static uint8_t    NVOCMP_getDstPage(NVOCMP_nvHandle_t *pNvHandle, uint16_t len);
static void       NVOCMP_changePageState(NVOCMP_nvHandle_t *pNvHandle, uint8_t pg,
                                         NVOCMP_pageState_t state);
static void       NVOCMP_setPageState(NVOCMP_nvHandle_t *pNvHandle, uint8_t pg,
                                      NVOCMP_pageState_t state);
static void       NVOCMP_setItemInactive(NVOCMP_nvHandle_t *pNvHandle, uint8_t pg,
                                         uint16_t iOfs);
static uint8_t    NVOCMP_readItem(NVOCMP_itemHdr_t *iHdr, uint16_t ofs, uint16_t len,
                                  void *pBuf, bool flag);
static uint8_t    NVOCMP_checkItem(NVINTF_itemID_t *id, uint16_t len, NVOCMP_itemHdr_t *iHdr,
                                   uint8_t flag);
static inline void NVOCMP_read(uint8_t pg, uint16_t off, uint8_t *pBuf, uint16_t len);
static uint8_t    NVOCMP_write(uint8_t dstPg, uint16_t off, uint8_t *pBuf, uint16_t len);

static void       NVOCMP_readHeader(uint8_t pg, uint16_t ofs, NVOCMP_itemHdr_t *iHdr, bool flag);
static void       NVOCMP_setCompactHdr(uint8_t dstPg, uint8_t pg, int16_t offset,
                                       uint16_t location);
static uint16_t   NVOCMP_findOffset(uint8_t pg, uint16_t ofs);
static uint8_t    NVOCMP_doNVCRC(uint8_t pg, uint16_t ofs, uint16_t len, uint8_t crc, bool flag);
static uint8_t    NVOCMP_doRAMCRC(uint8_t *input, uint16_t len, uint8_t crc);
static uint8_t    NVOCMP_verifyCRC(uint16_t iOfs, uint16_t len, uint8_t crc, uint8_t pg, bool flag);
static uint8_t    NVOCMP_readByte(uint8_t pg, uint16_t ofs);
static void       NVOCMP_writeByte(uint8_t pg, uint16_t ofs, uint8_t bwv);

#if (NVOCMP_NVPAGES > NVOCMP_NVTWOP)
static uint8_t    NVOCMP_findDstPage(NVOCMP_nvHandle_t *pNvHandle);
static uint8_t    NVOCMP_cleanPage(NVOCMP_nvHandle_t *pNvHandle);
static uint8_t    NVOCMP_findPage(NVOCMP_pageState_t state);
static void       NVOCMP_getCompactHdr(uint8_t dstPg, uint16_t location, NVOCMP_compactHdr_t *pHdr);
#endif

#if (NVOCMP_NVPAGES > NVOCMP_NVONEP)
#if !defined(NVOCMP_MIGRATE_DISABLED)
static void       NVOCMP_copyItem(uint8_t srcPg, uint8_t xPg, uint16_t sOfs, uint16_t dOfs, uint16_t len);
static void       NVOCMP_migratePage(NVOCMP_nvHandle_t *pNvHandle, uint8_t page);
#endif
#endif

//*****************************************************************************
// Load Pointer Functions (These are declared in nvoctp.h)
//*****************************************************************************

/**
 * @fn      NVOCMP_loadApiPtrs
 *
 * @brief   Global function to return function pointers for NV driver API that
 *          are supported by this module, NULL for functions not supported.
 *
 * @param   pfn - pointer to caller's structure of NV function pointers
 *
 * @return  none
 */
void NVOCMP_loadApiPtrs(NVINTF_nvFuncts_t *pfn)
{
    // Load caller's structure with pointers to the NV API functions
    pfn->initNV       = &NVOCMP_initNvApi;
    pfn->compactNV    = &NVOCMP_compactNvApi;
    pfn->createItem   = &NVOCMP_createItemApi;
    pfn->updateItem   = &NVOCMP_updateItemApi;
    pfn->deleteItem   = &NVOCMP_deleteItemApi;
    pfn->readItem     = &NVOCMP_readItemApi;
    pfn->readContItem = &NVOCMP_readContItemApi;
    pfn->writeItem    = &NVOCMP_writeItemApi;
    pfn->getItemLen   = &NVOCMP_getItemLenApi;
    pfn->lockNV       = NULL;
    pfn->unlockNV     = NULL;
    pfn->doNext       = NULL;
    pfn->expectComp   = &NVOCMP_expectCompApi;
    pfn->eraseNV      = &NVOCMP_eraseNvApi;
    pfn->getFreeNV    = &NVOCMP_getFreeNvApi;
}

/**
 * @fn      NVOCMP_loadApiPtrsMin
 *
 * @brief   Global function to return function pointers for NV driver API that
 *          are supported by this module, NULL for functions not supported.
 *          This function loads the minimum necessary API functions.
 *          This should allow smaller code size.
 *
 * @param   pfn - pointer to caller's structure of NV function pointers
 *
 * @return  none
 */
void NVOCMP_loadApiPtrsMin(NVINTF_nvFuncts_t *pfn)
{
    // Load caller's structure with pointers to the NV API functions
    pfn->initNV       = &NVOCMP_initNvApi;
    pfn->compactNV    = &NVOCMP_compactNvApi;
    pfn->createItem   = NULL;
    pfn->updateItem   = NULL;
    pfn->deleteItem   = NULL;
    pfn->readItem     = &NVOCMP_readItemApi;
    pfn->readContItem = NULL;
    pfn->writeItem    = &NVOCMP_writeItemApi;
    pfn->getItemLen   = NULL;
    pfn->lockNV       = NULL;
    pfn->unlockNV     = NULL;
    pfn->doNext       = NULL;
    pfn->expectComp   = &NVOCMP_expectCompApi;
    pfn->eraseNV      = &NVOCMP_eraseNvApi;
    pfn->getFreeNV    = &NVOCMP_getFreeNvApi;
}

/**
 * @fn      NVOCMP_loadApiPtrsExt
 *
 * @brief   Global function to return function pointers for NV driver API that
 *          are supported by this module, NULL for functions not supported.
 *          This function also loads the 'extended' API function pointers.
 *
 * @param   pfn - pointer to caller's structure of NV function pointers
 *
 * @return  none
 */
void NVOCMP_loadApiPtrsExt(NVINTF_nvFuncts_t *pfn)
{
    // Load caller's structure with pointers to the NV API functions
    pfn->initNV       = &NVOCMP_initNvApi;
    pfn->compactNV    = &NVOCMP_compactNvApi;
    pfn->createItem   = &NVOCMP_createItemApi;
    pfn->updateItem   = &NVOCMP_updateItemApi;
    pfn->deleteItem   = &NVOCMP_deleteItemApi;
    pfn->readItem     = &NVOCMP_readItemApi;
    pfn->readContItem = &NVOCMP_readContItemApi;
    pfn->writeItem    = &NVOCMP_writeItemApi;
    pfn->getItemLen   = &NVOCMP_getItemLenApi;
    pfn->lockNV       = &NVOCMP_lockNvApi;
    pfn->unlockNV     = &NVOCMP_unlockNvApi;
    pfn->doNext       = &NVOCMP_doNextApi;
    pfn->expectComp   = &NVOCMP_expectCompApi;
    pfn->eraseNV      = &NVOCMP_eraseNvApi;
    pfn->getFreeNV    = &NVOCMP_getFreeNvApi;
}

/**
 * @fn      NVOCMP_setCheckVoltage
 *
 * @brief   Global function to allow user to provide a voltage check function
 *          for the driver to use. If a pointer is provided, the driver will
 *          call the provided function before flash erases and writes. The
 *          provided function should return true when the battery voltage is
 *          sufficient and vice versa. The user can withdraw their function
 *          by passing a NULL pointer to this function.
 *
 * @param   funcPtr - pointer to a function which returns a bool.
 *
 * @return  none
 */
extern void NVOCMP_setCheckVoltage(void *funcPtr)
{
#ifndef NV_LINUX
    NVOCMP_voltCheckFptr = (bool (*)()) funcPtr;
#else
    // Do nothing
    (void)NVOCMP_voltCheckFptr;
#endif
}

#ifdef NVOCMP_MIN_VDD_FLASH_MV
/**
 * @fn      NVOCMP_setLowVoltageCb
 *
 * @brief   Global function to allow user to provide a low voltage callback function
 *          for the driver to use. If a pointer is provided, the driver will
 *          call the provided function when low voltage detected.
 *
 * @param   funcPtr - pointer to a function.
 *
 * @return  none
 */
static lowVoltCbFptr NVOCMP_lowVoltCbFptr = NULL;
extern void NVOCMP_setLowVoltageCb(lowVoltCbFptr funcPtr)
{
#ifndef NV_LINUX
  NVOCMP_lowVoltCbFptr = (lowVoltCbFptr) funcPtr;
#else
    // Do nothing
    (void)NVOCMP_lowVoltCbFptr;
#endif
}

/*******************************************************************************
 * @fn      NVOCMP_checkVoltage()
 *
 * @brief   Checks the caller supplied voltage threshold against the value read
 *          from the CC26xx BATMON register.
 *
 * @param   none
 *
 * @return  false if device voltage less than limit, otherwise true
 *******************************************************************************
 */
static bool NVOCMP_checkVoltage(void)
{
    uint32_t voltage = AONBatMonBatteryVoltageGet();
    voltage = (voltage * 1000) >> AON_BATMON_BAT_FRAC_W;
    if(voltage < NVOCMP_MIN_VDD_FLASH_MV)
    {
        // Measured device voltage is below threshold
        if(NVOCMP_lowVoltCbFptr)
        {
          NVOCMP_lowVoltCbFptr(voltage);
        }
        return(false);
    }

    return(true);
}
#endif

#ifdef NVDEBUG
void NVOCMP_corruptData(uint8_t pg, uint16_t off, uint16_t len, uint8_t buf)
{
    NVS_write(NVOCMP_nvsHandle, NVOCMP_FLASHOFFSET(pg, off), (uint8_t *)&buf, len, NVS_WRITE_POST_VERIFY);
}
#endif

/******************************************************************************
 * @fn      NVOCMP_initNvApi
 *
 * @brief   API function to initialize the specified NV Flash pages
 *
 * @param   param - pointer to caller's structure of NV init parameters
 *
 * @return  NVINTF_SUCCESS or specific failure code
 */
static uint8_t NVOCMP_initNvApi(void *param)
{
    NVOCMP_ALERT(false, "NVOCMP Init. Called!")
    NVOCMP_failW = NVOCMP_failF;

    if(NVOCMP_failF == NVINTF_NOTREADY)
    {
#ifdef NVOCMP_POSIX_MUTEX
        pthread_mutexattr_t attr;
#else
        GateMutexPri_Params gateParams;
#endif

        // Only one init per device reset
        NVOCMP_failF = NVINTF_SUCCESS;
        NVOCMP_failW = NVINTF_SUCCESS;

        // Create a priority gate mutex for the NV driver
#ifdef NVOCMP_POSIX_MUTEX
        if (pthread_mutexattr_init(&attr) != 0) {
            NVOCMP_failF = NVINTF_FAILURE;
            return(NVOCMP_failF);
        }

#ifndef NV_LINUX
        attr.type = PTHREAD_MUTEX_RECURSIVE;
#else
        pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE);
#endif
        if (pthread_mutex_init(&NVOCMP_gPosixMutex, &attr) != 0)
        {
            NVOCMP_failF = NVINTF_FAILURE;
            return(NVOCMP_failF);
        }
#else
        GateMutexPri_Params_init(&gateParams);
        NVOCMP_gMutexPri = GateMutexPri_create(&gateParams, NULL);
#endif

        memset(&NVOCMP_nvHandle, 0, sizeof(NVOCMP_nvHandle_t));
        memset(&NVOCMP_nvHandle.compactInfo, 0xFF, sizeof(NVOCMP_compactInfo_t));

#ifndef NV_LINUX
#ifdef NVOCMP_MIN_VDD_FLASH_MV
        NVOCMP_setCheckVoltage((void *)&NVOCMP_checkVoltage);
#endif
        // Initialize NVS objects
        NVS_init();

        // Use default NVS_Params to open this flash region
        NVOCMP_nvsHandle = NVS_open(NVOCMP_NVS_INDEX, param);

        // Get NV hardware attributes
        NVS_getAttrs(NVOCMP_nvsHandle,&NVOCMP_nvsAttrs);
#else
        NV_LINUX_init();

        NVOCMP_nvsHandle = NVS_HANDLE;
        NVOCMP_nvsAttrs.sectorSize = FLASH_PAGE_SIZE;
        NVOCMP_nvsAttrs.regionSize = FLASH_PAGE_SIZE * NVOCMP_NVPAGES;
#endif

        NVOCMP_nvHandle.nvSize = NVOCMP_nvsAttrs.regionSize/NVOCMP_nvsAttrs.sectorSize;
        NVOCMP_nvHandle.nvSize = NVOCMP_nvHandle.nvSize > NVOCMP_NVPAGES ? NVOCMP_NVPAGES : NVOCMP_nvHandle.nvSize;
        NVOCMP_size = NVOCMP_nvHandle.nvSize;

        // Confirm NV region has expected characteristics
        if (FLASH_PAGE_SIZE != NVOCMP_nvsAttrs.sectorSize ||
                (NVOCMP_NVSIZE * FLASH_PAGE_SIZE > NVOCMP_nvsAttrs.regionSize))
        {
            NVOCMP_failF = NVINTF_FAILURE;
            NVOCMP_EXCEPTION(pg, NVINTF_FAILURE)
            return(NVOCMP_failF);
        }

        // Confirm that the NVS region opened properly
        if (NVOCMP_nvsHandle == NULL)
        {
            NVOCMP_failF = NVINTF_FAILURE;
            NVOCMP_ASSERT(false,"NVS HANDLE IS NULL")
            NVOCMP_EXCEPTION(pg, NVINTF_NOTREADY);
            return(NVOCMP_failF);
        }

        // Initialize force compaction to false
        NVOCMP_nvHandle.forceCompact = 0;

        // Look for active page and clean up the other if necessary
        NVOCMP_nvHandle.actPage = NVOCMP_NULLPAGE;
        NVOCMP_nvHandle.actOffset = FLASH_PAGE_SIZE;

        NVOCMP_initNv(&NVOCMP_nvHandle);

#if defined (NVOCMP_STATS)
        {
            uint8_t err;
            NVOCMP_diag_t diags;

            // Look for a copy of diagnostic info
            err = NVOCMP_readItemApi(diagId, 0, sizeof(diags), &diags);
            if(err == NVINTF_NOTFOUND)
            {
                // Assume this is the first time,
                memset(&diags, 0, sizeof(diags));
                // Space available for everything else
                diags.available = FLASH_PAGE_SIZE - (NVOCMP_nvHandle.actOffset +
                        NVOCMP_ITEMHDRLEN + sizeof(diags));
            }
            // Remember this reset
            diags.resets += 1;
            // Create/Update the diagnostic NV item
            NVOCMP_writeItemApi(diagId, sizeof(diags), &diags);
        }
#endif // NVOCMP_STATS
    }

    return(NVOCMP_failW);
}

/******************************************************************************
 * @fn      NVOCMP_eraseNvApi
 *
 * @brief   API function to erase whole NV pages
 *
 * @param   none
 *
 * @return  NVINTF_SUCCESS or specific failure code
 */
static uint8_t NVOCMP_eraseNvApi(void)
{
  uint8_t pg;
  uint8_t err = NVINTF_SUCCESS;

  // Check voltage if possible
  NVOCMP_FLASHACCESS(err)
  if(err)
  {
    return(err);
  }

  NVOCMP_LOCK();

  // Erase All pages before start
  for(pg = 0; pg < NVOCMP_NVSIZE; pg++)
  {
    NVOCMP_failW |= NVOCMP_erase(&NVOCMP_nvHandle, pg);
  }

  err = NVOCMP_failW;

  // initial state, set head page, act page and tail page
  NVOCMP_nvHandle.headPage = 0;
  NVOCMP_nvHandle.tailPage = NVOCMP_NVSIZE - 1;
  NVOCMP_nvHandle.actPage = 0;
  NVOCMP_nvHandle.actOffset = NVOCMP_nvHandle.pageInfo[NVOCMP_nvHandle.actPage].offset;
  NVOCMP_changePageState(&NVOCMP_nvHandle, NVOCMP_nvHandle.headPage, NVOCMP_PGRDY);
  NVOCMP_changePageState(&NVOCMP_nvHandle, NVOCMP_nvHandle.tailPage, NVOCMP_PGXDST);

#ifdef NV_LINUX
    if(err == NVINTF_SUCCESS)
    {
        NV_LINUX_save();
    }
#endif

  NVOCMP_UNLOCK(err);
}

/******************************************************************************
 * @fn      NVOCMP_getFreeNvApi
 *
 * @brief   API function to get free space in whole NV pages
 *
 * @param   none
 *
 * @return  bytes of free space
 */
static uint32_t NVOCMP_getFreeNvApi(void)
{
  uint8_t pg = NVOCMP_nvHandle.actPage;
  NVOCMP_pageHdr_t pageHdr;
  uint32_t freespace = 0;
#if (NVOCMP_NVPAGES > NVOCMP_NVTWOP)
  uint16_t nvSearched = 0;
  for(pg = NVOCMP_nvHandle.actPage; nvSearched < NVOCMP_NVSIZE; pg = NVOCMP_INCPAGE(pg))
  {
    nvSearched++;
    if(pg == NVOCMP_nvHandle.tailPage)
    {
      continue;
    }
#endif
    NVOCMP_read(pg, NVOCMP_PGHDROFS, (uint8_t *)&pageHdr, NVOCMP_PGHDRLEN);
    if((pageHdr.state == NVOCMP_PGNACT) || (pageHdr.state == NVOCMP_PGRDY) || (pageHdr.state == NVOCMP_PGACT))
    {
      freespace += (FLASH_PAGE_SIZE - NVOCMP_nvHandle.pageInfo[pg].offset);
    }
#if (NVOCMP_NVPAGES > NVOCMP_NVTWOP)
  }
#endif
  return(freespace);
}

/******************************************************************************
 * @fn      NVOCMP_compactNvApi
 *
 * @brief   API function to force NV active page compaction
 *
 * @param   minAvail - threshold size of available bytes on Flash page to do
 *                     compaction: 0 = always, >0 = minimum remaining bytes
 *
 * @return  NVINTF_SUCCESS or specific failure code
 */
static uint8_t NVOCMP_compactNvApi(uint16_t minAvail)
{
    uint8_t err = NVINTF_SUCCESS;

    // Check voltage if possible
    NVOCMP_FLASHACCESS(err)
    if(err)
    {
      return(err);
    }

    // Prevent RTOS thread contention
    NVOCMP_LOCK();
    NVOCMP_ALERT(false, "API Compaction Request.")
    err = NVOCMP_failF;
    // Check for a fatal error
    if(err == NVINTF_SUCCESS)
    {
        int16_t left;

        // Number of bytes left on active page
        left = FLASH_PAGE_SIZE - NVOCMP_nvHandle.actOffset;

        // Time to do a compaction?
        if((left < minAvail) || (minAvail == 0))
        {
            // Transfer all items to non-ACTIVE page
            (void)NVOCMP_compactPage(&NVOCMP_nvHandle, 0);
            // 'failW' indicates compaction status
            err = NVOCMP_failW;
        }
        else
        {
            // Indicate "bad" minAvail value
            err = NVINTF_BADPARAM;
        }
    }

#ifdef NV_LINUX
    if(err == NVINTF_SUCCESS)
    {
        NV_LINUX_save();
    }
#endif

    NVOCMP_UNLOCK(err);
}

//*****************************************************************************
// API Functions - NV Data Items
//*****************************************************************************

/******************************************************************************
 * @fn      NVOCMP_createItemApi
 *
 * @brief   API function to create a new NV item in Flash memory. This function
 *          will return an error if the specified item already exists.
 *
 * @param   id - NV item type identifier
 * @param   len - length of NV data
 * @param   pBuf - pointer to caller's data buffer  (NULL is illegal)
 *
 * @return  NVINTF_SUCCESS or specific failure code
 */
static uint8_t NVOCMP_createItemApi(NVINTF_itemID_t id, uint32_t len, void *pBuf)
{
    uint8_t err;
    NVOCMP_itemHdr_t iHdr;

    // Parameter Sanity Check
    if (pBuf == NULL || len == 0)
    {
        return(NVINTF_BADPARAM);
    }

    err = NVOCMP_checkItem(&id, len, &iHdr, NVOCMP_FINDSTRICT);
    if(err)
    {
      return(err);
    }

    // Check voltage if possible
    NVOCMP_FLASHACCESS(err)
    if(err)
    {
      return(err);
    }

    // Prevent RTOS thread contention
    NVOCMP_LOCK();

    err = NVOCMP_findItem(&NVOCMP_nvHandle, NVOCMP_nvHandle.actPage, NVOCMP_nvHandle.actOffset, &iHdr,
                          NVOCMP_FINDSTRICT, NULL);

    if(err == NVINTF_SUCCESS)
    {
      err = NVINTF_EXIST;
    }
    else if(err == NVINTF_NOTFOUND)
    {
      // Create the new item
      err = NVOCMP_addItem(&NVOCMP_nvHandle, &iHdr, pBuf, NVOCMP_CREATE);
      if(err != NVINTF_SUCCESS)
      {
        NVOCMP_ALERT(false, "createItem failed.")
        err = NVINTF_FAILURE;
      }
    }
    else
    {
      NVOCMP_ALERT(false, "createItem failed.")
      err = NVINTF_FAILURE;
    }

#ifdef NV_LINUX
    if(err == NVINTF_SUCCESS)
    {
        NV_LINUX_save();
    }
#endif

    NVOCMP_UNLOCK(err);
}

/******************************************************************************
 * @fn      NVOCMP_updateItemApi
 *
 * @brief   API function to update an existing NV item in Flash memory. This function
 *          will return an error if the specified item does not exist.
 *
 * @param   id - NV item type identifier
 * @param   len - length of NV data
 * @param   pBuf - pointer to caller's data buffer  (NULL is illegal)
 *
 * @return  NVINTF_SUCCESS or specific failure code
 */
static uint8_t NVOCMP_updateItemApi(NVINTF_itemID_t id, uint32_t len, void *pBuf)
{
    uint8_t err;
    NVOCMP_itemHdr_t iHdr;

    // Parameter Sanity Check
    if (pBuf == NULL || len == 0)
    {
        return(NVINTF_BADPARAM);
    }

    err = NVOCMP_checkItem(&id, len, &iHdr, NVOCMP_FINDSTRICT);
    if(err)
    {
      return(err);
    }

    // Check voltage if possible
    NVOCMP_FLASHACCESS(err)
    if(err)
    {
      return(err);
    }

    // Prevent RTOS thread contention
    NVOCMP_LOCK();

    err = NVOCMP_findItem(&NVOCMP_nvHandle, NVOCMP_nvHandle.actPage, NVOCMP_nvHandle.actOffset, &iHdr,
                          NVOCMP_FINDSTRICT, NULL);

    if(err == NVINTF_SUCCESS)
    {
      // Create the new item
      err = NVOCMP_addItem(&NVOCMP_nvHandle, &iHdr, pBuf, NVOCMP_UPDATE);
      if((err == NVINTF_SUCCESS) && (iHdr.hofs > 0))
      {
          // Mark old item as inactive
          NVOCMP_setItemInactive(&NVOCMP_nvHandle, iHdr.hpage, iHdr.hofs);

          err = NVOCMP_failW;
      }
    }
    else if(err == NVINTF_NOTFOUND)
    {
      err = NVINTF_NOTFOUND;
    }
    else
    {
      NVOCMP_ALERT(false, "updateItem failed.")
      err = NVINTF_FAILURE;
    }

#ifdef NV_LINUX
    if(err == NVINTF_SUCCESS)
    {
        NV_LINUX_save();
    }
#endif

    NVOCMP_UNLOCK(err);
}

/******************************************************************************
 * @fn      NVOCMP_deleteItemApi
 *
 * @brief   API function to delete an existing NV item from Flash memory. Note,
 *          it is inefficient to use this function to delete a range of items.
 *          The doNext call is recommended for that use case.
 *
 * @param   id - NV item type identifier
 *
 * @return  NVINTF_SUCCESS or specific failure code
 */
static uint8_t NVOCMP_deleteItemApi(NVINTF_itemID_t id)
{
    uint8_t err;
    NVOCMP_itemHdr_t iHdr;

#if defined (NVOCMP_STATS)
    if(!memcmp(&id, &diagId, sizeof(NVINTF_itemID_t)))
    {
        // Protect NV driver item(s)
        return(NVINTF_BADSYSID);
    }
#endif // NVOCMP_STATS

    err = NVOCMP_checkItem(&id, 0, &iHdr, NVOCMP_FINDSTRICT);
    if(err)
    {
      return(err);
    }

    // Check voltage if possible
    NVOCMP_FLASHACCESS(err)
    if(err)
    {
      return(err);
    }

    // Prevent RTOS thread contention
    NVOCMP_LOCK();

    err = NVOCMP_findItem(&NVOCMP_nvHandle, NVOCMP_nvHandle.actPage, NVOCMP_nvHandle.actOffset, &iHdr,
                          NVOCMP_FINDSTRICT, NULL);

    if(!err)
    {
      // Mark this item as inactive
      NVOCMP_setItemInactive(&NVOCMP_nvHandle, iHdr.hpage, iHdr.hofs);

      // Verify that item has been removed
      err = (NVOCMP_findItem(&NVOCMP_nvHandle, NVOCMP_nvHandle.actPage, NVOCMP_nvHandle.actOffset,
                             &iHdr, NVOCMP_FINDSTRICT, NULL) == NVINTF_NOTFOUND) ?
                             NVOCMP_failW : NVINTF_FAILURE;

      // If item did get deleted, report 'failW' status
      NVOCMP_ALERT(err == NVOCMP_failW, "Item delete failed.")
    }

#ifdef NV_LINUX
    if(err == NVINTF_SUCCESS)
    {
        NV_LINUX_save();
    }
#endif

    NVOCMP_UNLOCK(err);
}

/******************************************************************************
 * @fn      NVOCMP_getItemLenApi
 *
 * @brief   API function to return the length of an NV data item
 *
 * @param   id - NV item type identifier
 *
 * @return  NV item length or 0 if item not found
 */
static uint32_t NVOCMP_getItemLenApi(NVINTF_itemID_t id)
{
    uint8_t err;
    uint32_t len = 0;
    NVOCMP_itemHdr_t iHdr;

    err = NVOCMP_checkItem(&id, 0, &iHdr, NVOCMP_FINDSTRICT);
    if(err)
    {
      return(len);
    }

    // Prevent RTOS thread contention
    NVOCMP_LOCK();

    // If there was any error, report zero length
    len = (NVOCMP_findItem(&NVOCMP_nvHandle, NVOCMP_nvHandle.actPage, NVOCMP_nvHandle.actOffset, &iHdr,
                           NVOCMP_FINDSTRICT, NULL) != NVINTF_SUCCESS) ? 0 : iHdr.len;

    NVOCMP_UNLOCK(len);
}

/******************************************************************************
 * @fn      NVOCMP_readContItemApi
 *
 * @brief   API function to read data from an NV item by comparing content
 *
 * @param   id   - NV item type identifier
 * @param   ofs - offset into NV data
 * @param   rlen - length of NV data to return (0 is illegal)
 * @param   rBuf - pointer to caller's read data buffer  (NULL is illegal)
 * @param   clen - length of NV data to return (0 is illegal)
 * @param   coff - offset of content in data
 * @param   cBuf - pointer to caller's read data buffer  (NULL is illegal)
 * @param   pSubId - pointer to store sub Id  (NULL is illegal)
 *
 * @return  NVINTF_SUCCESS or specific failure code
 */
static uint8_t NVOCMP_readContItemApi(NVINTF_itemID_t id, uint16_t ofs,
                                   uint16_t rlen, void *rBuf,
                                   uint16_t clen, uint16_t coff, void *cBuf, uint16_t *pSubId)
{
    uint8_t err;
    NVOCMP_itemHdr_t iHdr;
    NVOCMP_itemInfo_t itemInfo;

    *pSubId = NVOCMP_INVALIDSUBID;
    // Parameter Sanity Check
    if (rBuf == NULL || rlen == 0 || cBuf == NULL || clen == 0 || coff > FLASH_PAGE_SIZE)
    {
        return(NVINTF_BADPARAM);
    }

    err = NVOCMP_checkItem(&id, rlen, &iHdr, NVOCMP_FINDSTRICT);
    if(err)
    {
      return(err);
    }

    // Prevent RTOS thread contention
    NVOCMP_LOCK();

    itemInfo.cBuf = cBuf;
    itemInfo.clength = clen;
    itemInfo.coff = coff;
    itemInfo.rBuf = rBuf;
    itemInfo.rlength = rlen;
    err = NVOCMP_findItem(&NVOCMP_nvHandle, NVOCMP_nvHandle.actPage, NVOCMP_nvHandle.actOffset, &iHdr,
                          NVOCMP_FINDITMID|NVOCMP_FINDCONTENT, &itemInfo);

    if(!err)
    {
        *pSubId = iHdr.subid;
    }

    NVOCMP_UNLOCK(err);
}

/******************************************************************************
 * @fn      NVOCMP_readItemApi
 *
 * @brief   API function to read data from an NV item
 *
 * @param   id   - NV item type identifier
 * @param   ofs - offset into NV data
 * @param   len - length of NV data to return (0 is illegal)
 * @param   pBuf - pointer to caller's read data buffer  (NULL is illegal)
 *
 * @return  NVINTF_SUCCESS or specific failure code
 */
static uint8_t NVOCMP_readItemApi(NVINTF_itemID_t id, uint16_t ofs, uint16_t len,
                                  void *pBuf)
{
    uint8_t err;
    NVOCMP_itemHdr_t iHdr;

    // Parameter Sanity Check
    if (pBuf == NULL || len == 0)
    {
        return(NVINTF_BADPARAM);
    }

    err = NVOCMP_checkItem(&id, len, &iHdr, NVOCMP_FINDSTRICT);
    if(err)
    {
      return(err);
    }

    // Prevent RTOS thread contention
    NVOCMP_LOCK();

    err = NVOCMP_findItem(&NVOCMP_nvHandle, NVOCMP_nvHandle.actPage, NVOCMP_nvHandle.actOffset, &iHdr,
                          NVOCMP_FINDSTRICT, NULL);

    // Read Item
    if (!err)
    {
        err = NVOCMP_readItem(&iHdr, ofs, len, pBuf, false);
    }

    NVOCMP_UNLOCK(err);
}

/******************************************************************************
 * @fn      NVOCMP_writeItemApi
 *
 * @brief   API function to write data to item, creates item if needed.
 *          Note that when writing to an existing item, data is not
 *          checked for redundancy. Data passed to this function will be
 *          written to NV. NOTE: It is not recommended to write items with
 *          SYSID 0 as this is reserved for the driver. NVOCMP will not
 *          delete items with this SYSID.
 *
 * @param   id   - NV item type identifier
 * @param   len - data buffer length to write into NV block  (0 is illegal)
 * @param   pBuf - pointer to caller's data buffer to write  (NULL is illegal)
 *
 * @return  NVINTF_SUCCESS or specific failure code
 */
static uint8_t NVOCMP_writeItemApi(NVINTF_itemID_t id, uint16_t len, void *pBuf)
{
    uint8_t err;
    NVOCMP_itemHdr_t iHdr;

    // Parameter Sanity Check
    if (pBuf == NULL || len == 0)
    {
        return(NVINTF_BADPARAM);
    }

    err  = NVOCMP_checkItem(&id, len, &iHdr, NVOCMP_FINDSTRICT);
    if(err)
    {
      return(err);
    }

    // Check voltage if possible
    NVOCMP_FLASHACCESS(err)
    if(err)
    {
      return(err);
    }

    // Prevent RTOS thread contention
    NVOCMP_LOCK();

    // Create a new item
    err = NVOCMP_addItem(&NVOCMP_nvHandle, &iHdr, pBuf, NVOCMP_WRITE);
    if((err == NVINTF_SUCCESS) && (iHdr.hofs > 0))
    {
        // Mark old item as inactive
        NVOCMP_setItemInactive(&NVOCMP_nvHandle, iHdr.hpage, iHdr.hofs);

        err = NVOCMP_failW;
    }

#ifdef NV_LINUX
    if(err == NVINTF_SUCCESS)
    {
        NV_LINUX_save();
    }
#endif

    NVOCMP_UNLOCK(err);
}

//*****************************************************************************
// Extended API Functions
//*****************************************************************************

/**
 * @fn      NVOCMP_lockNvApi
 *
 * @brief   Global function to lock the NV priority gate mutex
 *
 * @return  Key value needed to unlock the gate
 */
static int32_t NVOCMP_lockNvApi(void)
{
#ifdef NVOCMP_POSIX_MUTEX
    return(pthread_mutex_lock(&NVOCMP_gPosixMutex));
#else
    return(GateMutexPri_enter(NVOCMP_gMutexPri));
#endif
}

/**
 * @fn      NVOCMP_unlockNvApi
 *
 * @brief   Global function to unlock the NV priority gate mutex
 *
 * @return  none
 */
static void NVOCMP_unlockNvApi(int32_t key)
{
#ifdef NVOCMP_POSIX_MUTEX
    (void)key;
    pthread_mutex_unlock(&NVOCMP_gPosixMutex);
#else
    GateMutexPri_leave(NVOCMP_gMutexPri,key);
#endif
}

/******************************************************************************
 * @fn      NVOCMP_doNextApi
 *
 * @brief   API function which allows operations on batches of NV items. This
 * function provides a faster way of finding, reading, or deleting multiple
 * NV items. However, the user must first lock access to NV with lockNV() to
 * ensure consistent results. The user must take care to minimize the time NV
 * is locked if NV access is shared. User must also remember to unlock NV when
 * done with unlockNV().
 *
 * Usage Details:
 * doNext is controlled through the nvProxy item pointed to by prx
 * User will set flag bit NVINTF_DOSTART and then other flags based on the
 * desired operation. For example to find all items in system NVINTF_SYS_BLE,
 * the user would set flag bit NVINTF_DOFIND and set prx->sysid = NVINTF_SYS_BLE.
 * Then every call to doNextApi() returns with a status code and with the proxy
 * item populated with the found item if there was one. NVINTF_SUCCESS is
 * returned on a successful item operation, NVINTF_NOTFOUND is returned when a
 * matching item is not found, and other error codes can be returned.
 * Sample Code:
 *
 * // Use doNext to delete items of sysid
 * NVINTF_nvFuncts_t nvFps;
 * NVINTF_nvProxy_t nvProxy;
 * NVOCMP_loadApiPtrsExt(&nvFps);
 * nvFps.initNV(NULL);
 * nvProxy.sysid = sysid;
 * nvProxy.flag = NVINTF_DOSTART | NVINTF_DOSYSID | NVINTF_DODELETE;
 *
 * key = nvFps.lockNV();
 * while(!status)
 * {
 *     status |= nvFps.doNext(&nvProxy);
 * }
 * nvFps.unlockNV(key);
 *
 * Notes:
 * -User changes to the proxy struct will have no effect until a new search is
 * started by setting NVINTF_DOSTART
 * -On read operations, the user will supply a buffer and length into the proxy
 * -Items with system id NVINTF_SYSID_NVDRVR cannot be deleted with this API,
 * deleteItemApi must be used one an individual item basis
 *
 * @param   prx - pointer to nvProxy item which contains user inputs
 *
 * @return  NVINTF_SUCCESS or specific failure code
 */

static uint8_t NVOCMP_doNextApi(NVINTF_nvProxy_t * prx)
{
    static enum {doFind, doRead, doDelete} op = doFind;
    NVOCMP_itemHdr_t hdr;
    static uint8_t search;
    static uint8_t sPage;
    static int16_t fOfs    = FLASH_PAGE_SIZE;
    static uint16_t bufLen = 0;
    uint8_t status         = NVINTF_SUCCESS;
    int16_t iOfs           = NVOCMP_nvHandle.actOffset;

    // Sanitize inputs
    if (NULL == prx)
    {
        return(NVINTF_BADPARAM);
    }
    else if (0 == prx->flag)
    {
        return(NVINTF_BADPARAM);
    }

    // Locks NV
    NVOCMP_LOCK();

    // New search if start flag set
    if (prx->flag & NVINTF_DOSTART)
    {
        // Remove start flag
        prx->flag &= ~NVINTF_DOSTART;
        // Start at latest item
        sPage = NVOCMP_nvHandle.actPage;
        fOfs = NVOCMP_nvHandle.actOffset;
        // Read in buffer len
        bufLen = prx->len;

        // Decode flag
        if (prx->flag & NVINTF_DOSYSID)
        {
            search = NVOCMP_FINDSYSID;
        }
        else if (prx->flag & NVINTF_DOITMID)
        {
            search = NVOCMP_FINDITMID;
        }
        else if (prx->flag & NVINTF_DOANYID)
        {
            search = NVOCMP_FINDANY;
        }
        if (prx->flag & NVINTF_DOFIND)
        {
            op = doFind;
        }
        else if (prx->flag & NVINTF_DOREAD)
        {
            op = doRead;
        }
        else if (prx->flag & NVINTF_DODELETE)
        {
            op = doDelete;
        }
    }

    hdr.sysid = prx->sysid;
    hdr.itemid = prx->itemid;
    hdr.subid = prx->subid;

    // Look for item
    if (!NVOCMP_findItem(&NVOCMP_nvHandle, sPage, fOfs, &hdr, search, NULL))
    {
        iOfs = hdr.hofs;
        // store its attributes
        prx->sysid  = hdr.sysid;
        prx->itemid = hdr.itemid;
        prx->subid  = hdr.subid;
        prx->len    = hdr.len;
        // start from this item on next findItem()
        fOfs = iOfs - hdr.len;
        sPage = hdr.hpage;

        // Do operation based on flag
        switch (op)
        {
        case doFind:
            // nothing, we already stored its info
            break;
        case doRead:
            // read item into user supplied buffer
            if (prx->buffer != NULL && hdr.len <= bufLen)
            {
                status = NVOCMP_readItem(&hdr, 0, hdr.len, prx->buffer, false);
            }
            break;
        case doDelete:
            if (prx->sysid != NVINTF_SYSID_NVDRVR)
            {
                NVOCMP_setItemInactive(&NVOCMP_nvHandle, hdr.hpage, iOfs);
            }
            break;
        default:
            NVOCMP_ALERT(false, "doNext flag is invalid.")
            status = NVINTF_BADPARAM;
        }
    }
    else
    {
        // No more items match, done.
        status = NVINTF_NOTFOUND;
    }

    // Unlocks NV
    NVOCMP_UNLOCK(status);
}

/******************************************************************************
 * @fn      NVOCMP_expectCompApi
 *
 * @brief   API function to check if compaction will happen if a data with size = len is written
 *
 * @param   len - data buffer length to write into NV block
 *
 * @return  true or false
 */
static bool NVOCMP_expectCompApi(uint16_t len)
{
  uint8_t dstPg;
  uint16_t iLen;
  bool compact = false;

  if(len)
  {
    iLen = NVOCMP_ITEMHDRLEN + len;
    dstPg = NVOCMP_getDstPage(&NVOCMP_nvHandle, iLen);

    if(dstPg == NVOCMP_NULLPAGE)
    {
      compact = true;
    }
  }
  return(compact);
}

//*****************************************************************************
// Local NV Driver Utility Functions
//*****************************************************************************

#ifdef NVOCMP_GPRAM
/******************************************************************************
* @fn      NVOCMP_disableCache
*
* @brief   Local function to disable cache
*
* @param   vm - pointer to mode storage
*
* @return  none
*/
static void NVOCMP_disableCache(uint32_t *vm)
{
    // Save current cache mode
    *vm = VIMSModeGet(VIMS_BASE) & VIMS_STAT_MODE_M;
    // Disable the cache
    VIMSModeSet(VIMS_BASE, VIMS_MODE_DISABLED);
    // Wait until it is
    while(VIMSModeGet(VIMS_BASE) != VIMS_MODE_DISABLED);
}

/******************************************************************************
* @fn      NVOCMP_restoreCache
*
* @brief   Local function to disable cache
*
* @param   vm - mode
*
* @return  none
*/
static void NVOCMP_restoreCache(uint32_t vm)
{
    // Restore cache to previous state
    VIMSModeSet(VIMS_BASE, vm);
}
#endif

#if (NVOCMP_NVPAGES > NVOCMP_NVTWOP)
/******************************************************************************
 * @fn      NVOCMP_findPage
 *
 * @brief   Local function to find page with specified state
 *
 * @param   state - page state to find
 *
 * @return  page number or NVOCMP_NULLPAGE
 */
static uint8_t NVOCMP_findPage(NVOCMP_pageState_t state)
{
  uint8_t pg;
  NVOCMP_pageHdr_t pageHdr;

  for(pg = 0; pg < NVOCMP_NVSIZE; pg++)
  {
    // Get page header
    NVOCMP_read(pg, NVOCMP_PGHDROFS, (uint8_t *)&pageHdr, NVOCMP_PGHDRLEN);
    if(pageHdr.state == state)
    {
      return(pg);
    }
  }
  return(NVOCMP_NULLPAGE);
}
#endif

#if (NVOCMP_NVPAGES > NVOCMP_NVONEP)
#if !defined(NVOCMP_MIGRATE_DISABLED)
/******************************************************************************
 * @fn      NVOCMP_migratePage
 *
 * @brief   Local function to find page with specified state
 *
 * @param   pNvHandle - pointer to NV handle
 * @param   page - page to convert
 *
 * @return  none
 */
static void NVOCMP_migratePage(NVOCMP_nvHandle_t *pNvHandle, uint8_t page)
{
  uint8_t dstPg;
  uint8_t tmp;
  uint16_t offset1;
  uint16_t offset2;
  NVOCMP_itemHdr_t iHdr;

  offset1 = pNvHandle->pageInfo[page].offset;
  offset2 = NVOCTP_PGDATAOFS;
  if(offset1 - NVOCTP_PGDATAOFS > FLASH_PAGE_SIZE - NVOCMP_PGDATAOFS)
  {
    offset2 = offset1;
    while(offset2 - NVOCTP_PGDATAOFS > FLASH_PAGE_SIZE - NVOCMP_PGDATAOFS)
    {
      NVOCMP_findItem(pNvHandle, page, offset2, &iHdr, NVOCMP_FINDANY, NULL);
      offset2 = iHdr.hofs - iHdr.len;
    }
  }
  if((pNvHandle->nvSize <= NVOCMP_NVTWOP) && (offset2 != NVOCTP_PGDATAOFS))
  {
    offset1 = offset2;
    offset2 = NVOCTP_PGDATAOFS;
  }

  dstPg = page;
  // copy from offset2 to top
  if(offset2 - NVOCTP_PGDATAOFS > 0)
  {
    dstPg = NVOCMP_INCPAGE(dstPg);
    NVOCMP_copyItem(page, dstPg, NVOCTP_PGDATAOFS, NVOCMP_PGDATAOFS, offset2 - NVOCTP_PGDATAOFS);
    NVOCMP_changePageState(pNvHandle, dstPg, NVOCMP_PGFULL);
    pNvHandle->pageInfo[dstPg].allActive = NVOCMP_SOMEINACTIVE;
    pNvHandle->pageInfo[dstPg].offset = NVOCMP_PGDATAOFS + offset2 - NVOCTP_PGDATAOFS;
    pNvHandle->actPage = dstPg;
    pNvHandle->actOffset = pNvHandle->pageInfo[dstPg].offset;
    tmp = NVOCMP_readByte(dstPg, NVOCMP_PGHDRVER);
    tmp &= ~NVOCMP_ALLACTIVE;
    NVOCMP_writeByte(dstPg, NVOCMP_PGHDRVER, tmp);
  }
  // copy from offset 1 to offset2
  if(offset1 - offset2 > 0)
  {
    dstPg = NVOCMP_INCPAGE(dstPg);
    NVOCMP_copyItem(page, dstPg, offset2, NVOCMP_PGDATAOFS, offset1 - offset2);
    NVOCMP_changePageState(pNvHandle, dstPg, NVOCMP_PGFULL);
    pNvHandle->pageInfo[dstPg].allActive = NVOCMP_SOMEINACTIVE;
    pNvHandle->pageInfo[dstPg].offset = NVOCMP_PGDATAOFS + offset1 - offset2;
    pNvHandle->actPage = dstPg;
    pNvHandle->actOffset = pNvHandle->pageInfo[dstPg].offset;
    tmp = NVOCMP_readByte(dstPg, NVOCMP_PGHDRVER);
    tmp &= ~NVOCMP_ALLACTIVE;
    NVOCMP_writeByte(dstPg, NVOCMP_PGHDRVER, tmp);
  }

  NVOCMP_failW |= NVOCMP_erase(pNvHandle, page);
  pNvHandle->tailPage = page;
  pNvHandle->headPage = NVOCMP_INCPAGE(page);
  NVOCMP_changePageState(pNvHandle, page, NVOCMP_PGXDST);
}
#endif
#endif

#if (NVOCMP_NVPAGES > NVOCMP_NVTWOP)
/******************************************************************************
 * @fn      NVOCMP_scanPage
 *
 * @brief   Local function to scan page to get page information
 *
 * @param   pNvHandle - pointer to NV handle
 * @param   pg - page to scan
 * @param   pPageInfo - page info pointer
 *
 * @return  NVINTF_SUCCESS or specific failure code
 */
static uint8_t NVOCMP_scanPage(NVOCMP_nvHandle_t *pNvHandle, uint8_t pg,
                               NVOCMP_pageInfo_t *pPageInfo)
{
  uint32_t pageHdr;
  NVOCMP_compactHdr_t thisHdr;
  NVOCMP_compactHdr_t startHdr;
  NVOCMP_compactHdr_t endHdr;
  NVOCMP_pageHdr_t *pHdr = (NVOCMP_pageHdr_t *)&pageHdr;

  // Get page header
  NVOCMP_read(pg, NVOCMP_PGHDROFS, (uint8_t *)pHdr, NVOCMP_PGHDRLEN);
#if ((NVOCMP_NVPAGES != NVOCMP_NVONEP) && !defined(NVOCMP_MIGRATE_DISABLED))
  uint8_t version = (pHdr->version << 2)|(pHdr->allActive);
  if((pHdr->signature == NVOCTP_SIGNATURE) && (version == NVOCTP_VERSION))
  {
    pPageInfo->state = pHdr->state;
    pPageInfo->cycle = pHdr->cycle;
    pPageInfo->allActive = NVOCMP_SOMEINACTIVE;
    pPageInfo->mode = NVOCMP_PGNORMAL;
    pPageInfo->offset = NVOCMP_findOffset(pg, FLASH_PAGE_SIZE);
    return(NVINTF_SUCCESS);
  }
#endif

  NVOCMP_getCompactHdr(pg, THISPAGEHDR, &thisHdr);
  NVOCMP_getCompactHdr(pg, XSRCSTARTHDR, &startHdr);
  NVOCMP_getCompactHdr(pg, XSRCENDHDR, &endHdr);
  uint8_t corruptFlag = ((pHdr->state != NVOCMP_PGNACT)
                      && (pHdr->state != NVOCMP_PGXDST)
                      && (pHdr->state != NVOCMP_PGRDY)
                      && (pHdr->state != NVOCMP_PGACT)
                      && (pHdr->state != NVOCMP_PGFULL)
                      && (pHdr->state != NVOCMP_PGXSRC));

  if(corruptFlag || (pHdr->version != NVOCMP_VERSION) || (pHdr->signature != NVOCMP_SIGNATURE))
  {
    // NV page and NV driver versions are different
    NVOCMP_ALERT(false, "Corrupted or Version/Signature mismatch.")
    NVOCMP_EXCEPTION(pg, NVINTF_BADVERSION);
    NVOCMP_failW = NVOCMP_erase(pNvHandle, pg);
    if(NVOCMP_failW == NVINTF_SUCCESS)
    {
      // Get page header
      NVOCMP_read(pg, NVOCMP_PGHDROFS, (uint8_t *)pHdr, NVOCMP_PGHDRLEN);
      NVOCMP_getCompactHdr(pg, THISPAGEHDR, &thisHdr);
      NVOCMP_getCompactHdr(pg, XSRCSTARTHDR, &startHdr);
      NVOCMP_getCompactHdr(pg, XSRCENDHDR, &endHdr);
    }
    else if(NVOCMP_failW == NVINTF_LOWPOWER)
    {
      return(NVINTF_LOWPOWER);
    }
    else
    {
      return(NVINTF_FAILURE);
    }
  }

  pPageInfo->state = pHdr->state;
  pPageInfo->cycle = pHdr->cycle;
  pPageInfo->allActive = pHdr->allActive;
  pPageInfo->mode = thisHdr.page;
  if((pPageInfo->state == NVOCMP_PGNACT) || (pPageInfo->state == NVOCMP_PGXDST))
  {
    pPageInfo->offset = NVOCMP_PGDATAOFS;
  }
  else if(thisHdr.pageOffset != NVOCMP_NULLOFFSET)
  {
    pPageInfo->offset = thisHdr.pageOffset;
  }
  else
  {
    pPageInfo->offset = NVOCMP_findOffset(pg, FLASH_PAGE_SIZE);
  }
  pPageInfo->sPage = startHdr.page;
  pPageInfo->sOffset = startHdr.pageOffset;
  pPageInfo->ePage = endHdr.page;
  pPageInfo->eOffset = endHdr.pageOffset;
  return(NVINTF_SUCCESS);
}
#else
/******************************************************************************
 * @fn      NVOCMP_scanPage
 *
 * @brief   Local function to scan page to get page information
 *
 * @param   pNvHandle - pointer to NV handle
 * @param   pg - page to scan
 * @param   pPageInfo - page info pointer
 *
 * @return  NVINTF_SUCCESS or specific failure code
 */
static uint8_t NVOCMP_scanPage(NVOCMP_nvHandle_t *pNvHandle, uint8_t pg,
                               NVOCMP_pageInfo_t *pPageInfo)
{
  uint32_t pageHdr;
  NVOCMP_pageHdr_t *pHdr = (NVOCMP_pageHdr_t *)&pageHdr;

  // Get page header
  NVOCMP_read(pg, NVOCMP_PGHDROFS, (uint8_t *)pHdr, NVOCMP_PGHDRLEN);
  pPageInfo->state = pHdr->state;
  pPageInfo->cycle = pHdr->cycle;
#if ((NVOCMP_NVPAGES > NVOCMP_NVONEP) && !defined(NVOCMP_MIGRATE_DISABLED))
  uint8_t version = (pHdr->version << 2)|(pHdr->allActive);
  if((pHdr->signature == NVOCTP_SIGNATURE) && (version == NVOCTP_VERSION))
  {
    pPageInfo->allActive = NVOCMP_SOMEINACTIVE;
    pPageInfo->mode = NVOCMP_PGNORMAL;
    pPageInfo->offset = NVOCMP_findOffset(pg, FLASH_PAGE_SIZE);
  }
#endif
  if ((pHdr->version == NVOCMP_VERSION) || (pHdr->signature == NVOCMP_SIGNATURE))
  {
    pPageInfo->allActive = pHdr->allActive;
    if((pPageInfo->state == NVOCMP_PGNACT) || (pPageInfo->state == NVOCMP_PGXDST) || (pPageInfo->state == NVOCMP_PGRDY))
    {
      pPageInfo->offset = NVOCMP_PGDATAOFS;
    }
    else
    {
      pPageInfo->offset = NVOCMP_findOffset(pg, FLASH_PAGE_SIZE);
    }
  }
  return(NVINTF_SUCCESS);
}
#endif

#if (NVOCMP_NVPAGES > NVOCMP_NVTWOP)
/******************************************************************************
 * @fn      NVOCMP_findDstPage
 *
 * @brief   Local function to find dst page after power loss
 *
 * @param   pNvHandle - pointer to NV handle
 *
 * @return  page
 */
static uint8_t NVOCMP_findDstPage(NVOCMP_nvHandle_t *pNvHandle)
{
  uint8_t pg;
  for(pg = 0; pg < NVOCMP_NVSIZE; pg++)
  {
    if(pNvHandle->pageInfo[pg].mode == NVOCMP_PGCDST)
    {
      return(pg);
    }
  }
  return(NVOCMP_NVSIZE);
}
#endif

#if (NVOCMP_NVPAGES > NVOCMP_NVTWOP)
/******************************************************************************
 * @fn      NVOCMP_initNv
 *
 * @brief   Local function to init whole NV area
 *
 * @param   pNvHandle - pointer to NV handle
 *
 * @return  none
 */
static void NVOCMP_initNv(NVOCMP_nvHandle_t *pNvHandle)
{
  uint8_t status;
  uint8_t pg;
  uint8_t tmpPg;
  NVOCMP_initAction_t action = NVOCMP_NORMAL_INIT;
  NVOCMP_pageInfo_t *pPageInfo;
  uint8_t noPgNact = 0;
  uint8_t noPgXdst = 0;
  uint8_t noPgRdy = 0;
  uint8_t noPgAct = 0;
  uint8_t noPgFull = 0;
  uint8_t noPgXsrc = 0;
  uint8_t noPgNdef = 0;
  uint16_t pgXdst = NVOCMP_NULLPAGE;
  uint16_t pgRdy = NVOCMP_NULLPAGE;
  uint16_t pgAct = NVOCMP_NULLPAGE;
  uint16_t pgNact = NVOCMP_NULLPAGE;
#if !defined(NVOCMP_MIGRATE_DISABLED)
  uint8_t noPgLeg = 0;
  uint16_t pgLegAct = NVOCMP_NULLPAGE;
  uint16_t pgLegXsrc = NVOCMP_NULLPAGE;
#endif
  uint16_t cleanPages = 0;

  // Scan Pages
  for(pg = 0; pg < NVOCMP_NVSIZE; pg++)
  {
    pPageInfo = &pNvHandle->pageInfo[pg];
    status = NVOCMP_scanPage(pNvHandle, pg, pPageInfo);
    if(status != NVINTF_SUCCESS)
    {
      return;
    }
    if(pPageInfo->state == NVOCMP_PGNACT)
    {
      noPgNact++;
      pgNact = pg;
    }
    else if(pPageInfo->state == NVOCMP_PGXDST)
    {
      pgXdst = pg;
      noPgXdst++;
    }
    else if(pPageInfo->state == NVOCMP_PGRDY)
    {
      pgRdy = pg;
      noPgRdy++;
    }
    else if(pPageInfo->state == NVOCMP_PGACT)
    {
      pgAct = pg;
      noPgAct++;
    }
    else if(pPageInfo->state == NVOCMP_PGFULL)
    {
      noPgFull++;
    }
    else if(pPageInfo->state == NVOCMP_PGXSRC)
    {
      noPgXsrc++;
    }
#if ((NVOCMP_NVPAGES != NVOCMP_NVONEP) && !defined(NVOCMP_MIGRATE_DISABLED))
    else if(pPageInfo->state == NVOCTP_PGACTIVE)
    {
      pgLegAct = pg;
      noPgLeg++;
    }
    else if(pPageInfo->state == NVOCTP_PGXFER)
    {
      pgLegXsrc = pg;
      noPgLeg++;
    }
#endif
    else
    {
      noPgNdef++;
    }
  }

  // Decide Action based on Page Informations
#if ((NVOCMP_NVPAGES != NVOCMP_NVONEP) && !defined(NVOCMP_MIGRATE_DISABLED))
  if(noPgLeg > 0)
  {
    action = NVOCMP_NORMAL_MIGRATE;
  }
  else
#endif
  if((noPgNdef > 0) ||(noPgXdst > 1) || (noPgXsrc > 1) || (noPgRdy > 1))
  {
    // This should not happen
    NVOCMP_ASSERT(false, "Something wrong serious");
    action = NVOCMP_FORCE_CLEAN;
  }
  else
  {
    if(noPgNact == NVOCMP_NVSIZE)
    {
      action = NVOCMP_NORMAL_INIT;
    }
    else if(noPgXsrc)
    {
      // Power lost during compaction in progress
      if(noPgXdst)
      {
        action = NVOCMP_RECOVER_COMPACT;
      }
      // Corrupted due to power lost while writing onto XDST page and erased the XDST page
      else if(noPgNact)
      {
        pgXdst = pgNact;
        action = NVOCMP_RECOVER_COMPACT;
      }
      // Power lost after compaction done, but before erasing PGXSRC page
      else
      {
        if(NVOCMP_findDstPage(pNvHandle) < NVOCMP_NVSIZE)
        {
          action = NVOCMP_RECOVER_ERASE;
        }
        else
        {
          action = NVOCMP_ERROR_UNKNOWN;
        }
      }
    }
    else if(noPgXdst)
    {
      action = NVOCMP_NORMAL_RESUME;
    }
    else if(noPgAct || noPgFull)
    {
      if(NVOCMP_findDstPage(pNvHandle) < NVOCMP_NVSIZE)
      {
        action = NVOCMP_RECOVER_ERASE;
      }
      else if(noPgNact)
      {
        pgXdst = pgNact;
        NVOCMP_changePageState(pNvHandle, pNvHandle->tailPage, NVOCMP_PGXDST);
        action = NVOCMP_NORMAL_RESUME;
      }
      else
      {
        action = NVOCMP_ERROR_UNKNOWN;
      }
    }
    else
    {
      // This case should be considered more
      NVOCMP_ASSERT(false, "Something wrong serious");
      action = NVOCMP_FORCE_CLEAN;
    }
  }

  gAction = action;

  switch(action)
  {
  case NVOCMP_FORCE_CLEAN :
      // Erase All pages before start
      for(pg = 0; pg < NVOCMP_NVSIZE; pg++)
      {
        NVOCMP_failW |= NVOCMP_erase(pNvHandle, pg);
      }
      // init should be followed by force clean
  case NVOCMP_NORMAL_INIT :
      // initial state, set head page, act page and tail page
      pNvHandle->headPage = 0;
      pNvHandle->tailPage = NVOCMP_NVSIZE - 1;
      pNvHandle->actPage = 0;
      pNvHandle->actOffset = pNvHandle->pageInfo[pNvHandle->actPage].offset;
      NVOCMP_changePageState(pNvHandle, pNvHandle->headPage, NVOCMP_PGRDY);
      NVOCMP_changePageState(pNvHandle, pNvHandle->tailPage, NVOCMP_PGXDST);
      break;
  case NVOCMP_NORMAL_RESUME :
      // resume state, set head page, act page and tail page
      pNvHandle->tailPage = pgXdst;
      pNvHandle->headPage = NVOCMP_INCPAGE(pgXdst);
      if(pgAct != NVOCMP_NULLPAGE)
      {
        pNvHandle->actPage = pgAct;
        pNvHandle->actOffset = pNvHandle->pageInfo[pgAct].offset;

        NVOCMP_itemHdr_t iHdr;
        int8_t status;
        if(pNvHandle->actOffset > NVOCMP_PGDATAOFS + NVOCMP_ITEMHDRLEN)
        {
          NVOCMP_readHeader(pNvHandle->actPage, pNvHandle->actOffset - NVOCMP_ITEMHDRLEN , &iHdr, false);
          if(iHdr.stats & NVOCMP_FOLLOWBIT)
          {
            status = NVOCMP_findItem(pNvHandle, pNvHandle->actPage, pNvHandle->actOffset - NVOCMP_ITEMHDRLEN - iHdr.len,
                            &iHdr, NVOCMP_FINDSTRICT, NULL);
            if((status == NVINTF_SUCCESS) && (iHdr.hofs > 0))
            {
              NVOCMP_setItemInactive(pNvHandle, iHdr.hpage, iHdr.hofs);
            }
          }
          else
          {
            NVOCMP_compactPage(pNvHandle, 0);
          }
        }
      }
      else if(pgRdy != NVOCMP_NULLPAGE)
      {
        pNvHandle->actPage = pgRdy;
        pNvHandle->actOffset = pNvHandle->pageInfo[pgRdy].offset;
      }
      else
      {
        pNvHandle->actPage = pNvHandle->headPage;
        pNvHandle->actOffset = pNvHandle->pageInfo[pNvHandle->actPage].offset;
      }
#ifdef NVOCMP_COMPACT_WHEN_RESUME
      NVOCMP_compactPage(pNvHandle, 0);
#endif
      break;
  case NVOCMP_RECOVER_COMPACT :
      pNvHandle->tailPage = pgXdst;
      pNvHandle->headPage = NVOCMP_INCPAGE(pgXdst);
      NVOCMP_failW = NVOCMP_erase(pNvHandle, pgXdst);
      NVOCMP_changePageState(pNvHandle, pgXdst, NVOCMP_PGXDST);
      pNvHandle->forceCompact = 1;
      NVOCMP_compactPage(pNvHandle, 0);
      break;
  case NVOCMP_RECOVER_ERASE :
      pg = NVOCMP_findDstPage(pNvHandle);
      pNvHandle->compactInfo.xDstPage = pg;
      pNvHandle->compactInfo.xSrcSPage = pNvHandle->pageInfo[pg].sPage;
      pNvHandle->compactInfo.xSrcEPage = pNvHandle->pageInfo[pg].ePage;
      cleanPages = NVOCMP_cleanPage(pNvHandle);
      pNvHandle->tailPage = NVOCMP_ADDPAGE(pg, cleanPages);
      pNvHandle->headPage = NVOCMP_INCPAGE(pNvHandle->tailPage);

      tmpPg = NVOCMP_findPage(NVOCMP_PGACT);
      if(tmpPg == NVOCMP_NULLPAGE)
      {
        tmpPg = NVOCMP_findPage(NVOCMP_PGRDY);
        if(tmpPg == NVOCMP_NULLPAGE)
        {
          tmpPg = NVOCMP_findPage(NVOCMP_PGNACT);
          if(tmpPg == NVOCMP_NULLPAGE)
          {
            tmpPg = NVOCMP_findPage(NVOCMP_PGFULL);
            if(tmpPg == NVOCMP_NULLPAGE)
            {
              tmpPg = pNvHandle->headPage;
            }
          }
        }
      }

      pNvHandle->actPage = tmpPg;
      pNvHandle->actOffset = pNvHandle->pageInfo[pNvHandle->actPage].offset;
      NVOCMP_changePageState(pNvHandle, pNvHandle->tailPage, NVOCMP_PGXDST);
      break;
#if !defined(NVOCMP_MIGRATE_DISABLED)
  case NVOCMP_NORMAL_MIGRATE :
      if(pgLegAct != NVOCMP_NULLPAGE)
      {
        pNvHandle->actPage = pgLegAct;
      }
      else
      {
        pNvHandle->actPage = pgLegXsrc;
      }
      pNvHandle->headPage = pNvHandle->actPage;
      pNvHandle->tailPage = NVOCMP_DECPAGE(pNvHandle->headPage);
      pNvHandle->actOffset = pNvHandle->pageInfo[pNvHandle->actPage].offset;
      NVOCMP_migratePage(pNvHandle, pNvHandle->actPage);
      NVOCMP_compactPage(pNvHandle, 0);
      break;
#endif
  case NVOCMP_ERROR_UNKNOWN :
      /* When this error happens, NV area should be erased to restart.
       * This while loop is for only debug purpose */
      NVOCMP_ASSERT1(0);
  default :
      break;
  }
}
#endif
#if (NVOCMP_NVPAGES == NVOCMP_NVTWOP)
/******************************************************************************
 * @fn      NVOCMP_initNv
 *
 * @brief   Local function to init whole NV area
 *
 * @param   pNvHandle - pointer to NV handle
 *
 * @return  none
 */
static void NVOCMP_initNv(NVOCMP_nvHandle_t *pNvHandle)
{
  uint8_t status;
  NVOCMP_itemHdr_t iHdr;
  uint8_t pg;
  NVOCMP_initAction_t action;
  NVOCMP_pageInfo_t *pPageInfo;
  uint16_t pgAct;
#if !defined(NVOCMP_MIGRATE_DISABLED)
  uint8_t noPgLeg = 0;
#endif
  bool compact = false;

  // Scan Pages
  pNvHandle->xsrcPage = NVOCMP_NULLPAGE;
  pNvHandle->tailPage = NVOCMP_NULLPAGE;

  for(pg = 0; pg < NVOCMP_NVSIZE; pg++)
  {
    pPageInfo = &pNvHandle->pageInfo[pg];
    NVOCMP_scanPage(pNvHandle, pg, pPageInfo);
#if ((NVOCMP_NVPAGES != NVOCMP_NVONEP) && !defined(NVOCMP_MIGRATE_DISABLED))
    if(pPageInfo->state == NVOCTP_PGACTIVE)
    {
      pNvHandle->actPage = pg;
      noPgLeg++;
    }
    else if(pPageInfo->state == NVOCTP_PGXFER)
    {
      pNvHandle->xsrcPage = pg;
      noPgLeg++;
    }
    else
#endif
    if((pPageInfo->state == NVOCMP_PGACT) || (pPageInfo->state == NVOCMP_PGFULL))
    {
      pNvHandle->actPage = pg;
    }
    else if(pPageInfo->state == NVOCMP_PGXSRC)
    {
      pNvHandle->xsrcPage = pg;
    }
    else if(pPageInfo->state == NVOCMP_PGXDST)
    {
      pNvHandle->tailPage = pg;
    }
  }

  action = NVOCMP_NORMAL_INIT;
  // Decide Action based on Page Informations
#if ((NVOCMP_NVPAGES != NVOCMP_NVONEP) && !defined(NVOCMP_MIGRATE_DISABLED))
  if(noPgLeg > 0)
  {
    action = NVOCMP_NORMAL_MIGRATE;
  }
  else
  {
#endif
    if(pNvHandle->actPage != NVOCMP_NULLPAGE)
    {
      action = NVOCMP_NORMAL_RESUME;
    }
    else if(pNvHandle->xsrcPage != NVOCMP_NULLPAGE)
    {
      pNvHandle->actPage = pNvHandle->xsrcPage;
      action = NVOCMP_RECOVER_COMPACT;
    }
    else if(pNvHandle->tailPage != NVOCMP_NULLPAGE)
    {
      pgAct = NVOCMP_INCPAGE(pNvHandle->tailPage);
      if(pNvHandle->pageInfo[pgAct].offset)
      {
        pNvHandle->actPage = pgAct;
        action = NVOCMP_RECOVER_COMPACT;
      }
      else
      {
        pNvHandle->tailPage = NVOCMP_NULLPAGE;
      }
    }

    if(pNvHandle->actPage == NVOCMP_NULLPAGE)
    {
      pNvHandle->actPage = 0;
      NVOCMP_failW |= NVOCMP_erase(pNvHandle, 0);
      NVOCMP_changePageState(pNvHandle, 0, NVOCMP_PGRDY);
    }

    if(pNvHandle->tailPage == NVOCMP_NULLPAGE)
    {
      pNvHandle->tailPage = NVOCMP_INCPAGE(pNvHandle->actPage);;
      NVOCMP_failW |= NVOCMP_erase(pNvHandle, pNvHandle->tailPage);
      NVOCMP_changePageState(pNvHandle, pNvHandle->tailPage, NVOCMP_PGXDST);
    }

    pNvHandle->headPage = pNvHandle->actPage;
    pNvHandle->actOffset = pNvHandle->pageInfo[pNvHandle->actPage].offset;
#if ((NVOCMP_NVPAGES != NVOCMP_NVONEP) && !defined(NVOCMP_MIGRATE_DISABLED))
  }
#endif

  gAction = action;

  switch(action)
  {
  case NVOCMP_NORMAL_RESUME :
      // resume state, set head page, act page and tail page
      if(pNvHandle->actOffset > NVOCMP_PGDATAOFS + NVOCMP_ITEMHDRLEN)
      {
        NVOCMP_readHeader(pNvHandle->actPage, pNvHandle->actOffset - NVOCMP_ITEMHDRLEN , &iHdr, false);
        if(iHdr.stats & NVOCMP_FOLLOWBIT)
        {
          status = NVOCMP_findItem(pNvHandle, pNvHandle->actPage, pNvHandle->actOffset - NVOCMP_ITEMHDRLEN - iHdr.len,
                          &iHdr, NVOCMP_FINDSTRICT, NULL);
          if((status == NVINTF_SUCCESS) && (iHdr.hofs > 0))
          {
            NVOCMP_setItemInactive(pNvHandle, iHdr.hpage, iHdr.hofs);
          }
        }
        else
        {
          pNvHandle->forceCompact = 1;
          compact = true;
        }
      }
#ifdef NVOCMP_COMPACT_WHEN_RESUME
      compact = true;
#endif
      break;
#if !defined(NVOCMP_MIGRATE_DISABLED)
  case NVOCMP_NORMAL_MIGRATE :
      if(pNvHandle->actPage == NVOCMP_NULLPAGE)
      {
        pNvHandle->actPage = pNvHandle->xsrcPage;
      }
      pNvHandle->headPage = pNvHandle->actPage;
      pNvHandle->tailPage = NVOCMP_DECPAGE(pNvHandle->headPage);
      pNvHandle->actOffset = pNvHandle->pageInfo[pNvHandle->actPage].offset;
      NVOCMP_failW = NVOCMP_erase(pNvHandle, pNvHandle->tailPage);
      NVOCMP_migratePage(pNvHandle, pNvHandle->actPage);
      compact = true;
      break;
#endif
  case NVOCMP_RECOVER_COMPACT :
    pNvHandle->forceCompact = 1;
    compact = true;
    break;
  default :
      break;
  }

  if(compact)
  {
    NVOCMP_compactPage(pNvHandle, 0);
  }
}
#endif

#if (NVOCMP_NVPAGES == NVOCMP_NVONEP)
/******************************************************************************
 * @fn      NVOCMP_initNv
 *
 * @brief   Local function to init whole NV area
 *
 * @param   pNvHandle - pointer to NV handle
 *
 * @return  none
 */
static void NVOCMP_initNv(NVOCMP_nvHandle_t *pNvHandle)
{
  uint8_t status;
  NVOCMP_itemHdr_t iHdr;
  NVOCMP_initAction_t action;
  NVOCMP_pageInfo_t *pPageInfo;
  bool compact = false;

  // Scan Pages
  pNvHandle->xsrcPage = NVOCMP_NULLPAGE;
  pNvHandle->tailPage = NVOCMP_NULLPAGE;

  pPageInfo = &pNvHandle->pageInfo[0];
  NVOCMP_scanPage(pNvHandle, 0, pPageInfo);
  if((pPageInfo->state == NVOCMP_PGACT) || (pPageInfo->state == NVOCMP_PGFULL))
  {
    pNvHandle->actPage = 0;
  }
  else if(pPageInfo->state == NVOCMP_PGXSRC)
  {
    pNvHandle->xsrcPage = 0;
  }

  action = NVOCMP_NORMAL_INIT;
  // Decide Action based on Page Informations
  if(pNvHandle->actPage != NVOCMP_NULLPAGE)
  {
    action = NVOCMP_NORMAL_RESUME;
  }
  else if(pNvHandle->xsrcPage != NVOCMP_NULLPAGE)
  {
    pNvHandle->actPage = 0;
    action = NVOCMP_RECOVER_COMPACT;
  }

  if(pNvHandle->actPage == NVOCMP_NULLPAGE)
  {
    pNvHandle->actPage = 0;
    NVOCMP_failW |= NVOCMP_erase(pNvHandle, 0);
    NVOCMP_changePageState(pNvHandle, 0, NVOCMP_PGRDY);
  }

  pNvHandle->tailPage = 0;
  pNvHandle->headPage = 0;
  pNvHandle->actOffset = pNvHandle->pageInfo[0].offset;

  gAction = action;

  switch(action)
  {
  case NVOCMP_NORMAL_RESUME :
      // resume state, set head page, act page and tail page
      if(pNvHandle->actOffset > NVOCMP_PGDATAOFS + NVOCMP_ITEMHDRLEN)
      {
        NVOCMP_readHeader(pNvHandle->actPage, pNvHandle->actOffset - NVOCMP_ITEMHDRLEN , &iHdr, false);
        if(iHdr.stats & NVOCMP_FOLLOWBIT)
        {
          status = NVOCMP_findItem(pNvHandle, pNvHandle->actPage, pNvHandle->actOffset - NVOCMP_ITEMHDRLEN - iHdr.len,
                          &iHdr, NVOCMP_FINDSTRICT, NULL);
          if((status == NVINTF_SUCCESS) && (iHdr.hofs > 0))
          {
            NVOCMP_setItemInactive(pNvHandle, iHdr.hpage, iHdr.hofs);
          }
        }
        else
        {
          pNvHandle->forceCompact = 1;
          compact = true;
        }
      }
#ifdef NVOCMP_COMPACT_WHEN_RESUME
      compact = true;
#endif
      break;
  case NVOCMP_RECOVER_COMPACT :
    pNvHandle->forceCompact = 1;
    compact = true;
    break;
  default :
      break;
  }

  if(compact)
  {
    NVOCMP_compactPage(pNvHandle, 0);
  }
}
#endif

/******************************************************************************
 * @fn      NVOCMP_checkItem
 *
 * @brief   Local function to check parameters and locate existing item
 *
 * @param   id   - NV item type identifier
 * @param   len - NV item data length
 * @param   pHdr - pointer to header buffer
 * @param   flag - flag for item search
 *
 * @return  NVINTF_SUCCESS or specific failure code
 */
static uint8_t NVOCMP_checkItem(NVINTF_itemID_t *id, uint16_t len, NVOCMP_itemHdr_t *pHdr,
                                uint8_t flag)
{
    if(len > NVOCMP_MAXLEN)
    {
        // Item data is too long
        NVOCMP_ALERT(false, "Item data too large.")
        return(NVINTF_BADLENGTH);
    }
    if(id->systemID > NVOCMP_MAXSYSID)
    {
        // Too large for compressed header
        NVOCMP_ALERT(false, "Item sysid too large.")
        return(NVINTF_BADSYSID);
    }
    if(id->itemID > NVOCMP_MAXITEMID)
    {
        // Too large for compressed header
        NVOCMP_ALERT(false, "Item itemid too large.")
        return(NVINTF_BADITEMID);
    }
    if(id->subID > NVOCMP_MAXSUBID)
    {
        // Too large for compressed header
        NVOCMP_ALERT(false, "Item subid too large.")
        return(NVINTF_BADSUBID);
    }

    if(NVOCMP_failF == NVINTF_NOTREADY)
    {
        // NV driver has not been initialized
        NVOCMP_ASSERT(false, "Driver uninitialized.")
        return(NVINTF_NOTREADY);
    }

    pHdr->len    = len;
    pHdr->hofs   = 0;
    pHdr->cmpid  = NVOCMP_CMPRID(id->systemID,id->itemID,id->subID);
    pHdr->subid  = id->subID;
    pHdr->itemid = id->itemID;
    pHdr->sysid  = id->systemID;
    pHdr->sig    = NVOCMP_SIGNATURE;

    return(NVINTF_SUCCESS);
}

/******************************************************************************
 * @fn      NVOCMP_getDstPage
 *
 * @brief   Local function to find the page where an item will be written on
 *
 * @param   pNvHandle - pointer to NV handle
 * @param   len - item size to write
 *
 * @return  page number or NVOCMP_NULLPAGE
 */
static uint8_t NVOCMP_getDstPage(NVOCMP_nvHandle_t *pNvHandle, uint16_t len)
{
  uint8_t dstPg = NVOCMP_NULLPAGE;
  uint8_t pg = pNvHandle->actPage;
  NVOCMP_pageHdr_t pageHdr;
#if (NVOCMP_NVPAGES > NVOCMP_NVTWOP)
  uint16_t nvSearched = 0;
  for(pg = pNvHandle->actPage; nvSearched < NVOCMP_NVSIZE; pg = NVOCMP_INCPAGE(pg))
  {
    nvSearched++;
    if(pg == pNvHandle->tailPage)
    {
      continue;
    }
#endif
    NVOCMP_read(pg, NVOCMP_PGHDROFS, (uint8_t *)&pageHdr, NVOCMP_PGHDRLEN);
    if(pageHdr.state == NVOCMP_PGFULL)
    {
#if (NVOCMP_NVPAGES > NVOCMP_NVTWOP)
      continue;
#else
      return(dstPg);
#endif
    }
    if((pageHdr.state == NVOCMP_PGNACT) || (pageHdr.state == NVOCMP_PGRDY))
    {
      NVOCMP_changePageState(pNvHandle, pg, NVOCMP_PGACT);
    }

    pNvHandle->actPage = pg;
    pNvHandle->actOffset = pNvHandle->pageInfo[pg].offset;
    if((pNvHandle->pageInfo[pg].offset + len) <= FLASH_PAGE_SIZE)
    {
      dstPg = pNvHandle->actPage;
#if (NVOCMP_NVPAGES > NVOCMP_NVTWOP)
      break;
#endif
    }
    else
    {
      NVOCMP_changePageState(pNvHandle, pg, NVOCMP_PGFULL);
    }
#if (NVOCMP_NVPAGES > NVOCMP_NVTWOP)
  }
#endif
  return(dstPg);
}

/******************************************************************************
 * @fn      NVOCMP_addItem
 *
 * @brief   Local function to check for adequate space and create a new item
 *
 * @param   pNvHandle - pointer to NV handle
 * @param   iHdr - pointer to header buffer
 * @param   pBuf - pointer to item
 * @param   wm - write mode
 *
 * @return  NVINTF_SUCCESS or specific failure code
 */
static uint8_t NVOCMP_addItem(NVOCMP_nvHandle_t *pNvHandle, NVOCMP_itemHdr_t *iHdr,
                              uint8_t *pBuf, NVOCMP_writeMode_t wm)
{
    uint8_t err;
    uint8_t dstPg;
    uint16_t iLen;
    bool compact = false;
    NVOCMP_itemHdr_t hdr;

    iLen = NVOCMP_ITEMHDRLEN + iHdr->len;
    dstPg = NVOCMP_getDstPage(pNvHandle, iLen);

    if(dstPg == NVOCMP_NULLPAGE)
    {
      compact = true;
      // Won't fit on the active page, compact and check again
      if(NVOCMP_compactPage(pNvHandle, iLen) < iLen)
      {
          // Failure means there's no place to put this item
          NVOCMP_ALERT(false, "Out of NV.")
          err = (NVOCMP_failW != NVINTF_SUCCESS) ?
                  NVOCMP_failW : NVINTF_BADLENGTH;
          return(err);
      }
    }

    if((NVOCMP_failW == NVINTF_SUCCESS)
    && ((wm == NVOCMP_WRITE) || ((wm == NVOCMP_UPDATE) && (compact == true))))
    {
        /* get item offset after compaction */
      hdr.sysid = iHdr->sysid;
      hdr.itemid = iHdr->itemid;
      hdr.subid = iHdr->subid;
      (void)NVOCMP_findItem(pNvHandle, pNvHandle->actPage, pNvHandle->actOffset, &hdr,
                            NVOCMP_FINDSTRICT, NULL);
      iHdr->hpage = hdr.hpage;
      iHdr->hofs = hdr.hofs;
    }

#if NVOCMP_NWSAMEITEM
    bool changed = false;
    if((iHdr->hofs) && (iHdr->len))
    {
      #define NVOCMP_COMPARE_SIZE   32
      uint8_t readBuf[NVOCMP_COMPARE_SIZE];
      uint16_t iOfs = (iHdr->hofs - iHdr->len);
      uint16_t dOfs = 0;
      uint16_t len2cmp = 0;

      // Failure to parse the command
      NVOCMP_ALERT(iHdr->hofs >= iHdr->len, "Something wrong in parsing.")

      do
      {
        len2cmp = (iHdr->len - dOfs) > NVOCMP_COMPARE_SIZE ? NVOCMP_COMPARE_SIZE : (iHdr->len - dOfs);
        NVOCMP_read(iHdr->hpage, iOfs + dOfs, readBuf, len2cmp);
        if(memcmp(readBuf, pBuf + dOfs, len2cmp))
        {
          changed = true;
          break;
        }
        dOfs += len2cmp;
      } while(dOfs < iHdr->len);
    }
    else
    {
      changed = true;
    }

    if(changed)
    {
    // Create the new NV item
      NVOCMP_writeItem(pNvHandle, iHdr, pNvHandle->actPage, pNvHandle->actOffset, pBuf);
    }
    else
    {
      iHdr->hofs = 0;
    }
#else
    // Create the new NV item
    NVOCMP_writeItem(pNvHandle, iHdr, pNvHandle->actPage, pNvHandle->actOffset, pBuf);
#endif

    // Status of writing/erasing Flash
    return(NVOCMP_failW);
}

/******************************************************************************
 * @fn      NVOCMP_read
 *
 * @brief   Writes to a flash buffer from RAM
 *
 * @param   pg  - Flash page to write from
 * @param   off - Offset in destination page to read from
 * @param   pBuf  - Pointer to write the results into
 * @param   len - Number of bytes to write into
 *
 * @return  NVS_STATUS_SUCCESS or other NVS status code
 */
static inline void NVOCMP_read(uint8_t pg, uint16_t off, uint8_t *pBuf, uint16_t len)
{
#ifndef NV_LINUX
    NVS_read(NVOCMP_nvsHandle, NVOCMP_FLASHOFFSET(pg, off), (uint8_t *)pBuf, len);
#else
    NV_LINUX_read(pg, off, pBuf, len);
#endif
}

/******************************************************************************
 * @fn      NVOCMP_write
 *
 * @brief   Writes to a flash buffer from RAM
 *
 * @param   dstPg  - Flash page to write to
 * @param   off - offset in destination page to write to
 * @param   pBuf  - Pointer to caller's buffer to write & verify
 * @param   len - number of bytes to write from pBuf
 *
 * @return  NVS_STATUS_SUCCESS or other NVS status code
 */
static uint8_t NVOCMP_write(uint8_t dstPg, uint16_t off, uint8_t *pBuf, uint16_t len)
{
    uint8_t err = NVINTF_SUCCESS;
    int_fast16_t nvsRes = 0;

    // check voltage if possible
    NVOCMP_FLASHACCESS(err)

    if (NVINTF_SUCCESS == err)
    {
#ifndef NV_LINUX
        nvsRes = NVS_write(NVOCMP_nvsHandle, NVOCMP_FLASHOFFSET(dstPg, off),
                           pBuf, len, NVS_WRITE_POST_VERIFY);
#else
        nvsRes = NV_LINUX_write(dstPg, off, pBuf, len);
#endif
    }
    else
    {
        err = NVINTF_LOWPOWER;
    }

    if (nvsRes < 0)
    {
        err = NVINTF_FAILURE;
    }

    NVOCMP_ALERT(NVINTF_LOWPOWER != err, "Voltage check failed.")
    if (NVINTF_FAILURE == err)
    {
        NVOCMP_ALERT(NVINTF_FAILURE != err, "NVS write failure.")
    }

    return(err);
}

/******************************************************************************
 * @fn      NVOCMP_erase
 *
 * @brief   Erases a flash page
 *
 * @param   pNvHandle - pointer to NV handle
 * @param   dstPg - Flash page to write to
 *
 * @return  NVINT_SUCCESS or other NVINTF status code
 */
static uint8_t NVOCMP_erase(NVOCMP_nvHandle_t *pNvHandle, uint8_t dstPg)
{
    uint8_t err = NVINTF_SUCCESS;
    int_fast16_t nvsRes = 0;

    // Check voltage if possible
    NVOCMP_FLASHACCESS(err)

    if (NVINTF_SUCCESS == err)
    {
#ifndef NV_LINUX
        nvsRes = NVS_erase(NVOCMP_nvsHandle, NVOCMP_FLASHOFFSET(dstPg, 0),
                           NVOCMP_nvsAttrs.sectorSize);
#else
        nvsRes = NV_LINUX_erase(dstPg);
#endif
        if (nvsRes < 0)
        {
          err = NVINTF_FAILURE;
        }
        else
        {
          // Bump the compaction cycle counter, wrap-around if at maximum
          pNvHandle->pageInfo[dstPg].cycle = (pNvHandle->pageInfo[dstPg].cycle < NVOCMP_MAXCYCLE) ?
                                           (pNvHandle->pageInfo[dstPg].cycle + 1) : NVOCMP_MINCYCLE;
          NVOCMP_setPageState(pNvHandle, dstPg, NVOCMP_PGNACT);
          NVOCMP_setCompactHdr(dstPg, NVOCMP_NULLPAGE, NVOCMP_NULLOFFSET, THISPAGEHDR);
          NVOCMP_setCompactHdr(dstPg, NVOCMP_NULLPAGE, NVOCMP_NULLOFFSET, XSRCSTARTHDR);
          NVOCMP_setCompactHdr(dstPg, NVOCMP_NULLPAGE, NVOCMP_NULLOFFSET, XSRCENDHDR);
          pNvHandle->pageInfo[dstPg].offset = NVOCMP_PGDATAOFS;
          pNvHandle->pageInfo[dstPg].mode = NVOCMP_PGNORMAL;
        }
    }
    else
    {
        err = NVINTF_LOWPOWER;
    }

    NVOCMP_ALERT(NVINTF_LOWPOWER != err, "Voltage check failed.")
    NVOCMP_ALERT(NVINTF_FAILURE != err, "NVS erase failure.")

    return(err);
}

/******************************************************************************
 * @fn      NVOCMP_writeItem
 *
 * @brief   Write entire NV item to new location on active Flash page.
 *          Each call to NVS_write() does a read-back to verify. If an
 *          error is detected, the 'failW' flag is set to inhibit further
 *          flash write attempts until the next NV transaction.
 *
 * @param   pNvHandle - pointer to NV handle
 * @param   pHdr  - Pointer to caller's item header buffer
 * @param   dstPg - Destination NV Flash page
 * @param   dstOff - Destination offset
 * @param   pBuf  - Points to buffer which will be written to item
 *
 * @return  none
 */
static void NVOCMP_writeItem(NVOCMP_nvHandle_t *pNvHandle, NVOCMP_itemHdr_t *pHdr,
                             uint8_t dstPg, uint16_t dstOff, uint8_t *pBuf)
{
    uint16_t iLen;
    NVOCMP_pageHdr_t pageHdr;

    NVOCMP_read(dstPg, NVOCMP_PGHDROFS, (uint8_t *)&pageHdr, NVOCMP_PGHDRLEN);
    if(pageHdr.state == NVOCMP_PGRDY)
    {
      NVOCMP_changePageState(pNvHandle, dstPg, NVOCMP_PGACT);
    }

    if(pageHdr.state != NVOCMP_PGACT)
    {
      NVOCMP_failW = NVINTF_FAILURE;
      return;
    }

    NVOCMP_ALERT(pageHdr.state == NVOCMP_PGACT, "Something wrong.")

    // Total length of this item
    iLen = NVOCMP_ITEMHDRLEN + pHdr->len;

    if((dstOff + iLen) <= FLASH_PAGE_SIZE)
    {
        cmpIH_t cHdr;
        uint16_t hOfs, dLen;
        uint8_t newCRC;

        // Compressed item header information <-- Lower Addr    Higher Addr-->
        // Byte: [0]      [1]      [2]      [3]      [4]      [5]      [6]
        // Item: SSSSSSII IIIIIIII SSSSSSSS SSLLLLLL LLLLLLCC CCCCCCAV SSSSSSSS
        // LSB of field:         ^           ^            ^        ^          ^
#if NVOCMP_HDRLE
        cHdr[0] = (pHdr->sysid & 0x3F) | ((pHdr->itemid & 0x3) << 6);
        cHdr[1] = (pHdr->itemid >> 2) & 0xFF;
        cHdr[2] = pHdr->subid & 0xFF;
        cHdr[3] = ((pHdr->subid >> 8) & 0x3) | ((pHdr->len & 0x3F) << 2);
        cHdr[4] = (pHdr->len >> 6) & 0x3F;
#else
        cHdr[0] = ((pHdr->sysid << 2) | ((pHdr->itemid >> 8) & 0x3));
        cHdr[1] = (pHdr->itemid & 0xFF);
        cHdr[2] = ((pHdr->subid >> 2) & 0xFF);
        cHdr[3] = ((pHdr->subid & 0x3) << 6) | ((pHdr->len >> 6) & 0x3F);
        cHdr[4] = ((pHdr->len & 0x3F) << 2);
#endif
        // Header is located after the item data
        dLen = pHdr->len;
        hOfs = dstOff + dLen;

        if (iLen <= NVOCMP_SMALLITEM)
        {
            // Construct item in one buffer
            // Put data into buffer
            memcpy(NVOCMP_itemBuffer, (const void *)pBuf, dLen);
            // Put most of header into buffer
            memcpy(NVOCMP_itemBuffer + dLen, (const void *)cHdr,
                   NVOCMP_HDRCRCINC);
            // Calculate CRC
            newCRC = NVOCMP_doRAMCRC(NVOCMP_itemBuffer, dLen +
                                     NVOCMP_HDRCRCINC, 0);
#if NVOCMP_HDRLE
            // Insert CRC and last bytes
            cHdr[4] |= ((newCRC & 0x3) << 6);
            // Note NVOCMP_VALIDIDBIT set implicitly zero
            cHdr[5] = ((newCRC >> 2) & 0x3F) | (NVOCMP_ACTIVEIDBIT << 6);
#else
            // Insert CRC and last bytes
            cHdr[4] |= ((newCRC >> 6) & 0x3);
            // Note NVOCMP_VALIDIDBIT set implicitly zero
            cHdr[5] = ((newCRC & 0x3F) << 2) | NVOCMP_ACTIVEIDBIT;
#endif
            cHdr[6] = NVOCMP_SIGNATURE;
            memcpy(NVOCMP_itemBuffer + dLen, (const void *)cHdr,
                   NVOCMP_ITEMHDRLEN);
            // NVS_write
            NVOCMP_failW = NVOCMP_write(dstPg, dstOff,
                                        NVOCMP_itemBuffer, iLen);
            // Advance to next location
            dstOff += iLen;
            pNvHandle->actOffset += iLen;
            pNvHandle->pageInfo[dstPg].offset = dstOff;
        }
        else
        {
            // Write header/item separately
            // Calculate CRC on data portion
            newCRC = NVOCMP_doRAMCRC(pBuf, dLen, 0);
            // Finish CRC using header portion
            newCRC = NVOCMP_doRAMCRC(cHdr, NVOCMP_HDRCRCINC, newCRC);
            // Complete Header with CRC, bits, and sig
#if NVOCMP_HDRLE
            // Insert CRC and last bytes
            cHdr[4] |= ((newCRC & 0x3) << 6);
            // Note NVOCMP_VALIDIDBIT set implicitly zero
            cHdr[5] = ((newCRC >> 2) & 0x3F) | (NVOCMP_ACTIVEIDBIT << 6);
#else
            // Insert CRC and last bytes
            cHdr[4] |= ((newCRC >> 6) & 0x3);
            // Note NVOCMP_VALIDIDBIT set implicitly zero
            cHdr[5] = ((newCRC & 0x3F) << 2) | NVOCMP_ACTIVEIDBIT;
#endif
            cHdr[6] = NVOCMP_SIGNATURE;
            // Write data
            NVOCMP_failW = NVOCMP_write(dstPg, dstOff, pBuf, dLen);
            // Write header
            NVOCMP_failW |= NVOCMP_write(dstPg, hOfs, cHdr, NVOCMP_ITEMHDRLEN);
            // Advance to next location
            NVOCMP_ASSERT(dstOff < (dstOff + iLen), "Page offset overflow!")
            if(!NVOCMP_failW)
            {
                dstOff += iLen;
                pNvHandle->actOffset += iLen;
                pNvHandle->pageInfo[dstPg].offset = dstOff;
            }
            else
            {
                return;
            }
        }
        // If there was a write failure, delete item
        NVOCMP_ALERT(!NVOCMP_failW, "Driver write failure. Item deleted.")
        if (NVOCMP_failW)
        {
            NVOCMP_setItemInactive(pNvHandle, dstPg, hOfs);
        }
    }
    else
    {
        // Not enough room on page
        NVOCMP_failW = NVINTF_BADLENGTH;
    }
}

/******************************************************************************
 * @fn      NVOCMP_readHeader
 *
 * @brief   Read header block from NV and expand into caller's buffer
 *
 * @param   pg   - A valid NV Flash page
 * @param   ofs  - A valid offset into the page
 * @param   pHdr - Pointer to caller's item header buffer
 * @param   flag - fast flag
 *
 * @return  none
 */
static void NVOCMP_readHeader(uint8_t pg, uint16_t ofs, NVOCMP_itemHdr_t *pHdr, bool flag)
{
#ifdef NVOCMP_GPRAM
    uint8_t *pTBuffer = RAM_BUFFER_ADDRESS;
#else
    uint8_t *pTBuffer = (uint8_t *)tBuffer;
#endif
    cmpIH_t cHdr;
    if(flag)
    {
      memcpy((uint8_t *)cHdr, (uint8_t *)(pTBuffer + ofs), NVOCMP_ITEMHDRLEN);
    }
    else
    {
      // Get item header from Flash
      NVOCMP_read(pg, ofs, (uint8_t *)cHdr, NVOCMP_ITEMHDRLEN);
    }
    // Offset to compressed header
    pHdr->hofs = ofs;
    pHdr->hpage = pg;

    // Compressed item header information <-- Lower Addr    Higher Addr-->
    // Byte: [0]      [1]      [2]      [3]      [4]      [5]      [6]
    // Item: SSSSSSII IIIIIIII SSSSSSSS SSLLLLLL LLLLLLCC CCCCCCAV SSSSSSSS
    // LSB of field:         ^           ^            ^        ^          ^
#if NVOCMP_HDRLE
    pHdr->sysid  = cHdr[0] & 0x3F;
    pHdr->itemid = ((cHdr[0] >> 6) & 0x3) | (cHdr[1] << 2);
    pHdr->subid  = cHdr[2] | ((cHdr[3] & 0x3) << 8);
    pHdr->len    = ((cHdr[3] >> 2) & 0x3F) | ((cHdr[4] & 0x3F) << 6);
    pHdr->crc8   = ((cHdr[4] >> 6) & 0x3) | ((cHdr[5] & 0x3F) << 2);
    pHdr->stats  = (cHdr[5] >> 6) & (NVOCMP_VALIDIDBIT | NVOCMP_ACTIVEIDBIT);
    pHdr->sig    = cHdr[6];
#else
    pHdr->sysid  = (cHdr[0] >> 2) & 0x3F;
    pHdr->itemid = ((cHdr[0] & 0x3) << 8) | cHdr[1];
    pHdr->subid  = (cHdr[2] << 2) | ((cHdr[3] >> 6) & 0x3);
    pHdr->len    = ((cHdr[3] & 0x3F) << 6) | ((cHdr[4] >> 2) & 0x3F);
    pHdr->crc8   = ((cHdr[4] & 0x3) << 6) | ((cHdr[5] >> 2) & 0x3F);
    pHdr->stats  = cHdr[5] & (NVOCMP_VALIDIDBIT | NVOCMP_ACTIVEIDBIT);
    pHdr->sig    = cHdr[6];
#endif
    pHdr->cmpid  = NVOCMP_CMPRID(pHdr->sysid, pHdr->itemid, pHdr->subid);
    // Our item has correct signature?
    if (pHdr->sig != NVOCMP_SIGNATURE)
    {
        // Indicate item is invalid
        NVOCMP_ALERT(false, "Invalid signature detected! Item corrupted.")
        pHdr->stats |=  NVOCMP_VALIDIDBIT;
    }
    else
    {
        (pHdr->stats) |= NVOCMP_FOLLOWBIT;
        NVOCMP_ALERT(pHdr->stats & NVOCMP_FOLLOWBIT,
                     "Item gap detected. Item not followed.")
    }
}

/******************************************************************************
 * @fn      NVOCMP_readItem
 *
 * @brief   Function to read an item described by iHdr into pBuf
 *
 * @param   iHdr - pointer to an item header struct
 * @param   bOfs - offset into NV data block
 * @param   len - length of NV data to return (0 is illegal)
 * @param   pBuf - pointer to caller's read data buffer  (NULL is illegal)
 * @param   flag - fast flag
 *
 * @return  NVINTF_SUCCESS or specific failure code
 */
static uint8_t NVOCMP_readItem(NVOCMP_itemHdr_t *iHdr, uint16_t ofs, uint16_t len,
                               void *pBuf, bool flag)
{
    uint8_t err = NVINTF_SUCCESS;
    uint16_t dOfs, iOfs;
#ifdef NVOCMP_GPRAM
    uint8_t *pTBuffer = RAM_BUFFER_ADDRESS;
#else
    uint8_t *pTBuffer = (uint8_t *)tBuffer;
#endif
    iOfs = (iHdr->hofs - iHdr->len);

    // Optional CRC integrity check
    if (NVOCMP_CRCONREAD)
    {
        err = NVOCMP_verifyCRC(iOfs, iHdr->len, iHdr->crc8, iHdr->hpage, flag);
    }
    if(err == NVINTF_SUCCESS)
    {
        // Offset to start of item data
        dOfs = iOfs + ofs;
        if((dOfs + len) <= iHdr->hofs)
        {
            if(flag)
            {
              // Copy from RAM
              memcpy((uint8_t *)pBuf, (uint8_t *)(pTBuffer+dOfs), len);
            }
            else
            {
              // Copy NV data block to caller's buffer
              NVOCMP_read(iHdr->hpage, dOfs, (uint8_t *)pBuf, len);
            }
        }
        else
        {
            // Bad length or offset
            err = (len > iHdr->len) ? NVINTF_BADLENGTH : NVINTF_BADOFFSET;
        }
    }

    return(err);
}

/******************************************************************************
 * @fn      NVOCMP_setItemInactive
 *
 * @brief   Mark an item as inactive
 *
 * @param   pNvHandle - pointer to NV handle
 * @param   pg - page where the item is located
 * @param   iOfs - Offset to item header (lowest address) in active page
 *
 * @return  none
 */
static void NVOCMP_setItemInactive(NVOCMP_nvHandle_t *pNvHandle, uint8_t pg,
                                   uint16_t iOfs)
{
    uint8_t tmp;

    // Get byte with validity bit
    tmp = NVOCMP_readByte(pg, iOfs + NVOCMP_HDRVLDOFS);

    // Remove ACTIVE_IDS_MARK
#if NVOCMP_HDRLE
    tmp &= ~(NVOCMP_ACTIVEIDBIT << 6);
#else
    tmp &= ~NVOCMP_ACTIVEIDBIT;
#endif
    // Mark the item as inactive
    NVOCMP_writeByte(pg, iOfs + NVOCMP_HDRVLDOFS, tmp);

    if(pNvHandle->pageInfo[pg].allActive)
    {
      tmp = NVOCMP_readByte(pg, NVOCMP_PGHDRVER);
      tmp &= ~NVOCMP_ALLACTIVE;
      NVOCMP_writeByte(pg, NVOCMP_PGHDRVER, tmp);
      pNvHandle->pageInfo[pg].allActive = NVOCMP_SOMEINACTIVE;
    }
}

/******************************************************************************
 * @fn      NVOCMP_setCompactHdr
 *
 * @brief   Set compact header
 *
 * @param   dstPg - destination page to write the header
 * @param   pg - page of compaction
 * @param   offset - offset of compaction
 * @param   location - location of the header
 *
 * @return  none
 */
static void NVOCMP_setCompactHdr(uint8_t dstPg, uint8_t pg, int16_t offset,
                                 uint16_t location)
{
  NVOCMP_compactHdr_t hdr = DEFAULT_COMPACTHDR;
  hdr.page = pg;
  hdr.pageOffset = offset;

  NVOCMP_failW = NVOCMP_write(dstPg, (location + 1) * NVOCMP_COMPACTHDRLEN,
                 (uint8_t *)&hdr, NVOCMP_COMPACTHDRLEN);
}

#if (NVOCMP_NVPAGES > NVOCMP_NVTWOP)
/******************************************************************************
 * @fn      NVOCMP_getCompactHdr
 *
 * @brief   Get compact header
 *
 * @param   dstPg - destination page to read the header
 * @param   location - location of the header
 * @param   pHdr - pointer to the header
 *
 * @return  none
 */
static void NVOCMP_getCompactHdr(uint8_t dstPg, uint16_t location,
                                 NVOCMP_compactHdr_t *pHdr)
{
  NVOCMP_read(dstPg, (location + 1) * NVOCMP_COMPACTHDRLEN,
              (uint8_t *)pHdr, NVOCMP_COMPACTHDRLEN);
}
#endif

/******************************************************************************
 * @fn      NVOCMP_setPageState
 *
 * @brief   Set specified NV page state
 *
 * @param   pNvHandle - pointer to NV handle
 * @param   pg - target NV page
 * @param   state - state of the page
 *
 * @return  none
 */
static void NVOCMP_setPageState(NVOCMP_nvHandle_t *pNvHandle, uint8_t pg,
                                NVOCMP_pageState_t state)
{
    NVOCMP_pageHdr_t pHdr;

    // Load header
    pHdr.state = (uint8_t)state;
    pHdr.cycle = (uint8_t)pNvHandle->pageInfo[pg].cycle;
    pHdr.allActive = NVOCMP_ALLACTIVE;
    pHdr.version = (uint8_t)NVOCMP_VERSION;
    pHdr.signature = (uint8_t)NVOCMP_SIGNATURE;

    // Write to page
    NVOCMP_failW = NVOCMP_write(pg, 0, (uint8_t *)&pHdr, NVOCMP_PGHDRLEN);

    if(NVOCMP_failW == NVINTF_SUCCESS)
    {
      if(state == NVOCMP_PGACT)
      {
        // No errors, switch active page
        pNvHandle->actPage = pg;
      }
      pNvHandle->pageInfo[pg].state = state;
      pNvHandle->pageInfo[pg].allActive = NVOCMP_ALLACTIVE;
    }
}

/******************************************************************************
 * @fn      NVOCMP_changePageState
 *
 * @brief   Change NV page state
 *
 * @param   pNvHandle - pointer to NV handle
 * @param   pg - target NV page
 * @param   state - state of the page
 *
 * @return  none
 */
static void NVOCMP_changePageState(NVOCMP_nvHandle_t *pNvHandle, uint8_t pg,
                                   NVOCMP_pageState_t state)
{
  NVOCMP_writeByte(pg, NVOCMP_PGHDROFS, (uint8_t)state);

  if(NVOCMP_failW == NVINTF_SUCCESS)
  {
    if(state == NVOCMP_PGACT)
    {
      // No errors, switch active page
      pNvHandle->actPage = pg;
    }
    pNvHandle->pageInfo[pg].state = state;
  }
}

/******************************************************************************
 * @fn      NVOCMP_findOffset
 *
 * @brief   Find the offset to next available empty space in specified page
 *
 * @param   pg   - Valid NV page on which to find offset to next available data
 * @param   ofs - Beginning offset to start search
 *
 * @return  Number of bytes from start of page to next available item location
 */
#if NVOCMP_FASTOFF
static uint16_t NVOCMP_findOffset(uint8_t pg, uint16_t ofs)
{
    uint8_t i,j;
    uint32_t *tmp;
#ifdef NVOCMP_GPRAM
    uint32_t vm;
    uint8_t *pTBuffer = RAM_BUFFER_ADDRESS;
#else
    uint8_t *pTBuffer = (uint8_t *)tBuffer;
#endif

#ifdef NVOCMP_GPRAM
    NVOCMP_disableCache(&vm);
#endif

    NVOCMP_read(pg, 0, (uint8_t *)pTBuffer, FLASH_PAGE_SIZE);

    // Find first non-erased 4-byte location
    tmp = (uint32_t *)pTBuffer;
    while(ofs >= sizeof(uint32_t))
    {
        ofs -= sizeof(uint32_t);
        tmp = (uint32_t *)(pTBuffer + ofs);
        if((*tmp) != 0xFFFFFFFF)
        {
            break;
        }
    }

    // Starting with LSB, look for non-erased byte
    for(i = j = 1; i <= 4; i++)
    {
        if(((*tmp) & 0xFF) != 0xFF)
        {
            // Last non-erased byte so far
            j = i;
        }
        (*tmp) >>= 8;
    }

#ifdef NVOCMP_GPRAM
        NVOCMP_restoreCache(vm);
#endif

    return(ofs + j);
}
#else
static uint16_t NVOCMP_findOffset(uint8_t pg, uint16_t ofs)
{
    uint8_t i,j;
    uint32_t tmp = 0;

    // Find first non-erased 4-byte location
    while(ofs >= sizeof(tmp))
    {
        ofs -= sizeof(tmp);
        NVOCMP_read(pg, ofs, (uint8_t *)&tmp, sizeof(tmp));
        if(tmp != NVOCMP_ERASEDWORD)
        {
            break;
        }
    }

    // Starting with LSB, look for non-erased byte
    for(i = j = 1; i <= 4; i++)
    {
        if((tmp & 0xFF) != NVOCMP_ERASEDBYTE)
        {
            // Last non-erased byte so far
            j = i;
        }
        tmp >>= 8;
    }

    return(ofs + j);
}
#endif

/******************************************************************************
 * @fn      NVOCMP_findItem
 *
 * @brief   Find a valid item from designated page and offset
 *
 * @param   pNvHandle - pointer to NV handle
 * @param   pg  - Valid NV page
 * @param   ofs - Offset in NV page from where to start search
 * @param   pHdr - pointer to item header
 * @param   flag - specifies type of search
 * @param   pInfo - pointer to item info
 *
 * @return  When >0, offset to the item header for found item
 *          When <=0, -number of items searched when item not found
 *
 */
#if NVOCMP_FASTITEM
static int8_t NVOCMP_findItem(NVOCMP_nvHandle_t *pNvHandle, uint8_t pg, uint16_t ofs,
                              NVOCMP_itemHdr_t *pHdr, int8_t flag, NVOCMP_itemInfo_t *pInfo)
{
    bool found = false;
    uint8_t p;
    uint16_t items = 0;
    uint16_t nvSearched = 0;
#ifdef NVOCMP_GPRAM
    uint32_t vm;
    uint8_t *pTBuffer = RAM_BUFFER_ADDRESS;
#else
    uint8_t *pTBuffer = (uint8_t *)tBuffer;
#endif
    uint32_t cid = NVOCMP_CMPRID(pHdr->sysid,pHdr->itemid,pHdr->subid);

#ifdef NVOCMP_GPRAM
    NVOCMP_disableCache(&vm);
#endif

    for(p = pg; nvSearched < NVOCMP_NVSIZE; p = NVOCMP_DECPAGE(p), ofs = pNvHandle->pageInfo[p].offset)
    {
      nvSearched++;
#if (NVOCMP_NVPAGES != NVOCMP_NVONEP)
      if(p == pNvHandle->tailPage)
      {
        continue;
      }
#endif
      NVOCMP_read(pg, 0, (uint8_t *)pTBuffer, FLASH_PAGE_SIZE);

      while(ofs >= (NVOCMP_PGDATAOFS + NVOCMP_ITEMHDRLEN))
      {
          NVOCMP_itemHdr_t iHdr;

          // Align to start of item header
          ofs -= NVOCMP_ITEMHDRLEN;

          // Read and decompress item header
          NVOCMP_readHeader(p, ofs, &iHdr, true);

          if((iHdr.stats & NVOCMP_ACTIVEIDBIT) &&
            !(iHdr.stats & NVOCMP_VALIDIDBIT))
          {
              uint32_t sysid = pHdr->sysid;
              uint32_t itemid = pHdr->itemid;

              switch (flag & NVOCMP_FINDLMASK)
              {
              case NVOCMP_FINDANY:
                  found = true;
                  break;
              case NVOCMP_FINDSTRICT:
                  // Return first cid match
                  if (cid == iHdr.cmpid)
                  {
                      found = true;
                  }
                  break;
              case NVOCMP_FINDSYSID:
                  // return first sysid match
                  if (sysid == iHdr.sysid)
                  {
                      found = true;
                  }
                  break;
              case NVOCMP_FINDITMID:
                  // return first sysid AND itemid match
                  if (sysid == iHdr.sysid && itemid == iHdr.itemid)
                  {
                      found = true;
                  }
                  break;
              default:
                  // Should not get here
                  NVOCMP_EXCEPTION(p, NVINTF_BADPARAM);
                  NVOCMP_ASSERT(false, "Unhandled case in findItem().")
#ifdef NVOCMP_GPRAM
                  NVOCMP_restoreCache(vm);
#endif
                  return(NVINTF_BADPARAM);
              }
              // Item found - return offset of item header
              if (found)
              {
                if((pInfo) && ((flag & NVOCMP_FINDHMASK) == NVOCMP_FINDCONTENT))
                {
                  if(!NVOCMP_readItem(&iHdr, 0, pInfo->rlength, pInfo->rBuf, false))
                  {
                    if(!memcmp((uint8_t *)pInfo->rBuf + pInfo->coff, pInfo->cBuf, pInfo->clength))
                    {
                      memcpy(pHdr, &iHdr, sizeof(NVOCMP_itemHdr_t));
#ifdef NVOCMP_GPRAM
                      NVOCMP_restoreCache(vm);
#endif
                      return(NVINTF_SUCCESS);
                    }
                  }
                  found = false;
                }
                else
                {
                  memcpy(pHdr, &iHdr, sizeof(NVOCMP_itemHdr_t));
#ifdef NVOCMP_GPRAM
                  NVOCMP_restoreCache(vm);
#endif
                  return(NVINTF_SUCCESS);
                }
              }
          }
          // Try to jump to next item
          if(iHdr.stats & NVOCMP_FOLLOWBIT)
          {
              // Appears to be an item there, check bounds
              if(iHdr.len < ofs)
              {
                  // Adjust offset for next try
                  ofs -= iHdr.len;
              }
              else
              {
                  // Length is corrupt, mark item invalid and compact
                  NVOCMP_ALERT(false, "Item length corrupted. Deleting item.")
                  NVOCMP_setItemInactive(pNvHandle, p, ofs);
#ifdef NVOCMP_GPRAM
                  NVOCMP_restoreCache(vm);
#endif
                  NVOCMP_compactPage(pNvHandle, 0);
                  p = NVOCMP_INCPAGE(pNvHandle->actPage);
                  ofs = 0;
                  nvSearched = 0;
              }
          }
          else
          {
              // Something is corrupted, compact to fix
              NVOCMP_ALERT(false, "No item following current item, "
                      "compaction needed.")
#ifdef NVOCMP_GPRAM
              NVOCMP_restoreCache(vm);
#endif
              NVOCMP_compactPage(pNvHandle, 0);
              p = NVOCMP_INCPAGE(pNvHandle->actPage);
              ofs = 0;
              nvSearched = 0;
          }
          // Running count of items searched
          items += 1;
      }
    }
#ifdef NVOCMP_GPRAM
    NVOCMP_restoreCache(vm);
#endif
    // Item not found (negate number of items searched)
    // or nth not found, return last found
    pHdr->hofs = 0;
    return(NVINTF_NOTFOUND);
}
#else
static int8_t NVOCMP_findItem(NVOCMP_nvHandle_t *pNvHandle, uint8_t pg, uint16_t ofs,
                              NVOCMP_itemHdr_t *pHdr, int8_t flag, NVOCMP_itemInfo_t *pInfo)
{
    bool found = false;
    uint8_t p = pg;
    uint16_t items = 0;
    uint32_t cid = NVOCMP_CMPRID(pHdr->sysid,pHdr->itemid,pHdr->subid);

#if (NVOCMP_NVPAGES > NVOCMP_NVTWOP)
    uint16_t nvSearched = 0;
    for(p = pg; nvSearched < NVOCMP_NVSIZE; p = NVOCMP_DECPAGE(p), ofs = pNvHandle->pageInfo[p].offset)
    {
      nvSearched++;
      if(p == pNvHandle->tailPage)
      {
        continue;
      }
#endif
      while(ofs >= (NVOCMP_PGDATAOFS + NVOCMP_ITEMHDRLEN))
      {
          NVOCMP_itemHdr_t iHdr;

          // Align to start of item header
          ofs -= NVOCMP_ITEMHDRLEN;

          // Read and decompress item header
          NVOCMP_readHeader(p, ofs, &iHdr, false);

          if((iHdr.stats & NVOCMP_ACTIVEIDBIT) &&
            !(iHdr.stats & NVOCMP_VALIDIDBIT))
          {
              uint32_t sysid = pHdr->sysid;
              uint32_t itemid = pHdr->itemid;

              switch (flag & NVOCMP_FINDLMASK)
              {
              case NVOCMP_FINDANY:
                  found = true;
                  break;
              case NVOCMP_FINDSTRICT:
                  // Return first cid match
                  if (cid == iHdr.cmpid)
                  {
                      found = true;
                  }
                  break;
              case NVOCMP_FINDSYSID:
                  // return first sysid match
                  if (sysid == iHdr.sysid)
                  {
                      found = true;
                  }
                  break;
              case NVOCMP_FINDITMID:
                  // return first sysid AND itemid match
                  if (sysid == iHdr.sysid && itemid == iHdr.itemid)
                  {
                      found = true;
                  }
                  break;
              default:
                  // Should not get here
                  NVOCMP_EXCEPTION(p, NVINTF_BADPARAM);
                  NVOCMP_ASSERT(false, "Unhandled case in findItem().")
                  return(NVINTF_BADPARAM);
              }
              // Item found - return offset of item header
              if (found)
              {
                if((pInfo) && ((flag & NVOCMP_FINDHMASK) == NVOCMP_FINDCONTENT))
                {
                  if(!NVOCMP_readItem(&iHdr, 0, pInfo->rlength, pInfo->rBuf, false))
                  {
                    if(!memcmp((uint8_t *)pInfo->rBuf + pInfo->coff, pInfo->cBuf, pInfo->clength))
                    {
                      memcpy(pHdr, &iHdr, sizeof(NVOCMP_itemHdr_t));
                      return(NVINTF_SUCCESS);
                    }
                  }
                  found = false;
                }
                else
                {
                  memcpy(pHdr, &iHdr, sizeof(NVOCMP_itemHdr_t));
                  return(NVINTF_SUCCESS);
                }
              }
          }
          // Try to jump to next item
          if(iHdr.stats & NVOCMP_FOLLOWBIT)
          {
              // Appears to be an item there, check bounds
              if(iHdr.len < ofs)
              {
                  // Adjust offset for next try
                  ofs -= iHdr.len;
              }
              else
              {
                  // Length is corrupt, mark item invalid and compact
                  NVOCMP_ALERT(false, "Item length corrupted. Deleting item.")
                  NVOCMP_setItemInactive(pNvHandle, p, ofs);
                  NVOCMP_compactPage(pNvHandle, 0);
#if (NVOCMP_NVPAGES > NVOCMP_NVTWOP)
                  p = NVOCMP_INCPAGE(pNvHandle->actPage);
                  ofs = 0;
                  nvSearched = 0;
#else
                  p = pNvHandle->actPage;
                  ofs = pNvHandle->actOffset;
#endif
              }
          }
          else
          {
              // Something is corrupted, compact to fix
              NVOCMP_ALERT(false, "No item following current item, "
                      "compaction needed.")
              NVOCMP_compactPage(pNvHandle, 0);
#if (NVOCMP_NVPAGES > NVOCMP_NVTWOP)
              p = NVOCMP_INCPAGE(pNvHandle->actPage);
              ofs = 0;
              nvSearched = 0;
#else
              p = pNvHandle->actPage;
              ofs = pNvHandle->actOffset;
#endif
          }
          // Running count of items searched
          items += 1;
      }
#if (NVOCMP_NVPAGES > NVOCMP_NVTWOP)
    }
#endif
    // Item not found (negate number of items searched)
    // or nth not found, return last found
    pHdr->hofs = 0;
    return(NVINTF_NOTFOUND);
}
#endif

#if (NVOCMP_NVPAGES > NVOCMP_NVTWOP)
/******************************************************************************
 * @fn      NVOCMP_cleanPage
 *
 * @brief   Clean the page that compaction is done with
 *
 * @param   pNvHandle - pointer to NV handle
 *
 * @return  Number of pages cleaned
 */
static uint8_t NVOCMP_cleanPage(NVOCMP_nvHandle_t *pNvHandle)
{
  uint8_t pg;
  uint8_t pages = 0;
  NVOCMP_pageHdr_t pageHdr;
  NVOCMP_compactHdr_t startHdr;
  NVOCMP_compactHdr_t endHdr;

  /* correct ofset */
  pg = pNvHandle->compactInfo.xDstPage;
  NVOCMP_getCompactHdr(pg, XSRCSTARTHDR, &startHdr);
  NVOCMP_getCompactHdr(pg, XSRCENDHDR, &endHdr);
  for(pg = pNvHandle->compactInfo.xSrcSPage;
      pg != NVOCMP_INCPAGE(pNvHandle->compactInfo.xSrcEPage);
      pg = NVOCMP_INCPAGE(pg))
  {
    if(pg == endHdr.page)
    {
      pNvHandle->pageInfo[pg].offset = endHdr.pageOffset;
    }
    else
    {
      pNvHandle->pageInfo[pg].offset = NVOCMP_PGDATAOFS;
    }
  }
  /* end of correction */

  for(pg = pNvHandle->compactInfo.xSrcSPage; pg != NVOCMP_INCPAGE(pNvHandle->compactInfo.xSrcEPage); pg = NVOCMP_INCPAGE(pg))
  {
    if(pNvHandle->pageInfo[pg].offset == NVOCMP_PGDATAOFS)
    {
      pages++;
      NVOCMP_failW = NVOCMP_erase(pNvHandle, pg);
    }
    else
    {
      NVOCMP_failW = NVOCMP_write(pg, (THISPAGEHDR + 1) * NVOCMP_COMPACTHDRLEN,
                                  (uint8_t *)&pNvHandle->pageInfo[pg].offset, sizeof(pNvHandle->pageInfo[pg].offset));
      NVOCMP_read(pg, NVOCMP_PGHDROFS, (uint8_t *)&pageHdr, NVOCMP_PGHDRLEN);
      if(pageHdr.state == NVOCMP_PGACT)
      {
        NVOCMP_changePageState(pNvHandle, pg, NVOCMP_PGFULL);
      }
      else
      {
        //this should not hit
        if(pageHdr.state == NVOCMP_PGXDST)
        {
          NVOCMP_ASSERT1(0);
        }
      }
    }
  }
  return(pages);
}
#endif

#if (NVOCMP_NVPAGES > NVOCMP_NVTWOP)
/******************************************************************************
 * @fn      NVOCMP_compactPage
 *
 * @brief   Compact specified page by copying active items to other page
 *
 *          Compaction occurs under three circumstances: (1) 'maintenance'
 *          activity which is triggered by a user call to compactNvApi(),
 *          (2) 'update' activity where an NV page is packed to make room
 *          for an item being written. The 'update' mode is performed by
 *          writing the item after the rest of the page has been compacted,
 *          and (3) when corruption is detected in the NV page. The compaction
 *          operation will move all active&valid items to the other page.
 *
 * @param   pNvHandle - pointer to NV handle
 * @param   nBytes - size of item to write if any
 *
 * @return  Number of available bytes on compacted page, -1 if error
 */
static int16_t NVOCMP_compactPage(NVOCMP_nvHandle_t *pNvHandle, uint16_t nBytes)
{
  uint8_t pg;
  uint8_t mode;
  uint8_t srcPg;
  uint8_t dstPg;
  uint16_t needBytes;
  uint16_t compactPages;
  uint16_t cleanPages;
  uint16_t skipPages = 0;
  bool foundRoom = false;
  NVOCMP_compactStatus_t status;
  NVOCMP_pageHdr_t pageHdr;
  uint8_t allActivePages = 0;
  uint8_t err = NVINTF_SUCCESS;

  // Check voltage if possible
  NVOCMP_FLASHACCESS(err)
  if(err)
  {
    return(0);
  }

  srcPg = pNvHandle->headPage;
  dstPg = pNvHandle->tailPage;
  compactPages = NVOCMP_NVSIZE - 1;
  if(nBytes)
  {
    pNvHandle->compactInfo.xSrcPages = 1;
  }
  else
  {
    pNvHandle->compactInfo.xSrcPages = NVOCMP_NVSIZE - 1;
  }

  // mark page mode
  for(pg = 0; pg < NVOCMP_NVSIZE; pg++)
  {
    if(pg != dstPg)
    {
      NVOCMP_read(pg, NVOCMP_PGHDROFS, (uint8_t *)&pageHdr, NVOCMP_PGHDRLEN);
      if(pageHdr.allActive)
      {
        allActivePages++;
      }
      if(pageHdr.state == NVOCMP_PGACT || pageHdr.state == NVOCMP_PGFULL)
      {
        mode = NVOCMP_PGCSRC;
        NVOCMP_writeByte(pg, NVOCMP_COMPMODEOFS, mode);
        pNvHandle->pageInfo[pg].mode = mode;
      }
    }
  }

  if((allActivePages == NVOCMP_NVSIZE - 1) && !pNvHandle->forceCompact)
  {
    return(0);
  }

  while(compactPages)
  {
    if(pNvHandle->compactInfo.xSrcPages == 0)
    {
      pNvHandle->compactInfo.xSrcPages = 1;
    }
    // Get page header
    NVOCMP_read(srcPg, NVOCMP_PGHDROFS, (uint8_t *)&pageHdr, NVOCMP_PGHDRLEN);
#if NVOCMP_FASTCP
    if(nBytes && pageHdr.allActive)
    {
      srcPg = NVOCMP_INCPAGE(srcPg);
      pNvHandle->compactInfo.xDstOffset = FLASH_PAGE_SIZE;
      pNvHandle->compactInfo.xSrcSPage = srcPg;
      pNvHandle->compactInfo.xSrcPages--;
      compactPages--;
      skipPages++;
      continue;
    }
#endif
    // Mark source page to be in PGXSRC state
    if(pageHdr.state != NVOCMP_PGXSRC)
    {
      NVOCMP_changePageState(pNvHandle, srcPg, NVOCMP_PGXSRC);
    }
    // Get page header
    NVOCMP_read(dstPg, NVOCMP_PGHDROFS, (uint8_t *)&pageHdr, NVOCMP_PGHDRLEN);
    // Mark destination page to be in PGXDST state
    if(pageHdr.state != NVOCMP_PGXDST)
    {
      NVOCMP_changePageState(pNvHandle, dstPg, NVOCMP_PGXDST);
    }

    mode = NVOCMP_PGCDST;
    NVOCMP_writeByte(dstPg, NVOCMP_COMPMODEOFS, mode);
    pNvHandle->pageInfo[dstPg].mode = mode;

    pNvHandle->compactInfo.xDstPage = dstPg;
    pNvHandle->compactInfo.xDstOffset = FLASH_PAGE_SIZE;
    pNvHandle->compactInfo.xSrcSPage = srcPg;
    pNvHandle->compactInfo.xSrcSOffset = pNvHandle->pageInfo[srcPg].offset;
    pNvHandle->compactInfo.xSrcEPage = NVOCMP_NULLPAGE;
    pNvHandle->compactInfo.xSrcEOffset = 0;
    status = NVOCMP_compact(pNvHandle);

    if(status == NVOCMP_COMPACT_FAILURE)
    {
#ifdef NVOCMP_RECOVER_FROM_COMPACT_FAILURE
        uint8_t p;
        for(p = 0; p < NVOCMP_NVSIZE; p++)
        {
            NVOCMP_failW |= NVOCMP_erase(pNvHandle, p);
            if((p != 0) && (p != NVOCMP_NVSIZE - 1))
            {
              NVOCMP_changePageState(pNvHandle, p, NVOCMP_PGRDY);
            }
        }
        pNvHandle->actPage = 0;
        pNvHandle->actOffset = pNvHandle->pageInfo[0].offset;
        pNvHandle->headPage = 0;
        pNvHandle->tailPage = NVOCMP_NVSIZE - 1;
        NVOCMP_changePageState(pNvHandle, NVOCMP_NVSIZE - 1, NVOCMP_PGXDST);
        NVOCMP_changePageState(pNvHandle, 0, NVOCMP_PGACT);

        pNvHandle->forceCompact = 0;
#endif
    return(0);
    }

    needBytes = nBytes ? nBytes : 16;

    if(nBytes)
    {
      if(FLASH_PAGE_SIZE - pNvHandle->compactInfo.xDstOffset >= needBytes)
      {
        foundRoom = true;
      }
    }
    // change XDST page state
    if(FLASH_PAGE_SIZE - pNvHandle->compactInfo.xDstOffset >= needBytes)
    {
      NVOCMP_changePageState(pNvHandle, dstPg, NVOCMP_PGACT);
    }
    else
    {
      NVOCMP_changePageState(pNvHandle, dstPg, NVOCMP_PGFULL);
    }
    // clean XSRC pages
    cleanPages = NVOCMP_cleanPage(pNvHandle);
    compactPages -= cleanPages;
    pNvHandle->compactInfo.xSrcPages -= cleanPages;

    // mark XDST page as done
    mode = NVOCMP_PGCDONE;
    NVOCMP_writeByte(dstPg, NVOCMP_COMPMODEOFS, mode);
    pNvHandle->pageInfo[dstPg].mode = mode;

    // move tail page and head page
    pNvHandle->tailPage = NVOCMP_ADDPAGE(pNvHandle->tailPage, cleanPages + skipPages);
    pNvHandle->headPage = NVOCMP_INCPAGE(pNvHandle->tailPage);
    skipPages = 0;
    // set next source page
    if(pNvHandle->pageInfo[pNvHandle->compactInfo.xSrcEPage].offset == NVOCMP_PGDATAOFS)
    {
      srcPg = NVOCMP_INCPAGE(pNvHandle->compactInfo.xSrcEPage);
    }
    else
    {
      srcPg = pNvHandle->compactInfo.xSrcEPage;
    }

    // set next destination page
    dstPg = pNvHandle->tailPage;
    if(nBytes && foundRoom)
    {
      break;
    }
  }

  pg = NVOCMP_findPage(NVOCMP_PGACT);
  if(pg == NVOCMP_NULLPAGE)
  {
    pg = NVOCMP_findPage(NVOCMP_PGRDY);
    if(pg == NVOCMP_NULLPAGE)
    {
      if(pNvHandle->pageInfo[pNvHandle->headPage].state == NVOCMP_PGNACT)
      {
        pg = pNvHandle->headPage;
        NVOCMP_changePageState(pNvHandle, pg, NVOCMP_PGRDY);
      }
      else
      {
        pg = NVOCMP_DECPAGE(pNvHandle->tailPage);
      }
    }
  }

  pNvHandle->actPage = pg;
  pNvHandle->actOffset = pNvHandle->pageInfo[pNvHandle->actPage].offset;
  NVOCMP_changePageState(pNvHandle, pNvHandle->tailPage, NVOCMP_PGXDST);

  pNvHandle->forceCompact = 0;
  return(FLASH_PAGE_SIZE - pNvHandle->compactInfo.xDstOffset);
}
#else
/******************************************************************************
 * @fn      NVOCMP_compactPage
 *
 * @brief   Compact specified page by copying active items to other page
 *
 *          Compaction occurs under three circumstances: (1) 'maintenance'
 *          activity which is triggered by a user call to compactNvApi(),
 *          (2) 'update' activity where an NV page is packed to make room
 *          for an item being written. The 'update' mode is performed by
 *          writing the item after the rest of the page has been compacted,
 *          and (3) when corruption is detected in the NV page. The compaction
 *          operation will move all active&valid items to the other page.
 *
 * @param   pNvHandle - pointer to NV handle
 * @param   nBytes - size of item to write if any
 *
 * @return  Number of available bytes on compacted page, -1 if error
 */
static int16_t NVOCMP_compactPage(NVOCMP_nvHandle_t *pNvHandle, uint16_t nBytes)
{
  uint8_t srcPg;
  uint8_t dstPg;
  uint16_t needBytes;
  NVOCMP_compactStatus_t status;
  NVOCMP_pageHdr_t pageHdr;
  uint8_t err = NVINTF_SUCCESS;

  // Check voltage if possible
  NVOCMP_FLASHACCESS(err)
  if(err)
  {
    return(0);
  }

#if(NVOCMP_NVPAGES == NVOCMP_NVONEP)
  srcPg = 0;
  dstPg = 0;
#else
  srcPg = pNvHandle->headPage;
  dstPg = pNvHandle->tailPage;
#endif
  pNvHandle->compactInfo.xSrcPages = 1;

  // mark page mode
  NVOCMP_read(srcPg, NVOCMP_PGHDROFS, (uint8_t *)&pageHdr, NVOCMP_PGHDRLEN);
  if(pageHdr.allActive && !pNvHandle->forceCompact)
  {
    return(0);
  }

  NVOCMP_writeByte(srcPg, NVOCMP_COMPMODEOFS, NVOCMP_PGCSRC);
  pNvHandle->pageInfo[srcPg].mode = NVOCMP_PGCSRC;

  // Mark source page to be in PGXSRC state
  if(pageHdr.state != NVOCMP_PGXSRC)
  {
    NVOCMP_changePageState(pNvHandle, srcPg, NVOCMP_PGXSRC);
  }
#if(NVOCMP_NVPAGES > NVOCMP_NVONEP)
  // Get page header
  NVOCMP_read(dstPg, NVOCMP_PGHDROFS, (uint8_t *)&pageHdr, NVOCMP_PGHDRLEN);
  // Mark destination page to be in PGXDST state
  if(pageHdr.state != NVOCMP_PGXDST)
  {
    NVOCMP_changePageState(pNvHandle, dstPg, NVOCMP_PGXDST);
  }

  NVOCMP_writeByte(dstPg, NVOCMP_COMPMODEOFS, NVOCMP_PGCDST);
  pNvHandle->pageInfo[dstPg].mode = NVOCMP_PGCDST;
#endif

  pNvHandle->compactInfo.xSrcSOffset = pNvHandle->pageInfo[srcPg].offset;
  status = NVOCMP_compact(pNvHandle);

  if(status == NVOCMP_COMPACT_FAILURE)
  {
#ifdef NVOCMP_RECOVER_FROM_COMPACT_FAILURE
      uint8_t p;
      for(p = 0; p < NVOCMP_NVSIZE; p++)
      {
        NVOCMP_failW |= NVOCMP_erase(pNvHandle, p);
        if((p != 0) && (p != NVOCMP_NVSIZE - 1))
        {
          NVOCMP_changePageState(pNvHandle, p, NVOCMP_PGRDY);
        }
      }
      pNvHandle->actPage = 0;
      pNvHandle->actOffset = pNvHandle->pageInfo[0].offset;
      pNvHandle->headPage = 0;
      pNvHandle->tailPage = NVOCMP_NVSIZE - 1;
      NVOCMP_changePageState(pNvHandle, NVOCMP_NVSIZE - 1, NVOCMP_PGXDST);
      NVOCMP_changePageState(pNvHandle, 0, NVOCMP_PGACT);

      pNvHandle->forceCompact = 0;
#endif
    return(0);
  }

  needBytes = nBytes ? nBytes : 16;

  // change XDST page state
  if(FLASH_PAGE_SIZE - pNvHandle->compactInfo.xDstOffset >= needBytes)
  {
    NVOCMP_changePageState(pNvHandle, dstPg, NVOCMP_PGACT);
  }
  else
  {
    NVOCMP_changePageState(pNvHandle, dstPg, NVOCMP_PGFULL);
  }

#if(NVOCMP_NVPAGES > NVOCMP_NVONEP)
  // clean XSRC pages
  NVOCMP_failW = NVOCMP_erase(pNvHandle, srcPg);
#endif

  // mark XDST page as done
  NVOCMP_writeByte(dstPg, NVOCMP_COMPMODEOFS, NVOCMP_PGCDONE);
  pNvHandle->pageInfo[dstPg].mode = NVOCMP_PGCDONE;

  // move tail page and head page
  pNvHandle->tailPage = srcPg;
  pNvHandle->headPage = dstPg;
  pNvHandle->actPage = dstPg;
  pNvHandle->actOffset = pNvHandle->pageInfo[dstPg].offset;
#if(NVOCMP_NVPAGES > NVOCMP_NVONEP)
  NVOCMP_changePageState(pNvHandle, srcPg ,NVOCMP_PGXDST);
#endif

  pNvHandle->forceCompact = 0;
  return(FLASH_PAGE_SIZE - pNvHandle->actOffset);
}
#endif

/******************************************************************************
* @fn      NVOCMP_findSignature
*
* @brief   Local function to scan page to get page information
*
* @param   pg - page to search
* @param   pSrcOff - source off to start search from
*
* @return  NVINTF_SUCCESS or specific failure code
*/
static bool NVOCMP_findSignature(uint8_t pg, uint16_t *pSrcOff)
{
  uint16_t i;
  uint16_t rdLen;
  uint8_t readBuffer[NVOCMP_XFERBLKMAX];
  uint16_t srcOff = *pSrcOff;
  uint16_t endOff = NVOCMP_PGDATAOFS + NVOCMP_ITEMHDRLEN - 1;

  while(srcOff > endOff)
  {
    rdLen = (srcOff - NVOCMP_XFERBLKMAX > endOff) ? NVOCMP_XFERBLKMAX : srcOff - endOff;
    srcOff -= rdLen;
    NVOCMP_read(pg, srcOff, readBuffer, rdLen);
    for(i = rdLen; i > 0; i--)
    {
      if(readBuffer[i-1] == NVOCMP_SIGNATURE)
      {
        // Found possible header, resume normal operation
        NVOCMP_ALERT(false, "Found possible signature.")
        srcOff += i;        // srcOff should point to one byte ahead
        *pSrcOff = srcOff;
        return(true);
      }
    }
  }
  return(false);
}

#if (NVOCMP_NVPAGES > NVOCMP_NVTWOP)
/******************************************************************************
* @fn      NVOCMP_compact
*
* @brief   Local function to compact NV
*
* @param   pNvHandle - pointer to NV handler
*
* @return  NVINTF_SUCCESS or specific failure code
*/
static NVOCMP_compactStatus_t NVOCMP_compact(NVOCMP_nvHandle_t *pNvHandle)
{
    bool needScan = false;
    bool needSkip = false;
    bool dstFull = false;
    uint16_t dstOff;
    uint16_t endOff;
    uint16_t srcOff;
    uint16_t crcOff;
    uint8_t srcStartPg;
    uint8_t srcEndPg;
    uint8_t dstPg;
    uint8_t srcPg;
    uint32_t aItem=0;
#ifdef NVOCMP_GPRAM
    uint32_t vm;
    uint8_t *pTBuffer = RAM_BUFFER_ADDRESS;
#else
    uint8_t *pTBuffer = (uint8_t *)tBuffer;
#endif
    NVOCMP_compactStatus_t status = NVOCMP_COMPACT_SUCCESS;

#ifndef NVOCMP_GPRAM
    memset(tBuffer, 0, sizeof(tBuffer));
#endif
    // Reset Flash erase/write fail indicator
    NVOCMP_failW = NVINTF_SUCCESS;
    srcStartPg = pNvHandle->compactInfo.xSrcSPage;
    srcEndPg = NVOCMP_ADDPAGE(srcStartPg, pNvHandle->compactInfo.xSrcPages - 1);

    // Stop looking when we get to this offset
    endOff = NVOCMP_PGDATAOFS + NVOCMP_ITEMHDRLEN - 1;

    srcPg = srcStartPg;
    srcOff = pNvHandle->pageInfo[srcPg].offset;
    dstPg = pNvHandle->compactInfo.xDstPage;
    dstOff = pNvHandle->pageInfo[dstPg].offset;

    NVOCMP_ALERT(false, "Compaction triggered.")

#ifdef NVOCMP_GPRAM
    NVOCMP_disableCache(&vm);
#endif
    while(srcPg != dstPg)
    {
        if(dstFull)
        {
          if(aItem)
          {
            pNvHandle->pageInfo[srcPg].offset = srcOff;
          }
          break;
        }

        if(srcOff <= endOff)
        {
          if(aItem)
          {
            pNvHandle->pageInfo[srcPg].offset = srcOff;
          }
          if(srcPg == srcEndPg)
          {
            break;
          }
          else
          {
            srcPg = NVOCMP_INCPAGE(srcPg);
            srcOff = pNvHandle->pageInfo[srcPg].offset;
            aItem = 0;
            continue;
          }
        }

        if(NVOCMP_failW == NVINTF_SUCCESS)
        {
            NVOCMP_itemHdr_t srcHdr;
            uint16_t dataLen;
            uint16_t itemSize;

            // Align to start of item header
            srcOff -= NVOCMP_ITEMHDRLEN;

            // Read and decompress item header
            NVOCMP_readHeader(srcPg, srcOff, &srcHdr, false);
            dataLen  = srcHdr.len;
            itemSize = NVOCMP_ITEMHDRLEN + dataLen;

            // Check if length is safe
            if (srcOff < (dataLen + NVOCMP_PGDATAOFS) ||
                    (NVOCMP_SIGNATURE != srcHdr.sig))
            {
                needScan = true;
            }
            else
            {
              if(!(srcHdr.stats & NVOCMP_VALIDIDBIT) && srcHdr.stats & NVOCMP_ACTIVEIDBIT)
              {
                NVOCMP_ALERT(srcOff >= (dataLen + NVOCMP_PGDATAOFS),
                             "Item header corrupted, data length too long.")
                crcOff    = srcOff - dataLen;
                if(NVOCMP_verifyCRC(crcOff,dataLen,srcHdr.crc8, srcPg, false))
                {
                  // Invalid CRC, corruption
                  NVOCMP_ALERT(false, "Item CRC incorrect!")
                  needScan = true;
                  srcOff--;
                }
                else
                {
                  needScan = false;
                  needSkip = false;
                }
              }
              else
              {
                needScan = false;
                needSkip = true;
              }
            }

            if(needScan)
            {
              // Detected a problem, find next header (scan for signature)
              NVOCMP_ALERT(false, "Attempting to find signature...")
              bool foundSig = NVOCMP_findSignature(srcPg, &srcOff);
              if(!foundSig)
              {
#ifdef NVOCMP_GPRAM
                NVOCMP_restoreCache(vm);
#endif
                // If we get here and foundSig is false, we never found another
                // item in the page
                NVOCMP_ALERT(foundSig, "Attempt to find signature failed.")
                return(NVOCMP_COMPACT_FAILURE);
              }
            }
            else
            {
              if(!needSkip)
              {
                if(dstOff + itemSize > FLASH_PAGE_SIZE)
                {
                  // cannot fit one page temp buffer and revert srcOff change
                  srcOff += NVOCMP_ITEMHDRLEN;
                  dstFull = true;
                  continue;
                }
                else
                {
                  // Get block of bytes from source page
#if NVOCMP_COMPR
                  NVOCMP_read(srcPg, crcOff, (uint8_t *)(pTBuffer + dstOff), itemSize);
#else
                  NVOCMP_read(srcPg, crcOff, (uint8_t *)(pTBuffer + FLASH_PAGE_SIZE - dstOff - itemSize), itemSize);
#endif
                  dstOff += itemSize;
                  aItem++;
                  if (dstOff == FLASH_PAGE_SIZE)
                  {
                    dstFull = true;
                  }
                }
              }
              NVOCMP_ALERT(srcOff > dataLen, "Offset overflow: srcOff")
              srcOff -= dataLen;
            }
        }
        else
        {
#ifdef NVOCMP_GPRAM
            NVOCMP_restoreCache(vm);
#endif
            // Failure during item xfer makes next findItem() unreliable
            NVOCMP_ASSERT(false, "COMPACTION FAILURE")
            return(NVOCMP_COMPACT_FAILURE);
        }
    } // end of while

    if(NVOCMP_failW != NVINTF_SUCCESS)
    {
#ifdef NVOCMP_GPRAM
        NVOCMP_restoreCache(vm);
#endif
        // Something bad happened when trying to compact the page
        NVOCMP_ASSERT(false, "COMPACTION FAILURE")
        return(NVOCMP_COMPACT_FAILURE);
    }

    // Write block to destination page
#if NVOCMP_COMPR
    uint16_t off = NVOCMP_PGDATAOFS;
    uint16_t len = dstOff - NVOCMP_PGDATAOFS;

    NVOCMP_failW |= NVOCMP_write(dstPg, off, (uint8_t *)(pTBuffer + off), len);
#else
    uint16_t off = NVOCMP_PGDATAOFS;
    uint16_t doff = FLASH_PAGE_SIZE - dstOff;
    uint16_t len = dstOff - NVOCMP_PGDATAOFS;

    NVOCMP_failW |= NVOCMP_write(dstPg, off, (uint8_t *)(pTBuffer + doff), len);
#endif

#ifdef NVOCMP_GPRAM
    NVOCMP_restoreCache(vm);
#endif

    if(srcPg == dstPg)
    {
      srcPg = NVOCMP_DECPAGE(srcPg);
      srcOff = pNvHandle->pageInfo[srcPg].offset;
    }
    pNvHandle->compactInfo.xDstOffset = dstOff;
    pNvHandle->compactInfo.xSrcEOffset = srcOff;

    pNvHandle->compactInfo.xSrcEPage = srcPg;
    pNvHandle->pageInfo[dstPg].offset = dstOff;

    NVOCMP_setCompactHdr(dstPg, pNvHandle->compactInfo.xSrcSPage, pNvHandle->compactInfo.xSrcSOffset, XSRCSTARTHDR);
    NVOCMP_setCompactHdr(dstPg, pNvHandle->compactInfo.xSrcEPage, pNvHandle->compactInfo.xSrcEOffset, XSRCENDHDR);

    if(srcOff <= endOff)
    {
      status |= NVOCMP_COMPACT_SRCDONE;
    }
    if(dstOff >= FLASH_PAGE_SIZE)
    {
      status |= NVOCMP_COMPACT_DSTDONE;
    }
    return(status);
}
#else
/******************************************************************************
* @fn      NVOCMP_compact
*
* @brief   Local function to compact NV
*
* @param   pNvHandle - pointer to NV handler
*
* @return  NVINTF_SUCCESS or specific failure code
*/
static NVOCMP_compactStatus_t NVOCMP_compact(NVOCMP_nvHandle_t *pNvHandle)
{
    bool needScan = false;
    bool needSkip = false;
    uint16_t dstOff;
    uint16_t endOff;
    uint16_t srcOff;
    uint16_t crcOff;
    uint8_t dstPg;
    uint8_t srcPg;
    uint32_t aItem=0;
#ifdef NVOCMP_GPRAM
    uint32_t vm;
    uint8_t *pTBuffer = RAM_BUFFER_ADDRESS;
#else
    uint8_t *pTBuffer = (uint8_t *)tBuffer;
#endif

#ifndef NVOCMP_GPRAM
    memset(tBuffer, 0, sizeof(tBuffer));
#endif
    // Reset Flash erase/write fail indicator
    NVOCMP_failW = NVINTF_SUCCESS;

    // Stop looking when we get to this offset
    endOff = NVOCMP_PGDATAOFS + NVOCMP_ITEMHDRLEN - 1;

#if (NVOCMP_NVPAGES == NVOCMP_NVONEP)
    srcPg = 0;
    srcOff = pNvHandle->pageInfo[0].offset;
    dstPg = 0;
    dstOff = NVOCMP_PGDATAOFS;
#else
    srcPg = pNvHandle->headPage;
    srcOff = pNvHandle->pageInfo[srcPg].offset;
    dstPg = pNvHandle->tailPage;
    dstOff = pNvHandle->pageInfo[dstPg].offset;
#endif

    NVOCMP_ALERT(false, "Compaction triggered.")

#ifdef NVOCMP_GPRAM
    NVOCMP_disableCache(&vm);
#endif
    while(srcOff > endOff)
    {
        if(NVOCMP_failW == NVINTF_SUCCESS)
        {
            NVOCMP_itemHdr_t srcHdr;
            uint16_t dataLen;
            uint16_t itemSize;

            // Align to start of item header
            srcOff -= NVOCMP_ITEMHDRLEN;

            // Read and decompress item header
            NVOCMP_readHeader(srcPg, srcOff, &srcHdr, false);
            dataLen  = srcHdr.len;
            itemSize = NVOCMP_ITEMHDRLEN + dataLen;

            // Check if length is safe
            if (srcOff < (dataLen + NVOCMP_PGDATAOFS) ||
                    (NVOCMP_SIGNATURE != srcHdr.sig))
            {
                NVOCMP_ALERT(false, "Item header corrupted, data length too long.")
                needScan = true;
            }
            else
            {
              if(!(srcHdr.stats & NVOCMP_VALIDIDBIT) && (srcHdr.stats & NVOCMP_ACTIVEIDBIT)) //valid bit is ok
              {
                crcOff    = srcOff - dataLen;
                if(NVOCMP_verifyCRC(crcOff,dataLen,srcHdr.crc8, srcPg, false))
                {
                  // Invalid CRC, corruption
                  NVOCMP_ALERT(false, "Item CRC incorrect!")
                  needScan = true;
                  srcOff--;
                }
                else
                {
                  needScan = false;
                  needSkip = false;
                }
              }
              else
              {
                needScan = false;
                needSkip = true;
              }
            }

            if(needScan)
            {
              // Detected a problem, find next header (scan for signature)
              NVOCMP_ALERT(false, "Attempting to find signature...")
              bool foundSig = NVOCMP_findSignature(srcPg, &srcOff);
              if(!foundSig)
              {
#ifdef NVOCMP_GPRAM
                NVOCMP_restoreCache(vm);
#endif
                // If we get here and foundSig is false, we never found another
                // item in the page
                NVOCMP_ALERT(foundSig, "Attempt to find signature failed.")
                return(NVOCMP_COMPACT_FAILURE);
              }
            }
            else
            {
              if(!needSkip)
              {
                // Get block of bytes from source page
#if NVOCMP_COMPR
                NVOCMP_read(srcPg, crcOff, (uint8_t *)(pTBuffer + dstOff), itemSize);
#else
                NVOCMP_read(srcPg, crcOff, (uint8_t *)(pTBuffer + FLASH_PAGE_SIZE - dstOff - itemSize), itemSize);
#endif
                dstOff += itemSize;
                aItem++;
              }
              NVOCMP_ALERT(srcOff > dataLen, "Offset overflow: srcOff")
              srcOff -= dataLen;
            }
        }
        else
        {
#ifdef NVOCMP_GPRAM
            NVOCMP_restoreCache(vm);
#endif
            // Failure during item xfer makes next findItem() unreliable
            NVOCMP_ASSERT(false, "COMPACTION FAILURE")
            return(NVOCMP_COMPACT_FAILURE);
        }
    } // end of while

    if(NVOCMP_failW != NVINTF_SUCCESS)
    {
#ifdef NVOCMP_GPRAM
        NVOCMP_restoreCache(vm);
#endif
        // Something bad happened when trying to compact the page
        NVOCMP_ASSERT(false, "COMPACTION FAILURE")
        return(NVOCMP_COMPACT_FAILURE);
    }

#if (NVOCMP_NVPAGES == NVOCMP_NVONEP)
    // Get XDST page ready
    NVOCMP_failW = NVOCMP_erase(pNvHandle, dstPg);
    NVOCMP_changePageState(pNvHandle, dstPg, NVOCMP_PGXDST);
#endif

    // Write block to destination page
#if NVOCMP_COMPR
    uint16_t off = NVOCMP_PGDATAOFS;
    uint16_t len = dstOff - NVOCMP_PGDATAOFS;
    NVOCMP_failW |= NVOCMP_write(dstPg, off, (uint8_t *)(pTBuffer + off), len);
#else
    uint16_t off = NVOCMP_PGDATAOFS;
    uint16_t doff = FLASH_PAGE_SIZE - dstOff;
    uint16_t len = dstOff - NVOCMP_PGDATAOFS;

    NVOCMP_failW |= NVOCMP_write(dstPg, off, (uint8_t *)(pTBuffer + doff), len);
#endif

#ifdef NVOCMP_GPRAM
    NVOCMP_restoreCache(vm);
#endif

    pNvHandle->pageInfo[dstPg].offset = dstOff;
    pNvHandle->compactInfo.xSrcEOffset = srcOff;
    pNvHandle->compactInfo.xDstOffset = dstOff;

    NVOCMP_setCompactHdr(dstPg, pNvHandle->compactInfo.xSrcSPage, pNvHandle->compactInfo.xSrcSOffset, XSRCSTARTHDR);
    NVOCMP_setCompactHdr(dstPg, pNvHandle->compactInfo.xSrcEPage, pNvHandle->compactInfo.xSrcEOffset, XSRCENDHDR);

    return(NVOCMP_COMPACT_SUCCESS);
}
#endif

#if ((NVOCMP_NVPAGES > NVOCMP_NVONEP) && !defined(NVOCMP_MIGRATE_DISABLED))
/******************************************************************************
 * @fn      NVOCMP_copyItem
 *
 * @brief   Copy an NV item from active page to specified destination page
 *
 * @param   srcPg - Source page
 * @param   dstPg - Destination page
 * @param   sOfs  - Source page offset of original data in active page
 * @param   dOfs  - Destination page offset to transferred copy of the item
 * @param   len   - Length of data to copy
 *
 * @return  none.
 */
static void NVOCMP_copyItem(uint8_t srcPg, uint8_t dstPg, uint16_t sOfs, uint16_t dOfs, uint16_t len)
{
    uint16_t num;
    uint8_t tmp[NVOCMP_XFERBLKMAX];

    // Copy over the data: Flash to RAM, then RAM to Flash
    while(len > 0 && !NVOCMP_failW)
    {
        // Number of bytes to transfer in this block
        num = (len < NVOCMP_XFERBLKMAX) ? len : NVOCMP_XFERBLKMAX;

        // Get block of bytes from source page
        NVOCMP_read(srcPg, sOfs, (uint8_t *)&tmp, num);

        // Write block to destination page
        NVOCMP_failW = NVOCMP_write(dstPg, dOfs, (uint8_t *)&tmp, num);

        dOfs += num;
        sOfs += num;
        len  -= num;
    }
}
#endif
/******************************************************************************
 * @fn      NVOCMP_readByte
 *
 * @brief   Read one byte from Flash memory
 *
 * @param   pg  - NV Flash page
 * @param   ofs - Offset into the page
 *
 * @return  byte read from flash memory
 */
static uint8_t NVOCMP_readByte(uint8_t pg, uint16_t ofs)
{
    uint8_t byteVal;
    NVOCMP_read(pg, ofs, &byteVal, NVOCMP_ONEBYTE);

    return(byteVal);
}


/******************************************************************************
 * @fn      NVOCMP_writeByte
 *
 * @brief   Write one byte to Flash and read back to verify
 *
 * @param   pg  - NV Flash page
 * @param   ofs - offset into the page
 * @param   bwv - byte to write & verify
 *
 * @return  none ('failF' or 'failW' will be set if write fails)
 */
static void NVOCMP_writeByte(uint8_t pg, uint16_t ofs, uint8_t bwv)
{
    NVOCMP_failW = NVOCMP_write(pg, ofs, &bwv, 1);
}


/******************************************************************************
 * @fn      NVOCMP_doNVCRC
 *
 * @brief   Computes the CRC8 on the NV buffer indicated
 *          CRC code external, API in crc.h
 *
 * @param   pg - Flash page to check
 * @param   ofs - Flash page offset to lowest address item byte
 * @param   len - Item data length
 * @param   crc - value to start with, should be NULL if new calculation
 * @param   flag - fast flag
 *
 * @return  crc byte
 */
static uint8_t NVOCMP_doNVCRC(uint8_t pg, uint16_t ofs, uint16_t len, uint8_t crc, bool flag)
{
    uint16_t rdLen = 0;
#ifdef NVOCMP_GPRAM
    uint8_t *pTBuffer = RAM_BUFFER_ADDRESS;
#else
    uint8_t *pTBuffer = (uint8_t *)tBuffer;
#endif
    uint8_t tmp[NVOCMP_XFERBLKMAX];
    crc_t newCRC = (crc_t)crc;

    // Read flash and compute CRC in blocks
    while(len > 0)
    {
        rdLen  = (len < NVOCMP_XFERBLKMAX ? len : NVOCMP_XFERBLKMAX);
        if(flag)
        {
          memcpy((uint8_t *)tmp, (uint8_t *)(pTBuffer+ofs), rdLen);
        }
        else
        {
          NVOCMP_read(pg, ofs, tmp, rdLen);
        }
        newCRC = crc_update(newCRC,tmp,rdLen);
        len   -= rdLen;
        ofs   += rdLen;
    }

    return(newCRC);
}

/******************************************************************************
 * @fn      NVOCMP_doRAMCRC
 *
 * @brief   Calculates CRC8 given a buffer and length
 *          CRC code external, API in crc.h
 *
 * @param   input - pointer to data buffer
 * @param   len - length of data in buffer
 * @param   crc - value to start with, should be NULL if new calculation
 *
 * @return  CRC8 byte
 */
static uint8_t NVOCMP_doRAMCRC(uint8_t *input, uint16_t len, uint8_t crc)
{
    crc_t newCRC = crc_update((crc_t)crc, input, len);

    return((uint8_t)newCRC);
}

/******************************************************************************
 * @fn      NVOCMP_verifyCRC
 *
 * @brief   Helper function to validate item crc from NV
 *
 * @param   iOfs - offset to item data
 * @param   len - length of item data
 * @param   crc - crc to compare against
 * @param   pg - page to work on
 * @param   flag - fast flag
 *
 * @return  status byte
 */
static uint8_t NVOCMP_verifyCRC(uint16_t iOfs, uint16_t len, uint8_t crc, uint8_t pg, bool flag)
{
    uint8_t newCRC;
    uint16_t crcLen   = len + NVOCMP_HDRCRCINC - 1;
#if NVOCMP_HDRLE
    uint8_t finalByte = (len >> 6) & 0x3F;
#else
    uint8_t finalByte = (len & 0x3F) << 2;
#endif
    // CRC calculations stop at the length field of header
    // So the last byte must be done separately
    newCRC = NVOCMP_doNVCRC(pg, iOfs, crcLen, 0, flag);
    newCRC = NVOCMP_doRAMCRC(&finalByte,sizeof(finalByte),newCRC);
    NVOCMP_ALERT(newCRC == crc, "Invalid CRC detected.")
#ifdef NVOCMP_STATS
    if (newCRC != crc)
    {
        NVOCMP_badCRCCount++;
    }
#endif // NVOCMP_STATS
    return(newCRC == crc ? NVINTF_SUCCESS : NVINTF_CORRUPT);
}

//*****************************************************************************
