| /* |
| * Flash-based transactional key-value store |
| * |
| * Copyright (C) 2011 Google, Inc. |
| * Author: Eugene Surovegin, Terry Heo |
| */ |
| |
| #include "common.h" |
| #include "nflash_drv.h" |
| #include "config.h" |
| #include "nand_priv.h" |
| #include "com_type.h" |
| #include "debug.h" |
| #include "errno-base.h" |
| #include "string.h" |
| #include "flash_ts.h" |
| |
| #define printf lgpl_printf |
| #define min(a,b) (((a) < (b)) ? (a) : (b)) |
| #define NAND_INT_PAGE_BUFF_SIZE ((MV_NAND_MAX_PAGE_SIZE + MV_NAND_MAX_OOB_SIZE) / 4) |
| #define EMMC_INT_PAGE_BUFF_SIZE (8448/4 + 32) |
| #define ALIGNED(val, n) ((((unsigned int)val) + (n) - 1) & (~((unsigned int)(n) - 1))) |
| |
| extern struct mv_nand_data nand_data ; |
| extern unsigned long simple_strtoul(const char *cp,char **endp,unsigned int base); |
| |
| inline int nand_page_size() { |
| return (nand_data.szofpg); |
| |
| } |
| |
| inline int nand_block_size() { |
| return (nand_data.szofblk); |
| } |
| |
| inline int nand_read_page(int page, void *buf, int size) { |
| #ifdef NAND_BOOT |
| static int page_buff[NAND_INT_PAGE_BUFF_SIZE]; |
| #elif defined(EMMC_BOOT) |
| static int temp[EMMC_INT_PAGE_BUFF_SIZE]; |
| int *page_buff; |
| page_buff = (int *)ALIGNED(temp, 32); |
| #else |
| #error |
| #endif |
| loff_t offset = page * nand_page_size(); |
| |
| if (size > nand_page_size()) { |
| return -1; |
| } |
| if (NFlash_PageRead(offset, page_buff)) { |
| // page may be not erased. return default data. |
| memset(buf, 0xff, size); |
| return 0; |
| } |
| UtilMemCpy(buf, page_buff, size); |
| return 0; |
| } |
| |
| inline int nand_write_page(int page, const void *buf, int size) { |
| #ifdef NAND_BOOT |
| static int page_buff[NAND_INT_PAGE_BUFF_SIZE]; |
| #elif defined(EMMC_BOOT) |
| static int temp[EMMC_INT_PAGE_BUFF_SIZE]; |
| int *page_buff; |
| page_buff = (int *)ALIGNED(temp, 32); |
| #else |
| #error |
| #endif |
| loff_t offset = page * nand_page_size(); |
| |
| if (size > nand_page_size()) { |
| return -1; |
| } |
| memset(page_buff, 0xff, sizeof(page_buff)); |
| UtilMemCpy(page_buff, buf, size); |
| if (NFlash_PageWrite(offset, page_buff)) { |
| lgpl_printf("Write failed @ 0x%08x\n", (unsigned)offset); |
| return -1; |
| } |
| return 0; |
| } |
| |
| /* Compatibility defines to keep code as similar as possible |
| * to the Linux version. |
| */ |
| #define DRV_NAME "fts" |
| static inline int is_power_of_2(unsigned int n) |
| { |
| return (n != 0 && ((n & (n - 1)) == 0)); |
| } |
| |
| //typedef unsigned u32; |
| //typedef long loff_t; |
| #define likely(x) __builtin_expect(!!(x), 1) |
| #define unlikely(x) __builtin_expect(!!(x), 0) |
| |
| /* Keep in sync with 'struct flash_ts' */ |
| #define FLASH_TS_HDR_SIZE (4 * sizeof(u32)) |
| #define FLASH_TS_MAX_SIZE (16 * 1024) |
| #define FLASH_TS_MAX_DATA_SIZE (FLASH_TS_MAX_SIZE - FLASH_TS_HDR_SIZE) |
| |
| #define FLASH_TS_MAGIC 0x53542a46 |
| |
| /* Physical flash layout */ |
| struct flash_ts { |
| u32 magic; /* "F*TS" */ |
| u32 crc; /* doesn't include magic and crc fields */ |
| u32 len; /* real size of data */ |
| u32 version; /* generation counter, must be positive */ |
| |
| /* data format is very similar to Unix environment: |
| * key1=value1\0key2=value2\0\0 |
| */ |
| char data[FLASH_TS_MAX_DATA_SIZE]; |
| }; |
| |
| /* Internal state */ |
| static struct flash_ts_priv { |
| /* chunk size, >= sizeof(struct flash_ts) */ |
| size_t chunk; |
| |
| /* current record offset within NAND partition */ |
| loff_t offset; |
| |
| loff_t base; /* NAND partition base offset */ |
| size_t size; /* NAND partition size */ |
| size_t erasesize; /* block size */ |
| size_t writesize; /* page size */ |
| |
| /* in-memory copy of flash content */ |
| struct flash_ts cache; |
| |
| /* temporary buffers |
| * - one backup for failure rollback |
| * - another for read-after-write verification |
| */ |
| struct flash_ts cache_tmp_backup; |
| struct flash_ts cache_tmp_verify; |
| } __ts_priv; |
| static struct flash_ts_priv *__ts = NULL; |
| |
| static int flash_block_isbad(struct flash_ts_priv *ts, loff_t off) |
| { |
| return is_block_bad(ts->base + off); |
| } |
| |
| static int flash_erase(struct flash_ts_priv *ts, loff_t off) |
| { |
| lgpl_printf(DRV_NAME ": flash_erase @ 0x%08x\n", (unsigned)(ts->base + off)); |
| int res = NFlash_Erase(ts->base + off); |
| if (unlikely(res)) |
| printf(DRV_NAME ": flash_erase(0x%08x) failed, errno %d\n", |
| (unsigned)off, res); |
| return res; |
| } |
| |
| static int flash_write(struct flash_ts_priv *ts, loff_t off, |
| const void *buf, size_t size) |
| { |
| int page = (ts->base + off) / ts->writesize; |
| int res = 0; |
| |
| while (size) { |
| size_t chunk = min(size, ts->writesize); |
| res = nand_write_page(page, buf, chunk); |
| if (likely(!res)) { |
| ++page; |
| buf = (const void*)((unsigned long)buf + chunk); |
| size -= chunk; |
| } else { |
| printf(DRV_NAME ": write_page(%u) failed, errno %d\n", |
| page, res); |
| break; |
| } |
| } |
| return res; |
| } |
| |
| static int flash_read(struct flash_ts_priv *ts, loff_t off, void *buf, size_t size) |
| { |
| int page = (ts->base + off) / ts->writesize; |
| int res = 0; |
| |
| while (size) { |
| size_t chunk = min(size, ts->writesize); |
| res = nand_read_page(page, buf, chunk); |
| if (likely(!res)) { |
| ++page; |
| buf = (void*)((unsigned long)buf + chunk); |
| size -= chunk; |
| } else { |
| printf(DRV_NAME ": read_page(%u), errno %d\n", |
| page, res); |
| break; |
| } |
| } |
| return res; |
| } |
| |
| static char *flash_ts_find(struct flash_ts_priv *ts, const char *key, |
| size_t key_len) |
| { |
| char *s = ts->cache.data; |
| while (*s) { |
| if (!strncmp(s, key, key_len)) { |
| if (s[key_len] == '=') |
| return s; |
| } |
| |
| s += strlen(s) + 1; |
| } |
| return NULL; |
| } |
| |
| |
| static inline u32 flash_ts_crc(const struct flash_ts *cache) |
| { |
| const unsigned char *p; |
| u32 crc = 0; |
| size_t len; |
| |
| /* skip magic and crc fields */ |
| len = cache->len + 2 * sizeof(u32); |
| p = (const unsigned char*)&cache->len; |
| |
| while (len--) { |
| int i; |
| |
| crc ^= *p++; |
| for (i = 0; i < 8; i++) |
| crc = (crc >> 1) ^ ((crc & 1) ? 0xedb88320 : 0); |
| } |
| return crc ^ ~0; |
| } |
| |
| /* Verifies cache consistency and locks it */ |
| static struct flash_ts_priv *__flash_ts_get(void) |
| { |
| struct flash_ts_priv *ts = __ts; |
| |
| if (likely(ts)) { |
| if (unlikely(ts->cache.crc != flash_ts_crc(&ts->cache))) { |
| printf(DRV_NAME |
| ": memory corruption detected\n"); |
| ts = NULL; |
| } |
| } else { |
| printf(DRV_NAME ": not initialized yet\n"); |
| } |
| |
| return ts; |
| } |
| |
| static int flash_ts_commit(struct flash_ts_priv *ts) |
| { |
| loff_t off = ts->offset + ts->chunk; |
| /* we try to make two passes to handle non-erased blocks |
| * this should only matter for the inital pass over the whole device. |
| */ |
| int max_iterations = (ts->size / ts->erasesize) * 2; |
| size_t size = (FLASH_TS_HDR_SIZE + ts->cache.len + ts->chunk - 1) |
| & ~(ts->chunk - 1); |
| |
| /* fill unused part of data */ |
| memset(ts->cache.data + ts->cache.len, 0xff, |
| sizeof(ts->cache.data) - ts->cache.len); |
| |
| while (max_iterations--) { |
| /* wrap around */ |
| if (off >= ts->size) |
| off = 0; |
| |
| /* new block? */ |
| if (!(off & (ts->erasesize - 1))) { |
| if (flash_block_isbad(ts, off)) { |
| /* skip this block */ |
| off += ts->erasesize; |
| continue; |
| } |
| |
| if (unlikely(flash_erase(ts, off))) { |
| /* skip this block */ |
| off += ts->erasesize; |
| continue; |
| } |
| } |
| |
| /* write and read back to veryfy */ |
| if (flash_write(ts, off, &ts->cache, size) || |
| flash_read(ts, off, &ts->cache_tmp_verify, size)) { |
| /* hmm, probably unclean block, skip it for now */ |
| off = (off + ts->erasesize) & ~(ts->erasesize - 1); |
| continue; |
| } |
| |
| /* compare */ |
| if (memcmp(&ts->cache, &ts->cache_tmp_verify, size)) { |
| printf(DRV_NAME |
| ": record v%u read mismatch @ 0x%08x\n", |
| (unsigned)ts->cache.version, (unsigned)off); |
| /* skip this block for now */ |
| off = (off + ts->erasesize) & ~(ts->erasesize - 1); |
| continue; |
| } |
| |
| /* for new block, erase the previous block after write done, |
| * it's to speed up flash_ts_scan |
| */ |
| if (!(off & (ts->erasesize - 1))) { |
| loff_t pre_block_base = ts->offset & ~(ts->erasesize - 1); |
| loff_t cur_block_base = off & ~(ts->erasesize - 1); |
| if (cur_block_base != pre_block_base) |
| flash_erase(ts, pre_block_base); |
| } |
| ts->offset = off; |
| printf(DRV_NAME ": record v%u commited @ 0x%08x\n", |
| (unsigned)ts->cache.version, (unsigned)off); |
| return 0; |
| } |
| |
| printf(DRV_NAME ": commit failure\n"); |
| return -EIO; |
| } |
| |
| int flash_ts_set(const char *key, const char *value) |
| { |
| struct flash_ts_priv *ts; |
| size_t klen = strlen(key); |
| size_t vlen = strlen(value); |
| int res; |
| char *p; |
| |
| ts = __flash_ts_get(); |
| if (unlikely(!ts)) |
| return -EINVAL; |
| |
| /* save current cache contents so we can restore it on failure */ |
| memcpy(&ts->cache_tmp_backup, &ts->cache, sizeof(ts->cache_tmp_backup)); |
| |
| p = flash_ts_find(ts, key, klen); |
| if (p) { |
| /* we are replacing existing entry, |
| * empty value (vlen == 0) removes entry completely. |
| */ |
| size_t cur_len = strlen(p) + 1; |
| size_t new_len = vlen ? klen + 1 + vlen + 1 : 0; |
| |
| if (cur_len != new_len) { |
| /* we need to move stuff around */ |
| |
| if ((ts->cache.len - cur_len) + new_len > |
| sizeof(ts->cache.data)) |
| goto no_space; |
| |
| memmove(p + new_len, p + cur_len, |
| ts->cache.len - (p - ts->cache.data + cur_len)); |
| |
| ts->cache.len = (ts->cache.len - cur_len) + new_len; |
| } else if (!strcmp(p + klen + 1, value)) { |
| /* skip update if new value is the same as the old one */ |
| res = 0; |
| goto out; |
| } |
| |
| if (vlen) { |
| p += klen + 1; |
| memcpy(p, value, vlen); |
| p[vlen] = '\0'; |
| } |
| } else { |
| size_t len = klen + 1 + vlen + 1; |
| |
| /* don't do anything if value is empty */ |
| if (!vlen) { |
| res = 0; |
| goto out; |
| } |
| |
| if (ts->cache.len + len > sizeof(ts->cache.data)) |
| goto no_space; |
| |
| /* add new entry at the end */ |
| p = ts->cache.data + ts->cache.len - 1; |
| memcpy(p, key, klen); |
| p += klen; |
| *p++ = '='; |
| memcpy(p, value, vlen); |
| p += vlen; |
| *p++ = '\0'; |
| *p = '\0'; |
| ts->cache.len += len; |
| } |
| |
| ++ts->cache.version; |
| ts->cache.crc = flash_ts_crc(&ts->cache); |
| res = flash_ts_commit(ts); |
| if (unlikely(res)) |
| memcpy(&ts->cache, &ts->cache_tmp_backup, sizeof(ts->cache)); |
| goto out; |
| |
| no_space: |
| printf(DRV_NAME ": no space left for '%s=%s'\n", key, value); |
| res = -ENOSPC; |
| out: |
| |
| return res; |
| } |
| |
| void flash_ts_get(const char *key, char *value, unsigned int size) |
| { |
| size_t klen = strlen(key); |
| struct flash_ts_priv *ts; |
| const char *p; |
| |
| *value = '\0'; |
| |
| ts = __flash_ts_get(); |
| if (unlikely(!ts)) |
| return; |
| |
| p = flash_ts_find(ts, key, klen); |
| if (p) |
| strlcpy(value, p + klen + 1, size); |
| } |
| |
| int flash_ts_get_int(const char *key, int default_value) |
| { |
| char value[16]; |
| int res; |
| |
| flash_ts_get(key, value, sizeof(value)); |
| if (!value[0]) |
| return default_value; |
| |
| res = (int)simple_strtoul(value, (char**)NULL, 0); |
| return res; |
| } |
| |
| static inline u32 flash_ts_check_header(const struct flash_ts *cache) |
| { |
| if (cache->magic == FLASH_TS_MAGIC && |
| cache->version && |
| cache->len && cache->len <= sizeof(cache->data) && |
| cache->crc == flash_ts_crc(cache) && |
| /* check correct null-termination */ |
| !cache->data[cache->len - 1] && |
| (cache->len == 1 || !cache->data[cache->len - 2])) { |
| /* all is good */ |
| return cache->version; |
| } |
| |
| return 0; |
| } |
| |
| static int flash_ts_scan(struct flash_ts_priv *ts) |
| { |
| int res, good_blocks = 0; |
| loff_t off = 0; |
| |
| do { |
| /* new block ? */ |
| if (!(off & (ts->erasesize - 1))) { |
| if (flash_block_isbad(ts, off)) { |
| printf(DRV_NAME |
| ": skipping bad block @ 0x%08x\n", |
| (unsigned)off); |
| off += ts->erasesize; |
| continue; |
| } else |
| ++good_blocks; |
| } |
| |
| res = flash_read(ts, off, &ts->cache_tmp_verify, |
| sizeof(ts->cache_tmp_verify)); |
| if (!res) { |
| u32 version = |
| flash_ts_check_header(&ts->cache_tmp_verify); |
| if (version > ts->cache.version) { |
| memcpy(&ts->cache, &ts->cache_tmp_verify, |
| sizeof(ts->cache)); |
| ts->offset = off; |
| } |
| if (0 == version && |
| is_blank(&ts->cache_tmp_verify, |
| sizeof(ts->cache_tmp_verify))) { |
| /* skip the whole block if chunk is blank */ |
| off = (off + ts->erasesize) & ~(ts->erasesize - 1); |
| } else |
| off += ts->chunk; |
| } else { |
| off += ts->chunk; |
| } |
| } while (off < ts->size); |
| |
| if (unlikely(!good_blocks)) { |
| printf(DRV_NAME ": no good blocks\n"); |
| return -ENODEV; |
| } |
| |
| if (unlikely(good_blocks < 2)) |
| printf(DRV_NAME ": less than 2 good blocks," |
| " reliability is not guaranteed\n"); |
| return 0; |
| } |
| |
| /* Round-up to the next power-of-2, |
| * from "Hacker's Delight" by Henry S. Warren. |
| */ |
| static inline u32 clp2(u32 x) |
| { |
| --x; |
| x |= x >> 1; |
| x |= x >> 2; |
| x |= x >> 4; |
| x |= x >> 8; |
| x |= x >> 16; |
| return x + 1; |
| } |
| |
| int flash_ts_init(int start_block, int blocks) |
| { |
| struct flash_ts_priv *ts = &__ts_priv; |
| int writesize = nand_page_size(); |
| int erasesize = nand_block_size(); |
| int res; |
| |
| /* make sure both page and block sizes are power-of-2 |
| * (this will make chunk size determination simpler). |
| */ |
| if (unlikely(writesize < 0 || !is_power_of_2(writesize) || |
| erasesize < 0 || !is_power_of_2(erasesize))) { |
| printf(DRV_NAME ": unsupported flash geometry: erasesize=%d, writesize=%d\n", |
| erasesize, writesize); |
| return -ENODEV; |
| } |
| |
| /* determine chunk size so it doesn't cross block boundary, |
| * is multiple of page size and there is no wasted space in a block. |
| * We assume page and block sizes are power-of-2. |
| */ |
| ts->chunk = clp2((sizeof(struct flash_ts) + writesize - 1) & |
| ~(writesize - 1)); |
| if (unlikely(ts->chunk > (unsigned int)erasesize)) { |
| printf(DRV_NAME ": NAND block size is too small\n"); |
| return -ENODEV; |
| } |
| |
| if (writesize > MV_NAND_MAX_PAGE_SIZE) { |
| printf(DRV_NAME ": NAND page size is too large\n"); |
| return -ENODEV; |
| } |
| |
| /* write flash information */ |
| ts->size = blocks * erasesize; |
| ts->base = start_block * erasesize; |
| ts->erasesize = erasesize; |
| ts->writesize = writesize; |
| |
| /* default empty state */ |
| ts->offset = ts->size - ts->chunk; |
| ts->cache.magic = FLASH_TS_MAGIC; |
| ts->cache.version = 0; |
| ts->cache.len = 1; |
| ts->cache.data[0] = '\0'; |
| ts->cache.crc = flash_ts_crc(&ts->cache); |
| |
| /* scan flash partition for the most recent record */ |
| res = flash_ts_scan(ts); |
| if (unlikely(res)) |
| return res; |
| |
| if (ts->cache.version) |
| printf(DRV_NAME ": v%u loaded from 0x%08x\n", |
| (unsigned)ts->cache.version, (unsigned)ts->offset); |
| |
| /* "enable" it */ |
| __ts = ts; |
| |
| return res; |
| } |