/* Copyright 2017 The Chromium OS Authors. All rights reserved. * Use of this source code is governed by a BSD-style license that can be * found in the LICENSE file. */ /* robust non-volatile incrementing counter */ #include "common.h" #include "flash.h" #include "util.h" #define INCORRECT_FLASH_CNT 0xdeadd0d0 /* * We have 2 pages of flash (containing PAGE_WORDS 4-byte words) for the counter * they are between the code/read-only area and the NVMEM area of each * RW partition : the 'LOW' page in RW_A and the 'HIGH' page in RW_B * (at the same relative offset). */ #define PAGE_WORDS (CONFIG_FLASH_BANK_SIZE / sizeof(uint32_t)) static uint32_t *FLASH_CNT_LO = (uint32_t *)CONFIG_FLASH_NVCTR_BASE_A; static uint32_t *FLASH_CNT_HI = (uint32_t *)CONFIG_FLASH_NVCTR_BASE_B; #ifndef CHIP_HOST /* Ensure the 2 flash counter areas are aligned on flash pages except for the * host board which simulates flash with a variable that cannot be checked * at compile time. */ BUILD_ASSERT(CONFIG_FLASH_NVCTR_BASE_A % CONFIG_FLASH_ERASE_SIZE == 0); BUILD_ASSERT(CONFIG_FLASH_NVCTR_BASE_B % CONFIG_FLASH_ERASE_SIZE == 0); #endif /* * An anti-rollback, persistent flash counter. This counter requires two pages * of flash, one HIGH page and one LOW page. * * The LOW page is implemented in a strike style, with each "strike" zero-ing * out 4 bits at a time, meaning each word can be struck a total of 8 times. * * Once the LOW page is completely struck, the HIGH page is incremented by 2. * The even increment is for the value, the odd increment is a guard signal that * the LOW page must be erased. So as an example: * * If HIGH is 2, the LOW page would increment to 3, erase itself, and then * increment to 4. If this process is interrupted for some reason (power loss * or user intervention) and the HIGH left at 3, on next resume, the HI page * will recognize something was left pending and erase again. * */ static void _write(const uint32_t *p, size_t o, uint32_t v) { int offset = (uintptr_t) (p + o) - CONFIG_PROGRAM_MEMORY_BASE; /* TODO: return code */ flash_physical_write(offset, sizeof(uint32_t), (const char *)&v); } static void _erase(const void *p) { int offset = (uintptr_t) p - CONFIG_PROGRAM_MEMORY_BASE; /* TODO: return code */ flash_physical_erase(offset, CONFIG_FLASH_BANK_SIZE); } static uint32_t _decode(const uint32_t *p, size_t i) { uint32_t v = p[i]; /* Return value for clean states */ switch (v) { case 0xffffffff: return 0; case 0x3cffffff: return 1; case 0x00ffffff: return 2; case 0x003cffff: return 3; case 0x0000ffff: return 4; case 0x00003cff: return 5; case 0x000000ff: return 6; case 0x0000003c: return 7; case 0x00000000: return 8; } /* * Not a clean state; figure which transition got interrupted, * and affirm that transition target and return its target value. */ if ((v & 0x3cffffff) == 0x3cffffff) { _write(p, i, 0x3cffffff); /* affirm */ return 1; } if ((v & 0xc3ffffff) == 0x00ffffff) { _write(p, i, 0x00ffffff); /* affirm */ return 2; } if ((v & 0xff3cffff) == 0x003cffff) { _write(p, i, 0x003cffff); /* affirm */ return 3; } if ((v & 0xffc3ffff) == 0x0000ffff) { _write(p, i, 0x0000ffff); /* affirm */ return 4; } if ((v & 0xffff3cff) == 0x00003cff) { _write(p, i, 0x00003cff); /* affirm */ return 5; } if ((v & 0xffffc3ff) == 0x000000ff) { _write(p, i, 0x000000ff); /* affirm */ return 6; } if ((v & 0xffffff3c) == 0x0000003c) { _write(p, i, 0x0000003c); /* affirm */ return 7; } if ((v & 0xffffffc3) == 0x00000000) { _write(p, i, 0x0000000000); /* affirm */ return 8; } return INCORRECT_FLASH_CNT; /* unknown state */ } static uint32_t _encode(size_t v) { if (v > 7) return 0; switch (v & 7) { case 0: return 0xffffffff; case 1: return 0x3cffffff; case 2: return 0x00ffffff; case 3: return 0x003cffff; case 4: return 0x0000ffff; case 5: return 0x00003cff; case 6: return 0x000000ff; case 7: return 0x0000003c; } return 0; } static void _inc(const uint32_t *p, size_t i) { uint32_t v = _decode(p, i); if (v == 8) { /* * re-affirm (w/ single pulse?) * in case previous strike got interrupted and is flaky but we * read it as 0 this run. Making sure next run will see it as 0 * for sure. * Note this is extra hit past the 8 / word.. should be ok, even * 8 writes per word is far below the word line requirement, an * extra should be negligible. */ _write(p, i, 0); _write(p, i + 1, _encode(1)); } else { /* This also re-affirms other 0 bits in this word. */ _write(p, i, _encode(v + 1)); } } uint32_t nvcounter_incr(void) { uint32_t cnt = 0; uint32_t hi, lo; uint32_t result; /* * First determine the current count * Do so by first iterating through the HIGH/LOW pages */ for (hi = 0; hi < PAGE_WORDS; ++hi) { result = _decode(FLASH_CNT_HI, hi); /* * if the WORD does not decode correctly, write the entire WORD * to 0 and move on. */ if (result == INCORRECT_FLASH_CNT) { /* jump the entire word ahead */ _write(FLASH_CNT_HI, hi, 0); /* count adds 4 because each HIGH word counts 4 times */ return (cnt + 4) * (8 * PAGE_WORDS + 1); } /* * if the decoded result is ODD, that means an erase operation * was interrupt and we need to finish it off again. */ if (result & 1) { _erase(FLASH_CNT_LO); /* mark erase done */ _write(FLASH_CNT_HI, hi, _encode(result + 1)); return (cnt + (result + 1) / 2) * (8 * PAGE_WORDS + 1); } cnt += result / 2; /* * if result equals 8, that means the current HIGH word is * entirely 0, so we have not yet reached the end, continue * counting, otherwise, breakout of the for loop. */ if (result != 8) break; } /* each count is worth the entire strike of the LOW array */ cnt *= (8 * PAGE_WORDS + 1); for (lo = 0; lo < PAGE_WORDS; ++lo) { result = _decode(FLASH_CNT_LO, lo); if (result == INCORRECT_FLASH_CNT) { /* Try fix-up broken LO write; assume worst */ _write(FLASH_CNT_LO, lo, 0); /* jump ahead */ /* each LOW word counts 8 times (instead of 4 like HIGH) */ return cnt + 8; /* done */ } cnt += result; if (result != 8) break; } if (hi == PAGE_WORDS && lo == PAGE_WORDS) { /* We are exhausted, can count no more */ return -1; } /* After current count is determined, increment as required */ if (lo == PAGE_WORDS) { /* All LOW page is striken, time to advance HIGH page */ _write(FLASH_CNT_LO, PAGE_WORDS - 1, 0); /* mark erase busy, odd increment */ _inc(FLASH_CNT_HI, hi); _erase(FLASH_CNT_LO); /* mark erase done, even increment */ _inc(FLASH_CNT_HI, hi); } else { _inc(FLASH_CNT_LO, lo); } /* return the final count */ return cnt + 1; }