📄 osal_nv.c
字号:
/*********************************************************************
Filename: OSAL_Nv.c
Revised: $Date: 2007-04-11 14:41:47 -0700 (Wed, 11 Apr 2007) $
Revision: $Revision: 13998 $
Description: This module contains the OSAL non-volatile memory functions.
Notes: A trick buried deep in initPage() requires that the MSB of the NV
Item Id be reserved for use by this module.
Copyright (c) 2007 by Texas Instruments, Inc.
All Rights Reserved. Permission to use, reproduce, copy, prepare
derivative works, modify, distribute, perform, display or sell this
software and/or its documentation for any purpose is prohibited
without the express written consent of Texas Instruments, Inc.
*********************************************************************/
/*********************************************************************
* INCLUDES
*/
#include "ZComDef.h"
#include "hal_adc.h"
#include "hal_dma.h"
#include "osal.h"
#include "OSAL_Nv.h"
#include <ioCC2430.h>
#if !defined ( OSAL_NV_CLEANUP )
#define OSAL_NV_CLEANUP FALSE
#endif
/*********************************************************************
* CONSTANTS
*/
#define OSAL_NV_DMA_CH (&dmaCh0)
#define OSAL_NV_ACTIVE 0x00
#define OSAL_NV_ERASED 0xFF
#define OSAL_NV_ERASED_ID 0xFFFF
#define OSAL_NV_ZEROED_ID 0x0000
#define OSAL_NV_PAGE_FREE (OSAL_NV_PAGE_SIZE - Z_EXTADDR_LEN)
/* The last Flash page will reserve an IEEE addr block at the end of the page where the tools know
* to program the IEEE.
*/
#define OSAL_NV_IEEE_OFFSET (OSAL_NV_PAGE_SIZE - Z_EXTADDR_LEN)
#define OSAL_NV_IEEE_PAGE 63
// 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
#define OSAL_NV_WORD_SIZE 4
#define OSAL_NV_PAGE_HDR_OFFSET 0
/*********************************************************************
* MACROS
*/
#define OSAL_NV_PAGE_ERASE( pg ) \
st( \
FADDRH = (pg) << 1; \
FCTL = 0x01; \
asm("NOP"); \
while(FCTL == 0x80); \
)
#define OSAL_NV_PAGE_TO_ADDR( pg ) ((uint32)pg << 11)
#define OSAL_NV_ADDR_TO_PAGE( addr ) ((uint8)(addr >> 11))
#define OSAL_NV_CHECK_BUS_VOLTAGE (HalAdcCheckVdd( HAL_ADC_VDD_LIMIT_4 ))
/*********************************************************************
* TYPEDEFS
*/
typedef struct
{
uint16 id;
uint16 len; // Enforce Flash-WORD size on len.
uint16 chk; // Byte-wise checksum of the 'len' data bytes of the item.
uint16 stat; // Item status.
} osalNvHdr_t;
// Struct member offsets.
#define OSAL_NV_HDR_ID 0
#define OSAL_NV_HDR_LEN 2
#define OSAL_NV_HDR_CHK 4
#define OSAL_NV_HDR_STAT 6
#define OSAL_NV_HDR_ITEM 2 // Length of any item of a header struct.
#define OSAL_NV_HDR_SIZE 8
#define OSAL_NV_HDR_HALF (OSAL_NV_HDR_SIZE / 2)
typedef struct
{
uint16 active;
uint16 inUse;
uint16 xfer;
uint16 spare;
} osalNvPgHdr_t;
// Struct member offsets.
#define OSAL_NV_PG_ACTIVE 0
#define OSAL_NV_PG_INUSE 2
#define OSAL_NV_PG_XFER 4
#define OSAL_NV_PG_SPARE 6
#define OSAL_NV_PAGE_HDR_SIZE 8
#define OSAL_NV_PAGE_HDR_HALF (OSAL_NV_PAGE_HDR_SIZE / 2)
typedef enum
{
eNvXfer,
eNvZero
} eNvHdrEnum;
typedef enum
{
ePgActive,
ePgInUse,
ePgXfer,
ePgSpare
} ePgHdrEnum;
/*********************************************************************
* GLOBAL VARIABLES
*/
uint8 __xdata FBuff[4]; // Flash buffer for DMA transfer.
/*********************************************************************
* EXTERNAL VARIABLES
*/
/*********************************************************************
* EXTERNAL FUNCTIONS
*/
extern __near_func uint8 GetCodeByte(uint32);
extern __near_func void halFlashDmaTrigger(void);
extern bool HalAdcCheckVdd(uint8 limit);
/*********************************************************************
* LOCAL VARIABLES
*/
// Offset into the page of the first available erased space.
static uint16 pgOff[OSAL_NV_PAGES_USED];
// Count of the bytes lost for the zeroed-out items.
static uint16 pgLost[OSAL_NV_PAGES_USED];
static uint8 pgRes; // Page reserved for item compacting transfer.
/* It saves ~100 code bytes to move a uint8* parameter/return value from findItem()
* to this local global.
*/
static uint8 findPg;
/* Immediately before the voltage critical operations of a page erase or
* a word write, check bus voltage. If less than min, set global flag & abort.
* Since this is to be done at the lowest level, many void functions would have to be changed to
* return a value and code added to check that value before proceeding, resulting in a very
* expensive code size hit for implementing this properly. Therefore, use this global as follows:
* at the start of osal_nv_item_init/osal_nv_write, set to FALSE, and at the end, before returning,
* check the value. Thus, the global is an accumulator of any error that occurred in any of the
* attempts to modify Flash with a low bus voltage during the complicated sequence of events that
* may occur on any item init or write. This is much more expedient and code saving than adding
* return values and checking return values to early out. No matter which method is used, an NV
* data record may end up mangled due to the low VCC conditions. The strategy is that the headers
* and checksums will detect and allow recovery from such a condition.
*
* One unfortunate side-effect of using the global fail flag vice adding and checking return
* values, is that setItem(), unaware that setting an item Id to zero has failed due to the low VCC
* check, will still update the page Lost bytes counter. Having an artificially high lost byte
* count makes it look like there are more bytes to recover from compacting a page than there may
* actually be. An easy work-around it to invoke initNV() from osal_nv_item_init or osal_nv_write
* anytime that the failF gets set - this will re-walk all of the pages and set the page offset
* count and page lost bytes count to their actual values.
*/
static uint8 failF;
/*********************************************************************
* LOCAL FUNCTIONS
*/
static void initDMA( void );
static void execDMA( void );
static uint8 initNV( void );
static void setPageUse( uint8 pg, uint8 inUse );
static uint16 initPage( uint8 pg, uint16 id );
static void erasePage( uint8 pg );
static void compactPage( uint8 pg );
static uint16 findItem( uint16 id );
static uint8 initItem( uint16 id, uint16 len, void *buf );
static uint8 initItem2( uint16 id, uint16 len, uint8 *comPg );
static void setItem( uint8 pg, uint16 offset, eNvHdrEnum stat );
static uint16 calcChkB( uint16 len, uint8 *buf );
static uint16 calcChkF( byte pg, uint16 offset, uint16 len );
static void readHdr( uint8 pg, uint16 offset, uint8 *buf );
static void readWord( uint8 pg, uint16 offset, uint8 *buf );
static void writeWord( uint8 pg, uint16 offset, uint8 *buf );
static void writeWordD( uint8 pg, uint16 offset, uint8 *buf );
static void writeWordH( uint8 pg, uint16 offset, uint8 *buf );
static void writeBuf( uint8 pg, uint16 offset, uint16 len, uint8 *buf );
static void xferBuf( uint8 srcPg, uint16 srcOff, uint8 dstPg, uint16 dstOff, uint16 len );
static uint8 writeItem( uint8 pg, uint16 id, uint16 len, void *buf );
/*********************************************************************
* @fn initDMA
*
* @brief Initialize the DMA Channel for NV flash operations.
*
* @param none
*
* @return None
*/
static void initDMA( void )
{
// Start address of the destination is the Flash Write Data register.
OSAL_NV_DMA_CH->dstAddrH = 0xdf;
OSAL_NV_DMA_CH->dstAddrL = 0xaf;
OSAL_NV_DMA_CH->srcAddrH = (uint16)FBuff >> 8;
OSAL_NV_DMA_CH->srcAddrL = (uint16)FBuff;
// Using the length field to determine how many bytes to transfer.
HAL_DMA_SET_VLEN( OSAL_NV_DMA_CH, HAL_DMA_VLEN_USE_LEN );
// Transfer 4 bytes at a time.
HAL_DMA_SET_LEN( OSAL_NV_DMA_CH, OSAL_NV_WORD_SIZE );
// Transfer size is 1 byte.
HAL_DMA_SET_WORD_SIZE( OSAL_NV_DMA_CH, HAL_DMA_WORDSIZE_BYTE );
// After every 4-byte transfer, must await Flash write done indication.
HAL_DMA_SET_TRIG_MODE( OSAL_NV_DMA_CH, HAL_DMA_TMODE_SINGLE );
HAL_DMA_SET_TRIG_SRC( OSAL_NV_DMA_CH, HAL_DMA_TRIG_FLASH );
// The source address is incremented by 1 byte after each transfer.
HAL_DMA_SET_SRC_INC( OSAL_NV_DMA_CH, HAL_DMA_SRCINC_1 );
// The destination address is constant - the Flash Write Data register.
HAL_DMA_SET_DST_INC( OSAL_NV_DMA_CH, HAL_DMA_DSTINC_0 );
// The DMA is to be polled and shall not issue an IRQ upon completion.
HAL_DMA_SET_IRQ( OSAL_NV_DMA_CH, HAL_DMA_IRQMASK_DISABLE );
// Xfer all 8 bits of a byte xfer.
HAL_DMA_SET_M8( OSAL_NV_DMA_CH, HAL_DMA_M8_USE_8_BITS );
// DMA memory access has highest priority.
HAL_DMA_SET_PRIORITY( OSAL_NV_DMA_CH, HAL_DMA_PRI_HIGH );
}
/*********************************************************************
* @fn execDMA
*
* @brief Arms and triggers a DMA write to Flash memory.
*
* @param none
*
* @return none
*/
static void execDMA( void )
{
if ( !OSAL_NV_CHECK_BUS_VOLTAGE )
{
failF = TRUE;
return;
}
HAL_DMA_CLEAR_IRQ( 0 );
HAL_DMA_ARM_CH( 0 );
halFlashDmaTrigger();
while ( !(HAL_DMA_CHECK_IRQ( 0 )) );
while ( FCTL & FWBUSY );
}
/*********************************************************************
* @fn initNV
*
* @brief Initialize the NV flash pages.
*
* @param none
*
* @return TRUE if NV pages seem ok; FALSE otherwise.
*/
static uint8 initNV( void )
{
osalNvPgHdr_t pgHdr, ieee;
uint8 oldPg = OSAL_NV_PAGE_NULL;
uint8 newPg = OSAL_NV_PAGE_NULL;
uint8 xBad;
uint8 pg;
readHdr( OSAL_NV_IEEE_PAGE, OSAL_NV_IEEE_OFFSET, (uint8 *)(&ieee) );
if ( (ieee.active == OSAL_NV_ERASED_ID) &&
(ieee.inUse == OSAL_NV_ERASED_ID) &&
(ieee.xfer == OSAL_NV_ERASED_ID) &&
(ieee.spare == OSAL_NV_ERASED_ID) )
{
xBad = TRUE;
}
else
{
xBad = FALSE;
}
pgRes = OSAL_NV_PAGE_NULL;
for ( pg = OSAL_NV_PAGE_BEG; pg <= OSAL_NV_PAGE_END; pg++ )
{
#if OSAL_NV_CLEANUP
OSAL_NV_PAGE_ERASE( pg );
asm( "NOP" );
#endif
readHdr( pg, OSAL_NV_PAGE_HDR_OFFSET, (uint8 *)(&pgHdr) );
if ( pgHdr.active == OSAL_NV_ERASED_ID )
{
if ( pgRes == OSAL_NV_PAGE_NULL )
{
pgRes = pg;
}
else
{
setPageUse( pg, TRUE );
}
}
else // Page is active.
{
// If the page is not yet in use, it is the tgt of items from an xfer.
if ( pgHdr.inUse == OSAL_NV_ERASED_ID )
{
newPg = pg;
}
// An Xfer from this page was in progress.
else if ( pgHdr.xfer != OSAL_NV_ERASED_ID )
{
oldPg = pg;
}
}
// Calculate page offset and lost bytes.
initPage( pg, OSAL_NV_ITEM_NULL );
readHdr( pg, OSAL_NV_IEEE_OFFSET, (uint8 *)(&pgHdr) );
if ( xBad )
{
/* TBD - For the cost of more code space, the IEEE could be checksummed & then tested here
* before installing to the erased IEEE on page 63.
*/
if ( (pgHdr.active != OSAL_NV_ERASED_ID) ||
(pgHdr.inUse != OSAL_NV_ERASED_ID) ||
(pgHdr.xfer != OSAL_NV_ERASED_ID) ||
(pgHdr.spare != OSAL_NV_ERASED_ID) )
{
writeWordD( OSAL_NV_IEEE_PAGE, OSAL_NV_IEEE_OFFSET, (uint8 *)(&pgHdr) );
xBad = FALSE;
}
}
else
{
if ( (pgHdr.active == OSAL_NV_ERASED_ID) &&
(pgHdr.inUse == OSAL_NV_ERASED_ID) &&
(pgHdr.xfer == OSAL_NV_ERASED_ID) &&
(pgHdr.spare == OSAL_NV_ERASED_ID) )
{
writeWordD( pg, OSAL_NV_IEEE_OFFSET, (uint8 *)(&ieee) );
}
}
} // for ( pg = OSAL_NV_PAGE_BEG; pg <= OSAL_NV_PAGE_END; pg++ )
/* First the old page is erased, and then the new page is put into use.
* So if a transfer was in progress, the new page will always not yet be
* marked as in use, since that is the last step to ending a transfer.
*/
if ( newPg != OSAL_NV_PAGE_NULL )
{
/* If there is already a fallow page reserved, keep it and put the newPg in use.
* An unfinished compaction will finish to the new reserve page and the old page
* will be erased and reserved.
*/
if ( pgRes != OSAL_NV_PAGE_NULL )
{
setPageUse( newPg, TRUE );
}
else
{
pgRes = newPg;
}
/* If a page compaction was interrupted and the page being compacted is not
* yet erased, then there may be items remaining to xfer before erasing.
*/
if ( oldPg != OSAL_NV_PAGE_NULL )
{
compactPage( oldPg );
}
}
return (pgRes != OSAL_NV_PAGE_NULL);
}
/*********************************************************************
* @fn setPageUse
*
* @brief Set page header active/inUse state according to 'inUse'.
*
* @param pg - Valid NV page to verify and init.
* @param inUse - Boolean TRUE if inUse, FALSE if only active.
*
* @return none
*/
static void setPageUse( uint8 pg, uint8 inUse )
{
osalNvPgHdr_t pgHdr;
pgHdr.active = OSAL_NV_ZEROED_ID;
if ( inUse )
{
pgHdr.inUse = OSAL_NV_ZEROED_ID;
}
else
{
pgHdr.inUse = OSAL_NV_ERASED_ID;
}
writeWord( pg, OSAL_NV_PAGE_HDR_OFFSET, (uint8*)(&pgHdr) );
}
/*********************************************************************
* @fn initPage
*
* @brief Walk the page items; calculate checksums, lost bytes & page offset.
*
* @param pg - Valid NV page to verify and init.
* @param id - Valid NV item Id to use function as a "findItem".
* If set to NULL then just perform the page initialization.
*
* @return If 'id' is non-NULL and good checksums are found, return the offset
* of the data corresponding to item Id; else OSAL_NV_ITEM_NULL.
*/
static uint16 initPage( uint8 pg, uint16 id )
{
uint16 offset = OSAL_NV_PAGE_HDR_SIZE;
uint16 sz, lost = 0;
osalNvHdr_t hdr;
do
{
readHdr( pg, offset, (uint8 *)(&hdr) );
if ( hdr.id == OSAL_NV_ERASED_ID )
{
break;
}
offset += OSAL_NV_HDR_SIZE;
sz = ((hdr.len + (OSAL_NV_WORD_SIZE-1)) / OSAL_NV_WORD_SIZE) * OSAL_NV_WORD_SIZE;
// A bad 'len' write has blown away the rest of the page.
if ( (offset + sz) > OSAL_NV_PAGE_FREE )
{
lost += (OSAL_NV_PAGE_FREE - offset + OSAL_NV_HDR_SIZE);
offset = OSAL_NV_PAGE_FREE;
break;
}
if ( hdr.id != OSAL_NV_ZEROED_ID )
{
if ( hdr.chk == calcChkF( pg, offset, hdr.len ) )
{
/* This trick allows function to do double duty for findItem() without
* compromising its essential functionality at powerup initialization.
*/
if ( id != OSAL_NV_ITEM_NULL )
{
/* This trick allows asking to find the old/transferred item in case
* of a successful new item write that gets interrupted before the
* old item can be zeroed out.
*/
if ( (id & 0x7fff) == hdr.id )
{
if ( (((id & 0x8000) == 0) && (hdr.stat == OSAL_NV_ERASED_ID)) ||
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -