Files
examples/cc25xx-8051-example/ti/BLE-CC254x/Components/osal/mcu/cc2540/osal_snv.c
2019-07-25 18:12:39 +08:00

869 lines
23 KiB
C

/**************************************************************************************************
Filename: osal_snv.c
Revised: $Date: 2013-02-15 10:12:26 -0800 (Fri, 15 Feb 2013) $
Revision: $Revision: 33143 $
Description: This module contains the OSAL simple non-volatile memory functions.
Copyright 2009-2013 Texas Instruments Incorporated. All rights reserved.
IMPORTANT: Your use of this Software is limited to those specific rights
granted under the terms of a software license agreement between the user
who downloaded the software, his/her employer (which must be your employer)
and Texas Instruments Incorporated (the "License"). You may not use this
Software unless you agree to abide by the terms of the License. The License
limits your use, and you acknowledge, that the Software may not be modified,
copied or distributed unless embedded on a Texas Instruments microcontroller
or used solely and exclusively in conjunction with a Texas Instruments radio
frequency transceiver, which is integrated into your product. Other than for
the foregoing purpose, you may not use, reproduce, copy, prepare derivative
works of, modify, distribute, perform, display or sell this Software and/or
its documentation for any purpose.
YOU FURTHER ACKNOWLEDGE AND AGREE THAT THE SOFTWARE AND DOCUMENTATION ARE
PROVIDED “AS IS” WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESS OR IMPLIED,
INCLUDING WITHOUT LIMITATION, ANY WARRANTY OF MERCHANTABILITY, TITLE,
NON-INFRINGEMENT AND FITNESS FOR A PARTICULAR PURPOSE. IN NO EVENT SHALL
TEXAS INSTRUMENTS OR ITS LICENSORS BE LIABLE OR OBLIGATED UNDER CONTRACT,
NEGLIGENCE, STRICT LIABILITY, CONTRIBUTION, BREACH OF WARRANTY, OR OTHER
LEGAL EQUITABLE THEORY ANY DIRECT OR INDIRECT DAMAGES OR EXPENSES
INCLUDING BUT NOT LIMITED TO ANY INCIDENTAL, SPECIAL, INDIRECT, PUNITIVE
OR CONSEQUENTIAL DAMAGES, LOST PROFITS OR LOST DATA, COST OF PROCUREMENT
OF SUBSTITUTE GOODS, TECHNOLOGY, SERVICES, OR ANY CLAIMS BY THIRD PARTIES
(INCLUDING BUT NOT LIMITED TO ANY DEFENSE THEREOF), OR OTHER SIMILAR COSTS.
Should you have any questions regarding your right to use this Software,
contact Texas Instruments Incorporated at www.TI.com.
**************************************************************************************************/
/*********************************************************************
* INCLUDES
*/
#include "hal_adc.h"
#include "hal_flash.h"
#include "hal_types.h"
#include "comdef.h"
#include "OSAL.h"
#include "osal_snv.h"
#include "hal_assert.h"
#include "saddr.h"
#ifdef OSAL_SNV_UINT16_ID
# error "This OSAL SNV implementation does not support the extended ID space"
#endif
/*********************************************************************
* CONSTANTS
*/
// NV page configuration
#define OSAL_NV_PAGE_SIZE HAL_FLASH_PAGE_SIZE
#define OSAL_NV_PAGES_USED HAL_NV_PAGE_CNT
#define OSAL_NV_PAGE_BEG HAL_NV_PAGE_BEG
#define OSAL_NV_PAGE_END (OSAL_NV_PAGE_BEG + OSAL_NV_PAGES_USED - 1)
// Default byte value when flash is erased
#define OSAL_NV_ERASED 0xFF
// NV page header size in bytes
#define OSAL_NV_PAGE_HDR_SIZE 4
// In case pages 0-1 are ever used, define a null page value.
#define OSAL_NV_PAGE_NULL 0
// In case item Id 0 is ever used, define a null item value.
#define OSAL_NV_ITEM_NULL 0
// Length in bytes of a flash word
#define OSAL_NV_WORD_SIZE HAL_FLASH_WORD_SIZE
// NV page header offset within a page
#define OSAL_NV_PAGE_HDR_OFFSET 0
// Flag in a length field of an item header to indicate validity
// of the length field
#define OSAL_NV_INVALID_LEN_MARK 0x8000
// Flag in an ID field of an item header to indicate validity of
// the identifier field
#define OSAL_NV_INVALID_ID_MARK 0x8000
// Bit difference between active page state indicator value and
// transfer page state indicator value
#define OSAL_NV_ACTIVE_XFER_DIFF 0x00100000
// active page state indicator value
#define OSAL_NV_ACTIVE_PAGE_STATE OSAL_NV_ACTIVE_XFER_DIFF
// transfer page state indicator value
#define OSAL_NV_XFER_PAGE_STATE (OSAL_NV_ACTIVE_PAGE_STATE ^ OSAL_NV_ACTIVE_XFER_DIFF)
#define OSAL_NV_MIN_COMPACT_THRESHOLD 70 // Minimum compaction threshold
#define OSAL_NV_MAX_COMPACT_THRESHOLD 95 // Maximum compaction threshold
/*********************************************************************
* MACROS
*/
// Macro to check supply voltage
#if (defined HAL_MCU_CC2530 || defined HAL_MCU_CC2531)
# define OSAL_NV_CHECK_BUS_VOLTAGE (HalAdcCheckVdd(VDD_MIN_FLASH))
#elif defined HAL_MCU_CC2533
# define OSAL_NV_CHECK_BUS_VOLTAGE (HalBatMonRead( HAL_BATMON_MIN_FLASH ))
#else
// The radio chip does not support voltage monitoring
# define OSAL_NV_CHECK_BUS_VOLTAGE TRUE
#endif
/*********************************************************************
* TYPEDEFS
*/
// NV item header structure
typedef struct
{
uint16 id;
uint16 len;
} osalNvItemHdr_t;
// Note that osalSnvId_t and osalSnvLen_t cannot be bigger than uint16
/*********************************************************************
* EXTERNAL FUNCTIONS
*/
extern bool HalAdcCheckVdd(uint8 limit);
/*********************************************************************
* GLOBAL VARIABLES
*/
#ifndef OAD_KEEP_NV_PAGES
// When NV pages are to remain intact during OAD download,
// the image itself should not include NV pages.
#pragma location="BLENV_ADDRESS_SPACE"
__no_init uint8 _nvBuf[OSAL_NV_PAGES_USED * OSAL_NV_PAGE_SIZE];
#pragma required=_nvBuf
#endif // OAD_KEEP_NV_PAGES
#if defined MAKE_CRC_SHDW
#pragma location="CRC_SHDW"
const CODE uint16 _crcShdw = 0xFFFF;
#pragma required=_crcShdw
#endif
/*********************************************************************
* LOCAL VARIABLES
*/
// active page
static uint8 activePg;
// active page offset
static uint16 pgOff;
// flag to indicate that an error has occurred while writing to or erasing the
// flash device. Once this flag indicates failure, it is unsafe to attempt
// another write or erase.
static uint8 failF;
/*********************************************************************
* LOCAL FUNCTIONS
*/
static uint8 initNV( void );
static void setActivePage( uint8 pg );
static void setXferPage(void);
static void erasePage( uint8 pg );
static void cleanErasedPage( uint8 pg );
static void findOffset( void );
static void compactPage( uint8 pg );
static void writeWord( uint8 pg, uint16 offset, uint8 *pBuf );
static void writeWordM( uint8 pg, uint16 offset, uint8 *pBuf, osalSnvLen_t cnt );
// NOTE: Triggering erase upon power up may cause fast aging of the flash device
// if there is power switch debounce issue, etc.
// Improvement of this is to add a certain delay upon power up before
// osal_nv_init() is called.
/*********************************************************************
* @fn initNV
*
* @brief Initialize the NV flash pages.
*
* @param none
*
* @return TRUE if initialization succeeds. FALSE, otherwise.
*/
static uint8 initNV( void )
{
uint32 pgHdr;
uint8 xferPg = OSAL_NV_PAGE_NULL;
uint8 pg;
failF = FALSE;
activePg = OSAL_NV_PAGE_NULL;
// Pick active page and clean up erased page if necessary
for ( pg = OSAL_NV_PAGE_BEG; pg <= OSAL_NV_PAGE_END; pg++ )
{
HalFlashRead(pg, OSAL_NV_PAGE_HDR_OFFSET, (uint8 *)(&pgHdr), OSAL_NV_PAGE_HDR_SIZE);
if ( pgHdr == OSAL_NV_ACTIVE_PAGE_STATE)
{
if (activePg != OSAL_NV_PAGE_NULL)
{
// Both pages are active only when power failed during flash erase and
// with very low probability.
// As it is hard (code size intensive) to figure out which page is the real active page,
// and theoretically impossible as well in lowest probability, erase both pages
// in this case
cleanErasedPage(activePg);
cleanErasedPage(pg);
activePg = OSAL_NV_PAGE_NULL;
}
else
{
activePg = pg;
}
}
else if ( pgHdr == OSAL_NV_XFER_PAGE_STATE)
{
xferPg = pg;
}
else
{
// Erase this page if it is not erased.
// This is to ensure that any page that were in the middle of
// compacting gets erased.
cleanErasedPage(pg);
}
}
if (activePg == OSAL_NV_PAGE_NULL)
{
if (xferPg == OSAL_NV_PAGE_NULL)
{
// Both pages are erased. This must be initial state.
// Pick one page as active page.
setActivePage(OSAL_NV_PAGE_BEG);
pgOff = OSAL_NV_PAGE_HDR_SIZE;
// If setting active page from a completely erased page failed,
// it is not recommended to operate any further.
// Other cases, even if non-active page is corrupt, NV module can still read
// the active page content and hence this function could return TRUE.
return (!failF);
}
else
{
// Compacting a page hasn't completed in previous power cycle.
// Complete the compacting.
activePg = xferPg;
findOffset();
compactPage(xferPg);
}
}
else
{
if (xferPg != OSAL_NV_PAGE_NULL)
{
// Compacting has completed except for the final step of erasing
// the xferPage.
erasePage(xferPg);
}
// find the active page offset to write a new variable location item
findOffset();
}
return TRUE;
}
/*********************************************************************
* @fn setActivePage
*
* @brief Set page header active state to be active.
*
* @param pg - Valid NV page to activate.
*
* @return none
*/
static void setActivePage( uint8 pg )
{
uint32 pgHdr;
pgHdr = OSAL_NV_ACTIVE_PAGE_STATE;
writeWord( pg, OSAL_NV_PAGE_HDR_OFFSET, (uint8*) &pgHdr );
if (!failF)
{
activePg = pg;
}
}
/*********************************************************************
* @fn setXferPage
*
* @brief Set active page header state to be transfer state.
*
* @param none
*
* @return none
*/
static void setXferPage(void)
{
uint32 pgHdr;
// erase difference bit between active state and xfer state
pgHdr = OSAL_NV_XFER_PAGE_STATE;
writeWord( activePg, OSAL_NV_PAGE_HDR_OFFSET, (uint8*)&pgHdr );
}
/*********************************************************************
* @fn erasePage
*
* @brief Erases a page in Flash.
*
* @param pg - Valid NV page to erase.
*
* @return none
*/
static void erasePage( uint8 pg )
{
if ( !OSAL_NV_CHECK_BUS_VOLTAGE || failF)
{
failF = TRUE;
return;
}
HalFlashErase(pg);
{
// Verify the erase operation
uint16 offset;
uint8 tmp;
for (offset = 0; offset < OSAL_NV_PAGE_SIZE; offset ++)
{
HalFlashRead(pg, offset, &tmp, 1);
if (tmp != OSAL_NV_ERASED)
{
failF = TRUE;
break;
}
}
}
}
/*********************************************************************
* @fn cleanErasedPage
*
* @brief Erases a page in Flash if the page is not completely erased.
*
* @param pg - Valid NV page to erase.
*
* @return none
*/
static void cleanErasedPage( uint8 pg )
{
uint8 buf;
uint16 offset;
for (offset = 0; offset < OSAL_NV_PAGE_SIZE; offset ++)
{
HalFlashRead(pg, offset, &buf, 1);
if (buf != OSAL_NV_ERASED)
{
erasePage(pg);
break;
}
}
}
/*********************************************************************
* @fn findOffset
*
* @brief find an offset of an empty space in active page
* where to write a new item to.
*
* @param None
*
* @return none
*/
static void findOffset(void)
{
uint16 offset;
for (offset = OSAL_NV_PAGE_SIZE - OSAL_NV_WORD_SIZE;
offset >= OSAL_NV_PAGE_HDR_SIZE;
offset -= OSAL_NV_WORD_SIZE)
{
uint32 tmp;
HalFlashRead(activePg, offset, (uint8 *)&tmp, OSAL_NV_WORD_SIZE);
if (tmp != 0xFFFFFFFF)
{
break;
}
}
pgOff = offset + OSAL_NV_WORD_SIZE;
}
/*********************************************************************
* @fn findItem
*
* @brief find a valid item from a designated page and offset
*
* @param pg - NV page
* @param offset - offset in the NV page from where to start
* search up.
* Usually this paramter is set to the empty space
* offset.
* @param id - NV item ID to search for
*
* @return offset of the item, 0 when not found
*/
static uint16 findItem(uint8 pg, uint16 offset, osalSnvId_t id)
{
offset -= OSAL_NV_WORD_SIZE;
while (offset >= OSAL_NV_PAGE_HDR_SIZE)
{
osalNvItemHdr_t hdr;
HalFlashRead(pg, offset, (uint8 *) &hdr, OSAL_NV_WORD_SIZE);
if (hdr.id == id)
{
// item found
// length field could be corrupt. Mask invalid length mark.
uint8 len = hdr.len & ~OSAL_NV_INVALID_LEN_MARK;
return offset - len;
}
else if (hdr.len & OSAL_NV_INVALID_LEN_MARK)
{
offset -= OSAL_NV_WORD_SIZE;
}
else
{
// valid length field
if (hdr.len + OSAL_NV_WORD_SIZE <= offset)
{
// valid length
offset -= hdr.len + OSAL_NV_WORD_SIZE;
}
else
{
// active page is corrupt
// This could happen if NV initialization failed upon failure to erase
// page and active page is set to uncleanly erased page.
HAL_ASSERT_FORCED();
return 0;
}
}
}
return 0;
}
/*********************************************************************
* @fn writeItem
*
* @brief Write a data item to NV. Function can write an entire item to NV
*
* @param pg - Page number
* @param offset - offset within the NV page where to write the new item
* @param id - NV item ID
* @param alignedLen - Length of data to write, alinged in flash word
* boundary
* @param *pBuf - Data to write.
*
* @return none
*/
static void writeItem( uint8 pg, uint16 offset, osalSnvId_t id, uint16 alignedLen, uint8 *pBuf )
{
osalNvItemHdr_t hdr;
hdr.id = 0xFFFF;
hdr.len = alignedLen | OSAL_NV_INVALID_LEN_MARK;
// Write the len portion of the header first
writeWord(pg, offset + alignedLen, (uint8 *) &hdr);
// remove invalid len mark
hdr.len &= ~OSAL_NV_INVALID_LEN_MARK;
writeWord(pg, offset + alignedLen, (uint8 *) &hdr);
// Copy over the data
writeWordM(pg, offset, pBuf, alignedLen / OSAL_NV_WORD_SIZE);
// value is valid. Write header except for the most significant bit.
hdr.id = id | OSAL_NV_INVALID_ID_MARK;
writeWord(pg, offset + alignedLen, (uint8 *) &hdr);
// write the most significant bit
hdr.id &= ~OSAL_NV_INVALID_ID_MARK;
writeWord(pg, offset + alignedLen, (uint8 *) &hdr);
}
/*********************************************************************
* @fn xferItem
*
* @brief Copy an NV item from the active page to a designated page.
*
* @param pg - NV page where to copy the item to.
* @param offset - NV page offset where to copy the item to.
* @param alignedLen - Length of data to write, aligned in flash word
* boundary.
* @param srcOff - NV page offset of the original data in active page
*
* @return none.
*/
static void xferItem( uint8 pg, uint16 offset, uint16 alignedLen, uint16 srcOff )
{
uint8 tmp[OSAL_NV_WORD_SIZE];
uint16 i = 0;
// Copy over the data
while (i <= alignedLen)
{
HalFlashRead(activePg, srcOff + i, tmp, OSAL_NV_WORD_SIZE);
writeWord(pg, offset + i, tmp);
i += OSAL_NV_WORD_SIZE;
}
}
/*********************************************************************
* @fn compactPage
*
* @brief Compacts the page specified.
*
* @param srcPg - Valid NV page to compact from.
* The page must have changed its state (header) to xfer state
* prior to this function call. This function will not
* modify the state of its header to xfer state before starting
* to compact.
*
* @return none.
*/
static void compactPage( uint8 srcPg )
{
uint16 srcOff, dstOff;
uint8 dstPg;
osalSnvId_t lastId = (osalSnvId_t) 0xFFFF;
dstPg = (srcPg == OSAL_NV_PAGE_BEG)? OSAL_NV_PAGE_END : OSAL_NV_PAGE_BEG;
dstOff = OSAL_NV_PAGE_HDR_SIZE;
// Read from the latest value
srcOff = pgOff - sizeof(osalNvItemHdr_t);
while (srcOff >= OSAL_NV_PAGE_HDR_SIZE)
{
osalNvItemHdr_t hdr;
if (failF)
{
// Failure during transfer item will make next findItem error prone.
return;
}
HalFlashRead(srcPg, srcOff, (uint8 *) &hdr, OSAL_NV_WORD_SIZE);
if (hdr.id == 0xFFFF)
{
// Invalid entry. Skip this one.
if (hdr.len & OSAL_NV_INVALID_LEN_MARK)
{
srcOff -= OSAL_NV_WORD_SIZE;
}
else
{
if (hdr.len + OSAL_NV_WORD_SIZE <= srcOff)
{
srcOff -= hdr.len + OSAL_NV_WORD_SIZE;
}
else
{
// invalid length. Source page must be a corrupt page.
// This is possible only if the NV initialization failed upon erasing
// what is selected as active page.
// This is supposed to be a very rare case, as power should be
// shutdown exactly during erase and then the page header is
// still retained as either the Xfer or the Active state.
// For production code, it might be useful to attempt to erase the page
// so that at next power cycle at least the device is runnable
// (with all entries removed).
// However, it might be still better not to attempt erasing the page
// just to see if this very rare case actually happened.
//erasePage(srcPg);
HAL_ASSERT_FORCED();
return;
}
}
continue;
}
// Consider only valid item
if (!(hdr.id & OSAL_NV_INVALID_ID_MARK) && hdr.id != lastId)
{
// lastId is used to speed up compacting in case the same item ID
// items were neighboring each other contiguously.
lastId = (osalSnvId_t) hdr.id;
// Check if the latest value of the item was already written
if (findItem(dstPg, dstOff, lastId) == 0)
{
// This item was not copied over yet.
// This must be the latest value.
// Write the latest value to the destination page
xferItem(dstPg, dstOff, hdr.len, srcOff - hdr.len);
dstOff += hdr.len + OSAL_NV_WORD_SIZE;
}
}
srcOff -= hdr.len + OSAL_NV_WORD_SIZE;
}
// All items copied.
// Activate the new page
setActivePage(dstPg);
if (!failF)
{
pgOff = dstOff; // update active page offset
}
// Erase the currently active page
erasePage(srcPg);
}
/*********************************************************************
* @fn verifyWordM
*
* @brief verify the written word.
*
* @param pg - A valid NV Flash page.
* @param offset - A valid offset into the page.
* @param pBuf - Pointer to source buffer.
* @param cnt - Number of 4-byte blocks to verify.
*
* @return none
*/
static void verifyWordM( uint8 pg, uint16 offset, uint8 *pBuf, osalSnvLen_t cnt )
{
uint8 tmp[OSAL_NV_WORD_SIZE];
while (cnt--)
{
// Reading byte per byte will reduce code size but will slow down
// and not sure it will meet the timing requirements.
HalFlashRead(pg, offset, tmp, OSAL_NV_WORD_SIZE);
if (FALSE == osal_memcmp(tmp, pBuf, OSAL_NV_WORD_SIZE))
{
failF = TRUE;
return;
}
offset += OSAL_NV_WORD_SIZE;
pBuf += OSAL_NV_WORD_SIZE;
}
}
/*********************************************************************
* @fn writeWord
*
* @brief Writes a Flash-WORD to NV.
*
* @param pg - A valid NV Flash page.
* @param offset - A valid offset into the page.
* @param pBuf - Pointer to source buffer.
*
* @return none
*/
static void writeWord( uint8 pg, uint16 offset, uint8 *pBuf )
{
uint16 addr = (offset >> 2) + ((uint16)pg << 9);
if ( !failF )
{
HalFlashWrite(addr, pBuf, 1);
verifyWordM(pg, offset, pBuf, 1);
}
}
/*********************************************************************
* @fn writeWordM
*
* @brief Writes multiple Flash-WORDs to NV.
*
* @param pg - A valid NV Flash page.
* @param offset - A valid offset into the page.
* @param buf - Pointer to source buffer.
* @param cnt - Number of 4-byte blocks to write.
*
* @return none
*/
static void writeWordM( uint8 pg, uint16 offset, uint8 *buf, osalSnvLen_t cnt )
{
uint16 addr = (offset >> 2) + ((uint16)pg << 9);
if ( !failF )
{
HalFlashWrite(addr, buf, cnt);
verifyWordM(pg, offset, buf, cnt);
}
}
/*********************************************************************
* @fn osal_snv_init
*
* @brief Initialize NV service.
*
* @return SUCCESS if initialization succeeds. FAILURE, otherwise.
*/
uint8 osal_snv_init( void )
{
if (!initNV())
{
// NV initialization failed
HAL_ASSERT_FORCED();
return FAILURE;
}
return SUCCESS;
}
/*********************************************************************
* @fn osal_snv_write
*
* @brief Write a data item to NV.
*
* @param id - Valid NV item Id.
* @param len - Length of data to write.
* @param *pBuf - Data to write.
*
* @return SUCCESS if successful, NV_OPER_FAILED if failed.
*/
uint8 osal_snv_write( osalSnvId_t id, osalSnvLen_t len, void *pBuf )
{
uint16 alignedLen;
{
uint16 offset = findItem(activePg, pgOff, id);
if (offset > 0)
{
uint8 tmp;
osalSnvLen_t i;
for (i = 0; i < len; i++)
{
HalFlashRead(activePg, offset, &tmp, 1);
if (tmp != ((uint8 *)pBuf)[i])
{
break;
}
offset++;
}
if (i == len)
{
// Changed value is the same value as before.
// Return here instead of re-writing the same value to NV.
return SUCCESS;
}
}
}
alignedLen = ((len + OSAL_NV_WORD_SIZE - 1) / OSAL_NV_WORD_SIZE) * OSAL_NV_WORD_SIZE;
if ( pgOff + alignedLen + OSAL_NV_WORD_SIZE > OSAL_NV_PAGE_SIZE )
{
setXferPage();
compactPage(activePg);
}
// pBuf shall be referenced beyond its valid length to save code size.
writeItem(activePg, pgOff, id, alignedLen, pBuf);
if (failF)
{
return NV_OPER_FAILED;
}
pgOff += alignedLen + OSAL_NV_WORD_SIZE;
return SUCCESS;
}
/*********************************************************************
* @fn osal_snv_read
*
* @brief Read data from NV.
*
* @param id - Valid NV item Id.
* @param len - Length of data to read.
* @param *pBuf - Data is read into this buffer.
*
* @return SUCCESS if successful.
* Otherwise, NV_OPER_FAILED for failure.
*/
uint8 osal_snv_read( osalSnvId_t id, osalSnvLen_t len, void *pBuf )
{
uint16 offset = findItem(activePg, pgOff, id);
if (offset != 0)
{
HalFlashRead(activePg, offset, pBuf, len);
return SUCCESS;
}
return NV_OPER_FAILED;
}
/*********************************************************************
* @fn osal_snv_compact
*
* @brief Compacts NV if its usage has reached a specific threshold.
*
* @param threshold - compaction threshold
*
* @return SUCCESS if successful,
* NV_OPER_FAILED if failed, or
* INVALIDPARAMETER if threshold invalid.
*/
uint8 osal_snv_compact( uint8 threshold )
{
if ( ( threshold < OSAL_NV_MIN_COMPACT_THRESHOLD ) ||
( threshold > OSAL_NV_MAX_COMPACT_THRESHOLD ) )
{
return INVALIDPARAMETER;
}
// See if NV active page usage has reached compaction threshold
if ( ( (uint32)pgOff * 100 ) >= ( OSAL_NV_PAGE_SIZE * (uint32)threshold ) )
{
setXferPage();
compactPage(activePg);
return SUCCESS;
}
return NV_OPER_FAILED;
}
/*********************************************************************
*********************************************************************/