usbMsgLen_t usbFunctionSetup(uint8_t data[8]) { usbRequest_t *rq = (void *)data; usbMsgPtr = usbOutputBuffer; if (rq->bRequest == REQ_TEST_PROGRAMMER) { usbOutputBuffer[0] = ERROR_OK; usbOutputBuffer[1] = MAJOR_VERSION; usbOutputBuffer[2] = MINOR_VERSION; usbOutputBuffer[3] = rq->wValue.bytes[0]; usbOutputBuffer[4] = rq->wValue.bytes[1]; usbOutputBuffer[5] = rq->wIndex.bytes[0]; usbOutputBuffer[6] = rq->wIndex.bytes[1]; return 7; } else if (rq->bRequest == REQ_TURN_PROG_ON) { return turnProgOn(); } else if (rq->bRequest == REQ_TURN_PROG_OFF) { return turnProgOff(); } else if (rq->bRequest == REQ_TEST_NRF) { return nrfTest(); } else if (rq->bRequest == REQ_PROGRAM_PAGE) { return programPage(rq->wValue.word, rq->wIndex.word); } else if(rq->bRequest == REQ_READ) { return readMemory(rq->wValue.word, rq->wIndex.word); } else if(rq->bRequest == REQ_ERASE_PAGE) { return erasePage(rq->wValue.word); } return 0; }
/********************************************************************* * @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 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 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 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 initNV * * @brief Initialize the NV flash pages. * * @param none * * @return TRUE */ static uint8 initNV( void ) { osalNvPgHdr_t pgHdr; uint8 oldPg = OSAL_NV_PAGE_NULL; uint8 newPg = OSAL_NV_PAGE_NULL; uint8 findDups = FALSE; uint8 pg; pgRes = OSAL_NV_PAGE_NULL; for ( pg = OSAL_NV_PAGE_BEG; pg <= OSAL_NV_PAGE_END; pg++ ) { HalFlashRead(pg, OSAL_NV_PAGE_HDR_OFFSET, (uint8 *)(&pgHdr), OSAL_NV_HDR_SIZE); 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 - any "old" item triggers an N^2 re-scan from start. if ( initPage( pg, OSAL_NV_ITEM_NULL, findDups ) != OSAL_NV_ITEM_NULL ) { findDups = TRUE; pg = OSAL_NV_PAGE_BEG-1; continue; } } // 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 ); } /* If setting old page to 'xfer' failed or board reset before it was effected, there is no way * to know which page was the 'old page' - so just reset all NV pages to start clean. */ else if ( oldPg != OSAL_NV_PAGE_NULL ) { 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 ); } } /* If no page met the criteria to be the reserve page: * - A compactPage() failed or board reset before doing so. * - Perhaps the user changed which Flash pages are dedicated to NV and downloaded the code * without erasing Flash? */ if ( pgRes == OSAL_NV_PAGE_NULL ) { for ( pg = OSAL_NV_PAGE_BEG; pg <= OSAL_NV_PAGE_END; pg++ ) { erasePage( pg ); } initNV(); } return TRUE; }
/********************************************************************* * @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 initNV * * @brief Initialize the NV flash pages. * * @param none * * @return TRUE */ static uint8 initNV( void ) { osalNvPgHdr_t pgHdr; uint8 oldPg = OSAL_NV_PAGE_NULL; uint8 findDups = FALSE; uint8 pg; pgRes = OSAL_NV_PAGE_NULL; for ( pg = OSAL_NV_PAGE_BEG; pg <= OSAL_NV_PAGE_END; pg++ ) { HalFlashRead(pg, OSAL_NV_PAGE_HDR_OFFSET, (uint8 *)(&pgHdr), OSAL_NV_HDR_SIZE); if ( pgHdr.active == OSAL_NV_ERASED_ID ) { if ( pgRes == OSAL_NV_PAGE_NULL ) { pgRes = pg; } else { setPageUse( pg, TRUE ); } } // An Xfer from this page was in progress. else if ( pgHdr.xfer != OSAL_NV_ERASED_ID ) { oldPg = pg; } } // If a page compaction was interrupted before the old page was erased. if ( oldPg != OSAL_NV_PAGE_NULL ) { /* Interrupted compaction before the target of compaction was put in use; * so erase the target of compaction and start again. */ if ( pgRes != OSAL_NV_PAGE_NULL ) { erasePage( pgRes ); (void)compactPage( oldPg, OSAL_NV_ITEM_NULL ); } /* Interrupted compaction after the target of compaction was put in use, * but before the old page was erased; so erase it now and create a new reserve page. */ else { erasePage( oldPg ); pgRes = oldPg; } } else if ( pgRes != OSAL_NV_PAGE_NULL ) { erasePage( pgRes ); // The last page erase could have been interrupted by a power-cycle. } /* else if there is no reserve page, COMPACT_PAGE_CLEANUP() must have succeeded to put the old * reserve page (i.e. the target of the compacted items) into use but got interrupted by a reset * while trying to erase the page to be compacted. Such a page should only contain duplicate items * (i.e. all items will be marked 'Xfer') and thus should have the lost count equal to the page * size less the page header. */ for ( pg = OSAL_NV_PAGE_BEG; pg <= OSAL_NV_PAGE_END; pg++ ) { // Calculate page offset and lost bytes - any "old" item triggers an N^2 re-scan from start. if ( initPage( pg, OSAL_NV_ITEM_NULL, findDups ) != OSAL_NV_ITEM_NULL ) { findDups = TRUE; pg = (OSAL_NV_PAGE_BEG - 1); // Pre-decrement so that loop increment will start over at zero. continue; } } if (findDups) { // Final pass to calculate page lost after invalidating duplicate items. for ( pg = OSAL_NV_PAGE_BEG; pg <= OSAL_NV_PAGE_END; pg++ ) { (void)initPage( pg, OSAL_NV_ITEM_NULL, FALSE ); } } if ( pgRes == OSAL_NV_PAGE_NULL ) { uint8 idx, mostLost = 0; for ( idx = 0; idx < OSAL_NV_PAGES_USED; idx++ ) { // Is this the page that was compacted? if (pgLost[idx] == (OSAL_NV_PAGE_SIZE - OSAL_NV_PAGE_HDR_SIZE)) { mostLost = idx; break; } /* This check is not expected to be necessary because the above test should always succeed * with an early loop exit. */ else if (pgLost[idx] > pgLost[mostLost]) { mostLost = idx; } } pgRes = mostLost + OSAL_NV_PAGE_BEG; erasePage( pgRes ); // The last page erase had been interrupted by a power-cycle. } return TRUE; }
/********************************************************************* * @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; }