/********************************************************************* * @fn writeItem * * @brief Writes an item header/data combo to the specified NV page. * * @param pg - Valid NV Flash page. * @param id - Valid NV item Id. * @param len - Byte count of the data to write. * @param buf - The data to write. If NULL, no data/checksum write. * @param flag - TRUE if the checksum should be written, FALSE otherwise. * * @return TRUE if header/data to write matches header/data read back, else FALSE. */ static uint8 writeItem( uint8 pg, uint16 id, uint16 len, void *buf, uint8 flag ) { uint16 offset = pgOff[pg-OSAL_NV_PAGE_BEG]; uint8 rtrn = FALSE; osalNvHdr_t hdr; hdr.id = id; hdr.len = len; writeWord( pg, offset, (uint8 *)&hdr ); HalFlashRead(pg, offset, (uint8 *)(&hdr), OSAL_NV_HDR_SIZE); if ( (hdr.id == id) && (hdr.len == len) ) { if ( flag ) { hdr.chk = calcChkB( len, buf ); offset += OSAL_NV_HDR_SIZE; if ( buf != NULL ) { writeBuf( pg, offset, len, buf ); } if ( hdr.chk == calcChkF( pg, offset, len ) ) { if ( hdr.chk == setChk( pg, offset, hdr.chk ) ) { hotItemUpdate(pg, offset, hdr.id); rtrn = TRUE; } } } else { rtrn = TRUE; } len = OSAL_NV_ITEM_SIZE( hdr.len ); } else { len = OSAL_NV_ITEM_SIZE( hdr.len ); if (len > (OSAL_NV_PAGE_SIZE - pgOff[pg - OSAL_NV_PAGE_BEG])) { len = (OSAL_NV_PAGE_SIZE - pgOff[pg - OSAL_NV_PAGE_BEG]); } pgLost[pg - OSAL_NV_PAGE_BEG] += len; } pgOff[pg - OSAL_NV_PAGE_BEG] += len; return rtrn; }
/********************************************************************* * @fn compactPage * * @brief Compacts the page specified. * * @param srcPg - Valid NV page to erase. * * @return none */ static void compactPage( uint8 srcPg ) { uint16 dstOff = pgOff[pgRes-OSAL_NV_PAGE_BEG]; uint16 srcOff = OSAL_NV_ZEROED_ID; osalNvHdr_t hdr; // Mark page as being in process of compaction. writeWordH( srcPg, OSAL_NV_PG_XFER, (uint8*)(&srcOff) ); srcOff = OSAL_NV_PAGE_HDR_SIZE; do { uint16 sz; HalFlashRead(srcPg, srcOff, (uint8 *)(&hdr), OSAL_NV_HDR_SIZE); if ( hdr.id == OSAL_NV_ERASED_ID ) { break; } srcOff += OSAL_NV_HDR_SIZE; if ( (srcOff + hdr.len) > OSAL_NV_PAGE_FREE ) { break; } sz = OSAL_NV_DATA_SIZE( hdr.len ); if ( hdr.id != OSAL_NV_ZEROED_ID ) { if ( hdr.chk == calcChkF( srcPg, srcOff, hdr.len ) ) { setItem( srcPg, srcOff, eNvXfer ); writeBuf( pgRes, dstOff, OSAL_NV_HDR_SIZE, (byte *)(&hdr) ); dstOff += OSAL_NV_HDR_SIZE; xferBuf( srcPg, srcOff, pgRes, dstOff, sz ); dstOff += sz; } setItem( srcPg, srcOff, eNvZero ); // Mark old location as invalid. } srcOff += sz; } while ( TRUE ); pgOff[pgRes-OSAL_NV_PAGE_BEG] = dstOff; /* In order to recover from a page compaction that is interrupted, * the logic in osal_nv_init() depends upon the following order: * 1. Compacted page is erased. * 2. State of the target of compaction is changed ePgActive to ePgInUse. */ erasePage( srcPg ); // Mark the reserve page as being in use. setPageUse( pgRes, TRUE ); // Set the reserve page to be the newly erased page. pgRes = srcPg; }
/********************************************************************* * @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, uint8 findDups ) { uint16 offset = OSAL_NV_PAGE_HDR_SIZE; uint16 sz, lost = 0; osalNvHdr_t hdr; do { HalFlashRead(pg, offset, (uint8 *)(&hdr), OSAL_NV_HDR_SIZE); if ( hdr.id == OSAL_NV_ERASED_ID ) { break; } offset += OSAL_NV_HDR_SIZE; sz = OSAL_NV_DATA_SIZE( hdr.len ); // 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 ) { /* 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 & OSAL_NV_SOURCE_ID) == 0) && (hdr.stat == OSAL_NV_ERASED_ID)) || (((id & OSAL_NV_SOURCE_ID) != 0) && (hdr.stat != OSAL_NV_ERASED_ID)) ) { return offset; } } } // When invoked from the osal_nv_init(), verify checksums and find & zero any duplicates. else { if ( hdr.chk == calcChkF( pg, offset, hdr.len ) ) { if ( findDups ) { if ( hdr.stat == OSAL_NV_ERASED_ID ) { /* The trick of setting the MSB of the item Id causes the logic * immediately above to return a valid page only if the header 'stat' * indicates that it was the older item being transferred. */ uint16 off = findItem( (hdr.id | OSAL_NV_SOURCE_ID) ); if ( off != OSAL_NV_ITEM_NULL ) { setItem( findPg, off, eNvZero ); // Mark old duplicate as invalid. } } } // Any "old" item immediately exits and triggers the N^2 exhaustive initialization. else if ( hdr.stat != OSAL_NV_ERASED_ID ) { return OSAL_NV_ERASED_ID; } } else { setItem( pg, offset, eNvZero ); // Mark bad checksum as invalid. lost += (OSAL_NV_HDR_SIZE + sz); } } } else { lost += (OSAL_NV_HDR_SIZE + sz); } offset += sz; } while ( TRUE ); pgOff[pg - OSAL_NV_PAGE_BEG] = offset; pgLost[pg - OSAL_NV_PAGE_BEG] = lost; return OSAL_NV_ITEM_NULL; }
/********************************************************************* * @fn compactPage * * @brief Compacts the page specified. * * @param srcPg - Valid NV page to erase. * @param skipId - Item Id to not compact. * * @return TRUE if valid items from 'srcPg' are successully compacted onto the 'pgRes'; * FALSE otherwise. * Note that on a failure, this could loop, re-erasing the 'pgRes' and re-compacting with * the risk of infinitely looping on HAL flash failure. * Worst case scenario: HAL flash starts failing in general, perhaps low Vdd? * All page compactions will fail which will cause all osal_nv_write() calls to return * NV_OPER_FAILED. * Eventually, all pages in use may also be in the state of "pending compaction" where * the page header member OSAL_NV_PG_XFER is zeroed out. * During this "HAL flash brown-out", the code will run and OTA should work (until low Vdd * causes an actual chip brown-out, of course.) Although no new NV items will be created * or written, the last value written with a return value of SUCCESS can continue to be * read successfully. * If eventually HAL flash starts working again, all of the pages marked as * "pending compaction" may or may not be eventually compacted. But, initNV() will * deterministically clean-up one page pending compaction per power-cycle * (if HAL flash is working.) Nevertheless, one erased reserve page will be maintained * through such a scenario. */ static uint8 compactPage( uint8 srcPg, uint16 skipId ) { uint16 srcOff; uint8 rtrn; // To minimize code size, only check for a clean page here where it's absolutely required. for (srcOff = 0; srcOff < OSAL_NV_PAGE_SIZE; srcOff++) { HalFlashRead(pgRes, srcOff, &rtrn, 1); if (rtrn != OSAL_NV_ERASED) { erasePage(pgRes); return FALSE; } } srcOff = OSAL_NV_PAGE_HDR_SIZE; rtrn = TRUE; while ( srcOff < (OSAL_NV_PAGE_SIZE - OSAL_NV_HDR_SIZE ) ) { osalNvHdr_t hdr; uint16 sz, dstOff = pgOff[pgRes-OSAL_NV_PAGE_BEG]; HalFlashRead(srcPg, srcOff, (uint8 *)(&hdr), OSAL_NV_HDR_SIZE); if ( hdr.id == OSAL_NV_ERASED_ID ) { break; } // Get the actual size in bytes which is the ceiling(hdr.len) sz = OSAL_NV_DATA_SIZE( hdr.len ); if ( sz > (OSAL_NV_PAGE_SIZE - OSAL_NV_HDR_SIZE - srcOff) ) { break; } if ( sz > (OSAL_NV_PAGE_SIZE - OSAL_NV_HDR_SIZE - dstOff) ) { rtrn = FALSE; break; } srcOff += OSAL_NV_HDR_SIZE; if ( (hdr.id != OSAL_NV_ZEROED_ID) && (hdr.id != skipId) ) { if ( hdr.chk == calcChkF( srcPg, srcOff, hdr.len ) ) { /* Prevent excessive re-writes to item header caused by numerous, rapid, & successive * OSAL_Nv interruptions caused by resets. */ if ( hdr.stat == OSAL_NV_ERASED_ID ) { setItem( srcPg, srcOff, eNvXfer ); } if ( writeItem( pgRes, hdr.id, hdr.len, NULL, FALSE ) ) { dstOff += OSAL_NV_HDR_SIZE; xferBuf( srcPg, srcOff, pgRes, dstOff, sz ); // Calculate and write the new checksum. if (hdr.chk == calcChkF(pgRes, dstOff, hdr.len)) { if ( hdr.chk != setChk( pgRes, dstOff, hdr.chk ) ) { rtrn = FALSE; break; } else { hotItemUpdate(pgRes, dstOff, hdr.id); } } else { rtrn = FALSE; break; } } else { rtrn = FALSE; break; } } } srcOff += sz; } if (rtrn == FALSE) { erasePage(pgRes); } else if (skipId == OSAL_NV_ITEM_NULL) { COMPACT_PAGE_CLEANUP(srcPg); } // else invoking function must cleanup. return rtrn; }
/********************************************************************* * @fn osal_nv_write * * @brief Write a data item to NV. Function can write an entire item to NV or * an element of an item by indexing into the item with an offset. * * @param id - Valid NV item Id. * @param ndx - Index offset into item * @param len - Length of data to write. * @param *buf - Data to write. * * @return SUCCESS if successful, NV_ITEM_UNINIT if item did not * exist in NV and offset is non-zero, NV_OPER_FAILED if failure. */ uint8 osal_nv_write( uint16 id, uint16 ndx, uint16 len, void *buf ) { uint8 rtrn = SUCCESS; if ( !OSAL_NV_CHECK_BUS_VOLTAGE ) { return NV_OPER_FAILED; } else if ( len != 0 ) { osalNvHdr_t hdr; uint16 origOff, srcOff; uint16 cnt, chk; uint8 *ptr, srcPg; origOff = srcOff = findItem( id ); srcPg = findPg; if ( srcOff == OSAL_NV_ITEM_NULL ) { return NV_ITEM_UNINIT; } HalFlashRead(srcPg, (srcOff - OSAL_NV_HDR_SIZE), (uint8 *)(&hdr), OSAL_NV_HDR_SIZE); if ( hdr.len < (ndx + len) ) { return NV_OPER_FAILED; } srcOff += ndx; ptr = buf; cnt = len; chk = 0; while ( cnt-- ) { uint8 tmp; HalFlashRead(srcPg, srcOff, &tmp, 1); if ( tmp != *ptr ) { chk = 1; // Mark that at least one byte is different. // Calculate expected checksum after transferring old data and writing new data. hdr.chk -= tmp; hdr.chk += *ptr; } srcOff++; ptr++; } if ( chk != 0 ) // If the buffer to write is different in one or more bytes. { uint8 comPg = OSAL_NV_PAGE_NULL; uint8 dstPg = initItem( FALSE, id, hdr.len, &comPg ); if ( dstPg != OSAL_NV_PAGE_NULL ) { uint16 tmp = OSAL_NV_DATA_SIZE( hdr.len ); uint16 dstOff = pgOff[dstPg-OSAL_NV_PAGE_BEG] - tmp; srcOff = origOff; /* Prevent excessive re-writes to item header caused by numerous, rapid, & successive * OSAL_Nv interruptions caused by resets. */ if ( hdr.stat == OSAL_NV_ERASED_ID ) { setItem( srcPg, srcOff, eNvXfer ); } xferBuf( srcPg, srcOff, dstPg, dstOff, ndx ); srcOff += ndx; dstOff += ndx; writeBuf( dstPg, dstOff, len, buf ); srcOff += len; dstOff += len; xferBuf( srcPg, srcOff, dstPg, dstOff, (hdr.len-ndx-len) ); // Calculate and write the new checksum. dstOff = pgOff[dstPg-OSAL_NV_PAGE_BEG] - tmp; if ( hdr.chk == calcChkF( dstPg, dstOff, hdr.len ) ) { if ( hdr.chk != setChk( dstPg, dstOff, hdr.chk ) ) { rtrn = NV_OPER_FAILED; } else { hotItemUpdate(dstPg, dstOff, hdr.id); } } else { rtrn = NV_OPER_FAILED; } } else { rtrn = NV_OPER_FAILED; } if ( comPg != OSAL_NV_PAGE_NULL ) { /* Even though the page compaction succeeded, if the new item is coming from the compacted * page and writing the new value failed, then the compaction must be aborted. */ if ( (srcPg == comPg) && (rtrn == NV_OPER_FAILED) ) { erasePage( pgRes ); } else { COMPACT_PAGE_CLEANUP( comPg ); } } /* Zero of the old item must wait until after compact page cleanup has finished - if the item * is zeroed before and cleanup is interrupted by a power-cycle, the new item can be lost. */ if ( (srcPg != comPg) && (rtrn != NV_OPER_FAILED) ) { setItem( srcPg, origOff, eNvZero ); } } } return rtrn; }