增加文件系统
This commit is contained in:
50
Lib/dhara/bytes.h
Normal file
50
Lib/dhara/bytes.h
Normal file
@@ -0,0 +1,50 @@
|
||||
/* Dhara - NAND flash management layer
|
||||
* Copyright (C) 2013 Daniel Beer <dlbeer@gmail.com>
|
||||
*
|
||||
* Permission to use, copy, modify, and/or distribute this software for any
|
||||
* purpose with or without fee is hereby granted, provided that the above
|
||||
* copyright notice and this permission notice appear in all copies.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
*/
|
||||
|
||||
#ifndef DHARA_BYTES_H_
|
||||
#define DHARA_BYTES_H_
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
static inline uint16_t dhara_r16(const uint8_t *data)
|
||||
{
|
||||
return ((uint16_t)data[0]) |
|
||||
(((uint16_t)data[1]) << 8);
|
||||
}
|
||||
|
||||
static inline void dhara_w16(uint8_t *data, uint16_t v)
|
||||
{
|
||||
data[0] = v;
|
||||
data[1] = v >> 8;
|
||||
}
|
||||
|
||||
static inline uint32_t dhara_r32(const uint8_t *data)
|
||||
{
|
||||
return ((uint32_t)data[0]) |
|
||||
(((uint32_t)data[1]) << 8) |
|
||||
(((uint32_t)data[2]) << 16) |
|
||||
(((uint32_t)data[3]) << 24);
|
||||
}
|
||||
|
||||
static inline void dhara_w32(uint8_t *data, uint32_t v)
|
||||
{
|
||||
data[0] = v;
|
||||
data[1] = v >> 8;
|
||||
data[2] = v >> 16;
|
||||
data[3] = v >> 24;
|
||||
}
|
||||
|
||||
#endif
|
||||
45
Lib/dhara/error.h
Normal file
45
Lib/dhara/error.h
Normal file
@@ -0,0 +1,45 @@
|
||||
/* Dhara - NAND flash management layer
|
||||
* Copyright (C) 2013 Daniel Beer <dlbeer@gmail.com>
|
||||
*
|
||||
* Permission to use, copy, modify, and/or distribute this software for any
|
||||
* purpose with or without fee is hereby granted, provided that the above
|
||||
* copyright notice and this permission notice appear in all copies.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
*/
|
||||
|
||||
#ifndef DHARA_ERROR_H_
|
||||
#define DHARA_ERROR_H_
|
||||
|
||||
typedef enum {
|
||||
DHARA_E_NONE = 0,
|
||||
DHARA_E_BAD_BLOCK,
|
||||
DHARA_E_ECC,
|
||||
DHARA_E_TOO_BAD,
|
||||
DHARA_E_RECOVER,
|
||||
DHARA_E_JOURNAL_FULL,
|
||||
DHARA_E_NOT_FOUND,
|
||||
DHARA_E_MAP_FULL,
|
||||
DHARA_E_CORRUPT_MAP,
|
||||
DHARA_E_MAX
|
||||
} dhara_error_t;
|
||||
|
||||
/* Produce a human-readable error message. This function is kept in a
|
||||
* separate compilation unit and can be omitted to reduce binary size.
|
||||
*/
|
||||
const char *dhara_strerror(dhara_error_t err);
|
||||
|
||||
/* Save an error */
|
||||
static inline void dhara_set_error(dhara_error_t *err, dhara_error_t v)
|
||||
{
|
||||
if (err)
|
||||
*err = v;
|
||||
}
|
||||
|
||||
#endif
|
||||
884
Lib/dhara/journal.c
Normal file
884
Lib/dhara/journal.c
Normal file
@@ -0,0 +1,884 @@
|
||||
/* Dhara - NAND flash management layer
|
||||
* Copyright (C) 2013 Daniel Beer <dlbeer@gmail.com>
|
||||
*
|
||||
* Permission to use, copy, modify, and/or distribute this software for any
|
||||
* purpose with or without fee is hereby granted, provided that the above
|
||||
* copyright notice and this permission notice appear in all copies.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
*/
|
||||
|
||||
#include <string.h>
|
||||
#include "journal.h"
|
||||
#include "bytes.h"
|
||||
|
||||
/************************************************************************
|
||||
* Metapage binary format
|
||||
*/
|
||||
|
||||
/* Does the page buffer contain a valid checkpoint page? */
|
||||
static inline int hdr_has_magic(const uint8_t *buf)
|
||||
{
|
||||
return (buf[0] == 'D') &&
|
||||
(buf[1] == 'h') &&
|
||||
(buf[2] == 'a');
|
||||
}
|
||||
|
||||
static inline void hdr_put_magic(uint8_t *buf)
|
||||
{
|
||||
buf[0] = 'D';
|
||||
buf[1] = 'h';
|
||||
buf[2] = 'a';
|
||||
}
|
||||
|
||||
/* What epoch is this page? */
|
||||
static inline uint8_t hdr_get_epoch(const uint8_t *buf)
|
||||
{
|
||||
return buf[3];
|
||||
}
|
||||
|
||||
static inline void hdr_set_epoch(uint8_t *buf, uint8_t e)
|
||||
{
|
||||
buf[3] = e;
|
||||
}
|
||||
|
||||
static inline dhara_page_t hdr_get_tail(const uint8_t *buf)
|
||||
{
|
||||
return dhara_r32(buf + 4);
|
||||
}
|
||||
|
||||
static inline void hdr_set_tail(uint8_t *buf, dhara_page_t tail)
|
||||
{
|
||||
dhara_w32(buf + 4, tail);
|
||||
}
|
||||
|
||||
static inline dhara_page_t hdr_get_bb_current(const uint8_t *buf)
|
||||
{
|
||||
return dhara_r32(buf + 8);
|
||||
}
|
||||
|
||||
static inline void hdr_set_bb_current(uint8_t *buf, dhara_page_t count)
|
||||
{
|
||||
dhara_w32(buf + 8, count);
|
||||
}
|
||||
|
||||
static inline dhara_page_t hdr_get_bb_last(const uint8_t *buf)
|
||||
{
|
||||
return dhara_r32(buf + 12);
|
||||
}
|
||||
|
||||
static inline void hdr_set_bb_last(uint8_t *buf, dhara_page_t count)
|
||||
{
|
||||
dhara_w32(buf + 12, count);
|
||||
}
|
||||
|
||||
/* Clear user metadata */
|
||||
static inline void hdr_clear_user(uint8_t *buf, uint8_t log2_page_size)
|
||||
{
|
||||
memset(buf + DHARA_HEADER_SIZE + DHARA_COOKIE_SIZE, 0xff,
|
||||
(1 << log2_page_size) - DHARA_HEADER_SIZE - DHARA_COOKIE_SIZE);
|
||||
}
|
||||
|
||||
/* Obtain pointers to user data */
|
||||
static inline size_t hdr_user_offset(uint8_t which)
|
||||
{
|
||||
return DHARA_HEADER_SIZE + DHARA_COOKIE_SIZE +
|
||||
which * DHARA_META_SIZE;
|
||||
}
|
||||
|
||||
/************************************************************************
|
||||
* Page geometry helpers
|
||||
*/
|
||||
|
||||
/* Is this page index aligned to N bits? */
|
||||
static inline int is_aligned(dhara_page_t p, int n)
|
||||
{
|
||||
return !(p & ((1 << n) - 1));
|
||||
}
|
||||
|
||||
/* Are these two pages from the same alignment group? */
|
||||
static inline int align_eq(dhara_page_t a, dhara_page_t b,
|
||||
int n)
|
||||
{
|
||||
return !((a ^ b) >> n);
|
||||
}
|
||||
|
||||
/* What is the successor of this block? */
|
||||
static dhara_block_t next_block(const struct dhara_nand *n, dhara_block_t blk)
|
||||
{
|
||||
blk++;
|
||||
if (blk >= n->num_blocks)
|
||||
blk = 0;
|
||||
|
||||
return blk;
|
||||
}
|
||||
|
||||
static dhara_page_t next_upage(const struct dhara_journal *j,
|
||||
dhara_page_t p)
|
||||
{
|
||||
p++;
|
||||
if (is_aligned(p + 1, j->log2_ppc))
|
||||
p++;
|
||||
|
||||
if (p >= (j->nand->num_blocks << j->nand->log2_ppb))
|
||||
p = 0;
|
||||
|
||||
return p;
|
||||
}
|
||||
|
||||
/* Calculate a checkpoint period: the largest value of ppc such that
|
||||
* (2**ppc - 1) metadata blocks can fit on a page with one journal
|
||||
* header.
|
||||
*/
|
||||
static int choose_ppc(int log2_page_size, int max)
|
||||
{
|
||||
const int max_meta = (1 << log2_page_size) -
|
||||
DHARA_HEADER_SIZE - DHARA_COOKIE_SIZE;
|
||||
int total_meta = DHARA_META_SIZE;
|
||||
int ppc = 1;
|
||||
|
||||
while (ppc < max) {
|
||||
total_meta <<= 1;
|
||||
total_meta += DHARA_META_SIZE;
|
||||
|
||||
if (total_meta > max_meta)
|
||||
break;
|
||||
|
||||
ppc++;
|
||||
}
|
||||
|
||||
return ppc;
|
||||
}
|
||||
|
||||
/************************************************************************
|
||||
* Journal setup/resume
|
||||
*/
|
||||
|
||||
/* Clear recovery status */
|
||||
static void clear_recovery(struct dhara_journal *j)
|
||||
{
|
||||
j->recover_next = DHARA_PAGE_NONE;
|
||||
j->recover_root = DHARA_PAGE_NONE;
|
||||
j->recover_meta = DHARA_PAGE_NONE;
|
||||
j->flags &= ~(DHARA_JOURNAL_F_BAD_META |
|
||||
DHARA_JOURNAL_F_RECOVERY |
|
||||
DHARA_JOURNAL_F_ENUM_DONE);
|
||||
}
|
||||
|
||||
/* Set up an empty journal */
|
||||
static void reset_journal(struct dhara_journal *j)
|
||||
{
|
||||
/* We don't yet have a bad block estimate, so make a
|
||||
* conservative guess.
|
||||
*/
|
||||
j->epoch = 0;
|
||||
j->bb_last = j->nand->num_blocks >> 6;
|
||||
j->bb_current = 0;
|
||||
|
||||
j->flags = 0;
|
||||
|
||||
/* Empty journal */
|
||||
j->head = 0;
|
||||
j->tail = 0;
|
||||
j->tail_sync = 0;
|
||||
j->root = DHARA_PAGE_NONE;
|
||||
|
||||
/* No recovery required */
|
||||
clear_recovery(j);
|
||||
|
||||
/* Empty metadata buffer */
|
||||
memset(j->page_buf, 0xff, 1 << j->nand->log2_page_size);
|
||||
}
|
||||
|
||||
static void roll_stats(struct dhara_journal *j)
|
||||
{
|
||||
j->bb_last = j->bb_current;
|
||||
j->bb_current = 0;
|
||||
j->epoch++;
|
||||
}
|
||||
|
||||
void dhara_journal_init(struct dhara_journal *j,
|
||||
const struct dhara_nand *n,
|
||||
uint8_t *page_buf)
|
||||
{
|
||||
/* Set fixed parameters */
|
||||
j->nand = n;
|
||||
j->page_buf = page_buf;
|
||||
j->log2_ppc = choose_ppc(n->log2_page_size, n->log2_ppb);
|
||||
|
||||
reset_journal(j);
|
||||
}
|
||||
|
||||
/* Find the first checkpoint-containing block. If a block contains any
|
||||
* checkpoints at all, then it must contain one in the first checkpoint
|
||||
* location -- otherwise, we would have considered the block eraseable.
|
||||
*/
|
||||
static int find_checkblock(struct dhara_journal *j,
|
||||
dhara_block_t blk, dhara_block_t *where,
|
||||
dhara_error_t *err)
|
||||
{
|
||||
int i;
|
||||
|
||||
for (i = 0; (blk < j->nand->num_blocks) &&
|
||||
(i < DHARA_MAX_RETRIES); i++) {
|
||||
const dhara_page_t p =
|
||||
(blk << j->nand->log2_ppb) |
|
||||
((1 << j->log2_ppc) - 1);
|
||||
|
||||
if (!(dhara_nand_is_bad(j->nand, blk) ||
|
||||
dhara_nand_read(j->nand, p,
|
||||
0, 1 << j->nand->log2_page_size,
|
||||
j->page_buf, err)) &&
|
||||
hdr_has_magic(j->page_buf)) {
|
||||
*where = blk;
|
||||
return 0;
|
||||
}
|
||||
|
||||
blk++;
|
||||
}
|
||||
|
||||
dhara_set_error(err, DHARA_E_TOO_BAD);
|
||||
return -1;
|
||||
}
|
||||
|
||||
static dhara_block_t find_last_checkblock(struct dhara_journal *j,
|
||||
dhara_block_t first)
|
||||
{
|
||||
dhara_block_t low = first;
|
||||
dhara_block_t high = j->nand->num_blocks - 1;
|
||||
|
||||
while (low <= high) {
|
||||
const dhara_block_t mid = (low + high) >> 1;
|
||||
dhara_block_t found;
|
||||
|
||||
if ((find_checkblock(j, mid, &found, NULL) < 0) ||
|
||||
(hdr_get_epoch(j->page_buf) != j->epoch)) {
|
||||
if (!mid)
|
||||
return first;
|
||||
|
||||
high = mid - 1;
|
||||
} else {
|
||||
dhara_block_t nf;
|
||||
|
||||
if (((found + 1) >= j->nand->num_blocks) ||
|
||||
(find_checkblock(j, found + 1,
|
||||
&nf, NULL) < 0) ||
|
||||
(hdr_get_epoch(j->page_buf) != j->epoch))
|
||||
return found;
|
||||
|
||||
low = nf;
|
||||
}
|
||||
}
|
||||
|
||||
return first;
|
||||
}
|
||||
|
||||
/* Test whether a checkpoint group is in a state fit for reprogramming,
|
||||
* but allow for the fact that is_free() might not have any way of
|
||||
* distinguishing between an unprogrammed page, and a page programmed
|
||||
* with all-0xff bytes (but if so, it must be ok to reprogram such a
|
||||
* page).
|
||||
*
|
||||
* We used to test for an unprogrammed checkpoint group by checking to
|
||||
* see if the first user-page had been programmed since last erase (by
|
||||
* testing only the first page with is_free). This works if is_free is
|
||||
* precise, because the pages are written in order.
|
||||
*
|
||||
* If is_free is imprecise, we need to check all pages in the group.
|
||||
* That also works, because the final page in a checkpoint group is
|
||||
* guaranteed to contain non-0xff bytes. Therefore, we return 1 only if
|
||||
* the group is truly unprogrammed, or if it was partially programmed
|
||||
* with some all-0xff user pages (which changes nothing for us).
|
||||
*/
|
||||
static int cp_free(struct dhara_journal *j, dhara_page_t first_user)
|
||||
{
|
||||
const int count = 1 << j->log2_ppc;
|
||||
int i;
|
||||
|
||||
for (i = 0; i < count; i++)
|
||||
if (!dhara_nand_is_free(j->nand, first_user + i))
|
||||
return 0;
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
static dhara_page_t find_last_group(struct dhara_journal *j,
|
||||
dhara_block_t blk)
|
||||
{
|
||||
const int num_groups = 1 << (j->nand->log2_ppb - j->log2_ppc);
|
||||
int low = 0;
|
||||
int high = num_groups - 1;
|
||||
|
||||
/* If a checkpoint group is completely unprogrammed, everything
|
||||
* following it will be completely unprogrammed also.
|
||||
*
|
||||
* Therefore, binary search checkpoint groups until we find the
|
||||
* last programmed one.
|
||||
*/
|
||||
while (low <= high) {
|
||||
int mid = (low + high) >> 1;
|
||||
const dhara_page_t p = (mid << j->log2_ppc) |
|
||||
(blk << j->nand->log2_ppb);
|
||||
|
||||
if (cp_free(j, p)) {
|
||||
high = mid - 1;
|
||||
} else if (((mid + 1) >= num_groups) ||
|
||||
cp_free(j, p + (1 << j->log2_ppc))) {
|
||||
return p;
|
||||
} else {
|
||||
low = mid + 1;
|
||||
}
|
||||
}
|
||||
|
||||
return blk << j->nand->log2_ppb;
|
||||
}
|
||||
|
||||
static int find_root(struct dhara_journal *j, dhara_page_t start,
|
||||
dhara_error_t *err)
|
||||
{
|
||||
const dhara_block_t blk = start >> j->nand->log2_ppb;
|
||||
int i = (start & ((1 << j->nand->log2_ppb) - 1)) >> j->log2_ppc;
|
||||
|
||||
while (i >= 0) {
|
||||
const dhara_page_t p = (blk << j->nand->log2_ppb) +
|
||||
((i + 1) << j->log2_ppc) - 1;
|
||||
|
||||
if (!dhara_nand_read(j->nand, p,
|
||||
0, 1 << j->nand->log2_page_size,
|
||||
j->page_buf, err) &&
|
||||
(hdr_has_magic(j->page_buf)) &&
|
||||
(hdr_get_epoch(j->page_buf) == j->epoch)) {
|
||||
j->root = p - 1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
i--;
|
||||
}
|
||||
|
||||
dhara_set_error(err, DHARA_E_TOO_BAD);
|
||||
return -1;
|
||||
}
|
||||
|
||||
static int find_head(struct dhara_journal *j, dhara_page_t start,
|
||||
dhara_error_t *err)
|
||||
{
|
||||
j->head = next_upage(j, start);
|
||||
if (!j->head)
|
||||
roll_stats(j);
|
||||
|
||||
/* Starting from the last good checkpoint, find either:
|
||||
*
|
||||
* (a) the next free user-page in the same block
|
||||
* (b) or, the first page of the next block
|
||||
*
|
||||
* The block we end up on might be bad, but that's ok -- we'll
|
||||
* skip it when we go to prepare the next write.
|
||||
*/
|
||||
for (;;) {
|
||||
/* How many free pages trail this checkpoint group? */
|
||||
const unsigned int ppc = 1 << j->log2_ppc;
|
||||
unsigned int n = 0;
|
||||
dhara_page_t first = j->head & ~(dhara_page_t)(ppc - 1);
|
||||
|
||||
while (n < ppc &&
|
||||
dhara_nand_is_free(j->nand, first + ppc - n - 1))
|
||||
n++;
|
||||
|
||||
/* If we have some, then we've found our next free
|
||||
* userpage.
|
||||
*/
|
||||
if (n > 1) {
|
||||
j->head = first + ppc - n;
|
||||
break;
|
||||
}
|
||||
|
||||
/* Skip to the next checkpoint group */
|
||||
j->head = first + ppc;
|
||||
if (j->head >= (j->nand->num_blocks << j->nand->log2_ppb)) {
|
||||
j->head = 0;
|
||||
roll_stats(j);
|
||||
}
|
||||
|
||||
/* If we hit the end of the block, we're done */
|
||||
if (is_aligned(j->head, j->nand->log2_ppb)) {
|
||||
/* Make sure we don't chase over the tail */
|
||||
if (align_eq(j->head, j->tail, j->nand->log2_ppb))
|
||||
j->tail = next_block(j->nand,
|
||||
j->tail >> j->nand->log2_ppb) <<
|
||||
j->nand->log2_ppb;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int dhara_journal_resume(struct dhara_journal *j, dhara_error_t *err)
|
||||
{
|
||||
dhara_block_t first, last;
|
||||
dhara_page_t last_group;
|
||||
|
||||
/* Find the first checkpoint-containing block */
|
||||
if (find_checkblock(j, 0, &first, err) < 0) {
|
||||
reset_journal(j);
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* Find the last checkpoint-containing block in this epoch */
|
||||
j->epoch = hdr_get_epoch(j->page_buf);
|
||||
last = find_last_checkblock(j, first);
|
||||
|
||||
/* Find the last programmed checkpoint group in the block */
|
||||
last_group = find_last_group(j, last);
|
||||
|
||||
/* Perform a linear scan to find the last good checkpoint (and
|
||||
* therefore the root).
|
||||
*/
|
||||
if (find_root(j, last_group, err) < 0) {
|
||||
reset_journal(j);
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* Restore settings from checkpoint */
|
||||
j->tail = hdr_get_tail(j->page_buf);
|
||||
j->bb_current = hdr_get_bb_current(j->page_buf);
|
||||
j->bb_last = hdr_get_bb_last(j->page_buf);
|
||||
hdr_clear_user(j->page_buf, j->nand->log2_page_size);
|
||||
|
||||
/* Perform another linear scan to find the next free user page */
|
||||
if (find_head(j, last_group, err) < 0) {
|
||||
reset_journal(j);
|
||||
return -1;
|
||||
}
|
||||
|
||||
j->flags = 0;
|
||||
j->tail_sync = j->tail;
|
||||
|
||||
clear_recovery(j);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**************************************************************************
|
||||
* Public interface
|
||||
*/
|
||||
|
||||
dhara_page_t dhara_journal_capacity(const struct dhara_journal *j)
|
||||
{
|
||||
const dhara_block_t max_bad = j->bb_last > j->bb_current ?
|
||||
j->bb_last : j->bb_current;
|
||||
const dhara_block_t good_blocks = j->nand->num_blocks - max_bad - 1;
|
||||
const int log2_cpb = j->nand->log2_ppb - j->log2_ppc;
|
||||
const dhara_page_t good_cps = good_blocks << log2_cpb;
|
||||
|
||||
/* Good checkpoints * (checkpoint period - 1) */
|
||||
return (good_cps << j->log2_ppc) - good_cps;
|
||||
}
|
||||
|
||||
dhara_page_t dhara_journal_size(const struct dhara_journal *j)
|
||||
{
|
||||
/* Find the number of raw pages, and the number of checkpoints
|
||||
* between the head and the tail. The difference between the two
|
||||
* is the number of user pages (upper limit).
|
||||
*/
|
||||
dhara_page_t num_pages = j->head;
|
||||
dhara_page_t num_cps = j->head >> j->log2_ppc;
|
||||
|
||||
if (j->head < j->tail_sync) {
|
||||
const dhara_page_t total_pages =
|
||||
j->nand->num_blocks << j->nand->log2_ppb;
|
||||
|
||||
num_pages += total_pages;
|
||||
num_cps += total_pages >> j->log2_ppc;
|
||||
}
|
||||
|
||||
num_pages -= j->tail_sync;
|
||||
num_cps -= j->tail_sync >> j->log2_ppc;
|
||||
|
||||
return num_pages - num_cps;
|
||||
}
|
||||
|
||||
int dhara_journal_read_meta(struct dhara_journal *j, dhara_page_t p,
|
||||
uint8_t *buf, dhara_error_t *err)
|
||||
{
|
||||
/* Offset of metadata within the metadata page */
|
||||
const dhara_page_t ppc_mask = (1 << j->log2_ppc) - 1;
|
||||
const size_t offset = hdr_user_offset(p & ppc_mask);
|
||||
|
||||
/* Special case: buffered metadata */
|
||||
if (align_eq(p, j->head, j->log2_ppc)) {
|
||||
memcpy(buf, j->page_buf + offset, DHARA_META_SIZE);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Special case: incomplete metadata dumped at start of
|
||||
* recovery.
|
||||
*/
|
||||
if ((j->recover_meta != DHARA_PAGE_NONE) &&
|
||||
align_eq(p, j->recover_root, j->log2_ppc))
|
||||
return dhara_nand_read(j->nand, j->recover_meta,
|
||||
offset, DHARA_META_SIZE,
|
||||
buf, err);
|
||||
|
||||
/* General case: fetch from metadata page for checkpoint group */
|
||||
return dhara_nand_read(j->nand, p | ppc_mask,
|
||||
offset, DHARA_META_SIZE,
|
||||
buf, err);
|
||||
}
|
||||
|
||||
dhara_page_t dhara_journal_peek(struct dhara_journal *j)
|
||||
{
|
||||
if (j->head == j->tail)
|
||||
return DHARA_PAGE_NONE;
|
||||
|
||||
if (is_aligned(j->tail, j->nand->log2_ppb)) {
|
||||
dhara_block_t blk = j->tail >> j->nand->log2_ppb;
|
||||
int i;
|
||||
|
||||
for (i = 0; i < DHARA_MAX_RETRIES; i++) {
|
||||
if ((blk == (j->head >> j->nand->log2_ppb)) ||
|
||||
!dhara_nand_is_bad(j->nand, blk)) {
|
||||
j->tail = blk << j->nand->log2_ppb;
|
||||
|
||||
if (j->tail == j->head)
|
||||
j->root = DHARA_PAGE_NONE;
|
||||
|
||||
return j->tail;
|
||||
}
|
||||
|
||||
blk = next_block(j->nand, blk);
|
||||
}
|
||||
}
|
||||
|
||||
return j->tail;
|
||||
}
|
||||
|
||||
static dhara_page_t wrap(dhara_page_t a, dhara_page_t b)
|
||||
{
|
||||
return a >= b ? (a - b) : a;
|
||||
}
|
||||
|
||||
void dhara_journal_dequeue(struct dhara_journal *j)
|
||||
{
|
||||
if (j->head == j->tail)
|
||||
return;
|
||||
|
||||
j->tail = next_upage(j, j->tail);
|
||||
|
||||
/* If the journal is clean at the time of dequeue, then this
|
||||
* data was always obsolete, and can be reused immediately.
|
||||
*/
|
||||
if (!(j->flags & (DHARA_JOURNAL_F_DIRTY | DHARA_JOURNAL_F_RECOVERY)))
|
||||
j->tail_sync = j->tail;
|
||||
|
||||
const dhara_page_t chip_size = j->nand->num_blocks << j->nand->log2_ppb;
|
||||
const dhara_page_t raw_size = wrap(j->head + chip_size - j->tail,
|
||||
chip_size);
|
||||
const dhara_page_t root_offset = wrap(j->head + chip_size - j->root,
|
||||
chip_size);
|
||||
|
||||
if (root_offset > raw_size)
|
||||
j->root = DHARA_PAGE_NONE;
|
||||
}
|
||||
|
||||
void dhara_journal_clear(struct dhara_journal *j)
|
||||
{
|
||||
j->tail = j->head;
|
||||
j->root = DHARA_PAGE_NONE;
|
||||
j->flags |= DHARA_JOURNAL_F_DIRTY;
|
||||
|
||||
hdr_clear_user(j->page_buf, j->nand->log2_page_size);
|
||||
}
|
||||
|
||||
static int skip_block(struct dhara_journal *j, dhara_error_t *err)
|
||||
{
|
||||
const dhara_block_t next = next_block(j->nand,
|
||||
j->head >> j->nand->log2_ppb);
|
||||
|
||||
/* We can't roll onto the same block as the tail */
|
||||
if ((j->tail_sync >> j->nand->log2_ppb) == next) {
|
||||
dhara_set_error(err, DHARA_E_JOURNAL_FULL);
|
||||
return -1;
|
||||
}
|
||||
|
||||
j->head = next << j->nand->log2_ppb;
|
||||
if (!j->head)
|
||||
roll_stats(j);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Make sure the head pointer is on a ready-to-program page. */
|
||||
static int prepare_head(struct dhara_journal *j, dhara_error_t *err)
|
||||
{
|
||||
const dhara_page_t next = next_upage(j, j->head);
|
||||
int i;
|
||||
|
||||
/* We can't write if doing so would cause the head pointer to
|
||||
* roll onto the same block as the last-synced tail.
|
||||
*/
|
||||
if (align_eq(next, j->tail_sync, j->nand->log2_ppb) &&
|
||||
!align_eq(next, j->head, j->nand->log2_ppb)) {
|
||||
dhara_set_error(err, DHARA_E_JOURNAL_FULL);
|
||||
return -1;
|
||||
}
|
||||
|
||||
j->flags |= DHARA_JOURNAL_F_DIRTY;
|
||||
if (!is_aligned(j->head, j->nand->log2_ppb))
|
||||
return 0;
|
||||
|
||||
for (i = 0; i < DHARA_MAX_RETRIES; i++) {
|
||||
const dhara_block_t blk = j->head >> j->nand->log2_ppb;
|
||||
|
||||
if (!dhara_nand_is_bad(j->nand, blk))
|
||||
return dhara_nand_erase(j->nand, blk, err);
|
||||
|
||||
j->bb_current++;
|
||||
if (skip_block(j, err) < 0)
|
||||
return -1;
|
||||
}
|
||||
|
||||
dhara_set_error(err, DHARA_E_TOO_BAD);
|
||||
return -1;
|
||||
}
|
||||
|
||||
static void restart_recovery(struct dhara_journal *j, dhara_page_t old_head)
|
||||
{
|
||||
/* Mark the current head bad immediately, unless we're also
|
||||
* using it to hold our dumped metadata (it will then be marked
|
||||
* bad at the end of recovery).
|
||||
*/
|
||||
if ((j->recover_meta == DHARA_PAGE_NONE) ||
|
||||
!align_eq(j->recover_meta, old_head, j->nand->log2_ppb))
|
||||
dhara_nand_mark_bad(j->nand, old_head >> j->nand->log2_ppb);
|
||||
else
|
||||
j->flags |= DHARA_JOURNAL_F_BAD_META;
|
||||
|
||||
/* Start recovery again. Reset the source enumeration to
|
||||
* the start of the original bad block, and reset the
|
||||
* destination enumeration to the newly found good
|
||||
* block.
|
||||
*/
|
||||
j->flags &= ~DHARA_JOURNAL_F_ENUM_DONE;
|
||||
j->recover_next =
|
||||
j->recover_root & ~((1 << j->nand->log2_ppb) - 1);
|
||||
|
||||
j->root = j->recover_root;
|
||||
}
|
||||
|
||||
static int dump_meta(struct dhara_journal *j, dhara_error_t *err)
|
||||
{
|
||||
int i;
|
||||
|
||||
/* We've just begun recovery on a new erasable block, but we
|
||||
* have buffered metadata from the failed block.
|
||||
*/
|
||||
for (i = 0; i < DHARA_MAX_RETRIES; i++) {
|
||||
dhara_error_t my_err;
|
||||
|
||||
/* Try to dump metadata on this page */
|
||||
if (!(prepare_head(j, &my_err) ||
|
||||
dhara_nand_prog(j->nand, j->head,
|
||||
j->page_buf, &my_err))) {
|
||||
j->recover_meta = j->head;
|
||||
j->head = next_upage(j, j->head);
|
||||
if (!j->head)
|
||||
roll_stats(j);
|
||||
hdr_clear_user(j->page_buf, j->nand->log2_page_size);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Report fatal errors */
|
||||
if (my_err != DHARA_E_BAD_BLOCK) {
|
||||
dhara_set_error(err, my_err);
|
||||
return -1;
|
||||
}
|
||||
|
||||
j->bb_current++;
|
||||
dhara_nand_mark_bad(j->nand, j->head >> j->nand->log2_ppb);
|
||||
|
||||
if (skip_block(j, err) < 0)
|
||||
return -1;
|
||||
}
|
||||
|
||||
dhara_set_error(err, DHARA_E_TOO_BAD);
|
||||
return -1;
|
||||
}
|
||||
|
||||
static int recover_from(struct dhara_journal *j,
|
||||
dhara_error_t write_err,
|
||||
dhara_error_t *err)
|
||||
{
|
||||
const dhara_page_t old_head = j->head;
|
||||
|
||||
if (write_err != DHARA_E_BAD_BLOCK) {
|
||||
dhara_set_error(err, write_err);
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* Advance to the next free page */
|
||||
j->bb_current++;
|
||||
if (skip_block(j, err) < 0)
|
||||
return -1;
|
||||
|
||||
/* Are we already in the middle of a recovery? */
|
||||
if (dhara_journal_in_recovery(j)) {
|
||||
restart_recovery(j, old_head);
|
||||
dhara_set_error(err, DHARA_E_RECOVER);
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* Were we block aligned? No recovery required! */
|
||||
if (is_aligned(old_head, j->nand->log2_ppb)) {
|
||||
dhara_nand_mark_bad(j->nand, old_head >> j->nand->log2_ppb);
|
||||
return 0;
|
||||
}
|
||||
|
||||
j->recover_root = j->root;
|
||||
j->recover_next =
|
||||
j->recover_root & ~((1 << j->nand->log2_ppb) - 1);
|
||||
|
||||
/* Are we holding buffered metadata? Dump it first. */
|
||||
if (!is_aligned(old_head, j->log2_ppc) &&
|
||||
dump_meta(j, err) < 0)
|
||||
return -1;
|
||||
|
||||
j->flags |= DHARA_JOURNAL_F_RECOVERY;
|
||||
dhara_set_error(err, DHARA_E_RECOVER);
|
||||
return -1;
|
||||
}
|
||||
|
||||
static void finish_recovery(struct dhara_journal *j)
|
||||
{
|
||||
/* We just recovered the last page. Mark the recovered
|
||||
* block as bad.
|
||||
*/
|
||||
dhara_nand_mark_bad(j->nand,
|
||||
j->recover_root >> j->nand->log2_ppb);
|
||||
|
||||
/* If we had to dump metadata, and the page on which we
|
||||
* did this also went bad, mark it bad too.
|
||||
*/
|
||||
if (j->flags & DHARA_JOURNAL_F_BAD_META)
|
||||
dhara_nand_mark_bad(j->nand,
|
||||
j->recover_meta >> j->nand->log2_ppb);
|
||||
|
||||
/* Was the tail on this page? Skip it forward */
|
||||
clear_recovery(j);
|
||||
}
|
||||
|
||||
static int push_meta(struct dhara_journal *j, const uint8_t *meta,
|
||||
dhara_error_t *err)
|
||||
{
|
||||
const dhara_page_t old_head = j->head;
|
||||
dhara_error_t my_err;
|
||||
const size_t offset =
|
||||
hdr_user_offset(j->head & ((1 << j->log2_ppc) - 1));
|
||||
|
||||
/* We've just written a user page. Add the metadata to the
|
||||
* buffer.
|
||||
*/
|
||||
if (meta)
|
||||
memcpy(j->page_buf + offset, meta, DHARA_META_SIZE);
|
||||
else
|
||||
memset(j->page_buf + offset, 0xff, DHARA_META_SIZE);
|
||||
|
||||
/* Unless we've filled the buffer, don't do any IO */
|
||||
if (!is_aligned(j->head + 2, j->log2_ppc)) {
|
||||
j->root = j->head;
|
||||
j->head++;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* We don't need to check for immediate recover, because that'll
|
||||
* never happen -- we're not block-aligned.
|
||||
*/
|
||||
hdr_put_magic(j->page_buf);
|
||||
hdr_set_epoch(j->page_buf, j->epoch);
|
||||
hdr_set_tail(j->page_buf, j->tail);
|
||||
hdr_set_bb_current(j->page_buf, j->bb_current);
|
||||
hdr_set_bb_last(j->page_buf, j->bb_last);
|
||||
|
||||
if (dhara_nand_prog(j->nand, j->head + 1, j->page_buf, &my_err) < 0)
|
||||
return recover_from(j, my_err, err);
|
||||
|
||||
j->flags &= ~DHARA_JOURNAL_F_DIRTY;
|
||||
|
||||
j->root = old_head;
|
||||
j->head = next_upage(j, j->head);
|
||||
|
||||
if (!j->head)
|
||||
roll_stats(j);
|
||||
|
||||
if (j->flags & DHARA_JOURNAL_F_ENUM_DONE)
|
||||
finish_recovery(j);
|
||||
|
||||
if (!(j->flags & DHARA_JOURNAL_F_RECOVERY))
|
||||
j->tail_sync = j->tail;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int dhara_journal_enqueue(struct dhara_journal *j,
|
||||
const uint8_t *data, const uint8_t *meta,
|
||||
dhara_error_t *err)
|
||||
{
|
||||
dhara_error_t my_err;
|
||||
int i;
|
||||
|
||||
for (i = 0; i < DHARA_MAX_RETRIES; i++) {
|
||||
if (!(prepare_head(j, &my_err) ||
|
||||
(data && dhara_nand_prog(j->nand, j->head, data,
|
||||
&my_err))))
|
||||
return push_meta(j, meta, err);
|
||||
|
||||
if (recover_from(j, my_err, err) < 0)
|
||||
return -1;
|
||||
}
|
||||
|
||||
dhara_set_error(err, DHARA_E_TOO_BAD);
|
||||
return -1;
|
||||
}
|
||||
|
||||
int dhara_journal_copy(struct dhara_journal *j,
|
||||
dhara_page_t p, const uint8_t *meta,
|
||||
dhara_error_t *err)
|
||||
{
|
||||
dhara_error_t my_err;
|
||||
int i;
|
||||
|
||||
for (i = 0; i < DHARA_MAX_RETRIES; i++) {
|
||||
if (!(prepare_head(j, &my_err) ||
|
||||
dhara_nand_copy(j->nand, p, j->head, &my_err)))
|
||||
return push_meta(j, meta, err);
|
||||
|
||||
if (recover_from(j, my_err, err) < 0)
|
||||
return -1;
|
||||
}
|
||||
|
||||
dhara_set_error(err, DHARA_E_TOO_BAD);
|
||||
return -1;
|
||||
}
|
||||
|
||||
dhara_page_t dhara_journal_next_recoverable(struct dhara_journal *j)
|
||||
{
|
||||
const dhara_page_t n = j->recover_next;
|
||||
|
||||
if (!dhara_journal_in_recovery(j))
|
||||
return DHARA_PAGE_NONE;
|
||||
|
||||
if (j->flags & DHARA_JOURNAL_F_ENUM_DONE)
|
||||
return DHARA_PAGE_NONE;
|
||||
|
||||
if (j->recover_next == j->recover_root)
|
||||
j->flags |= DHARA_JOURNAL_F_ENUM_DONE;
|
||||
else
|
||||
j->recover_next = next_upage(j, j->recover_next);
|
||||
|
||||
return n;
|
||||
}
|
||||
256
Lib/dhara/journal.h
Normal file
256
Lib/dhara/journal.h
Normal file
@@ -0,0 +1,256 @@
|
||||
/* Dhara - NAND flash management layer
|
||||
* Copyright (C) 2013 Daniel Beer <dlbeer@gmail.com>
|
||||
*
|
||||
* Permission to use, copy, modify, and/or distribute this software for any
|
||||
* purpose with or without fee is hereby granted, provided that the above
|
||||
* copyright notice and this permission notice appear in all copies.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
*/
|
||||
|
||||
#ifndef DHARA_JOURNAL_H_
|
||||
#define DHARA_JOURNAL_H_
|
||||
|
||||
#include <stdint.h>
|
||||
#include "nand.h"
|
||||
|
||||
/* Number of bytes used by the journal checkpoint header. */
|
||||
#define DHARA_HEADER_SIZE 16
|
||||
|
||||
/* Global metadata available for a higher layer. This metadata is
|
||||
* persistent once the journal reaches a checkpoint, and is restored on
|
||||
* startup.
|
||||
*/
|
||||
#define DHARA_COOKIE_SIZE 4
|
||||
|
||||
/* This is the size of the metadata slice which accompanies each written
|
||||
* page. This is independent of the underlying page/OOB size.
|
||||
*/
|
||||
#define DHARA_META_SIZE 132
|
||||
|
||||
/* When a block fails, or garbage is encountered, we try again on the
|
||||
* next block/checkpoint. We can do this up to the given number of
|
||||
* times.
|
||||
*/
|
||||
#define DHARA_MAX_RETRIES 8
|
||||
|
||||
/* This is a page number which can be used to represent "no such page".
|
||||
* It's guaranteed to never be a valid user page.
|
||||
*/
|
||||
#define DHARA_PAGE_NONE ((dhara_page_t)0xffffffff)
|
||||
|
||||
/* State flags */
|
||||
#define DHARA_JOURNAL_F_DIRTY 0x01
|
||||
#define DHARA_JOURNAL_F_BAD_META 0x02
|
||||
#define DHARA_JOURNAL_F_RECOVERY 0x04
|
||||
#define DHARA_JOURNAL_F_ENUM_DONE 0x08
|
||||
|
||||
/* The journal layer presents the NAND pages as a double-ended queue.
|
||||
* Pages, with associated metadata may be pushed onto the end of the
|
||||
* queue, and pages may be popped from the end.
|
||||
*
|
||||
* Block erase, metadata storage are handled automatically. Bad blocks
|
||||
* are handled by relocating data to the next available non-bad page in
|
||||
* the sequence.
|
||||
*
|
||||
* It's up to the user to ensure that the queue doesn't grow beyond the
|
||||
* capacity of the NAND chip, but helper functions are provided to
|
||||
* assist with this. If the head meets the tail, the journal will refuse
|
||||
* to enqueue more pages.
|
||||
*/
|
||||
struct dhara_journal {
|
||||
const struct dhara_nand *nand;
|
||||
uint8_t *page_buf;
|
||||
|
||||
/* In the journal, user data is grouped into checkpoints of
|
||||
* 2**log2_ppc contiguous aligned pages.
|
||||
*
|
||||
* The last page of each checkpoint contains the journal header
|
||||
* and the metadata for the other pages in the period (the user
|
||||
* pages).
|
||||
*/
|
||||
uint8_t log2_ppc;
|
||||
|
||||
/* Epoch counter. This is incremented whenever the journal head
|
||||
* passes the end of the chip and wraps around.
|
||||
*/
|
||||
uint8_t epoch;
|
||||
|
||||
/* General purpose flags field */
|
||||
uint8_t flags;
|
||||
|
||||
/* Bad-block counters. bb_last is our best estimate of the
|
||||
* number of bad blocks in the chip as a whole. bb_current is
|
||||
* the number of bad blocks in all blocks before the current
|
||||
* head.
|
||||
*/
|
||||
dhara_block_t bb_current;
|
||||
dhara_block_t bb_last;
|
||||
|
||||
/* Log head and tail. The tail pointer points to the last user
|
||||
* page in the log, and the head pointer points to the next free
|
||||
* raw page. The root points to the last written user page.
|
||||
*/
|
||||
dhara_page_t tail_sync;
|
||||
dhara_page_t tail;
|
||||
dhara_page_t head;
|
||||
|
||||
/* This points to the last written user page in the journal */
|
||||
dhara_page_t root;
|
||||
|
||||
/* Recovery mode: recover_root points to the last valid user
|
||||
* page in the block requiring recovery. recover_next points to
|
||||
* the next user page needing recovery.
|
||||
*
|
||||
* If we had buffered metadata before recovery started, it will
|
||||
* have been dumped to a free page, indicated by recover_meta.
|
||||
* If this block later goes bad, we will have to defer bad-block
|
||||
* marking until recovery is complete (F_BAD_META).
|
||||
*/
|
||||
dhara_page_t recover_next;
|
||||
dhara_page_t recover_root;
|
||||
dhara_page_t recover_meta;
|
||||
};
|
||||
|
||||
/* Initialize a journal. You must supply a pointer to a NAND chip
|
||||
* driver, and a single page buffer. This page buffer will be used
|
||||
* exclusively by the journal, but you are responsible for allocating
|
||||
* it, and freeing it (if necessary) at the end.
|
||||
*
|
||||
* No NAND operations are performed at this point.
|
||||
*/
|
||||
void dhara_journal_init(struct dhara_journal *j,
|
||||
const struct dhara_nand *n,
|
||||
uint8_t *page_buf);
|
||||
|
||||
/* Start up the journal -- search the NAND for the journal head, or
|
||||
* initialize a blank journal if one isn't found. Returns 0 on success
|
||||
* or -1 if a (fatal) error occurs.
|
||||
*
|
||||
* This operation is O(log N), where N is the number of pages in the
|
||||
* NAND chip. All other operations are O(1).
|
||||
*
|
||||
* If this operation fails, the journal will be reset to an empty state.
|
||||
*/
|
||||
int dhara_journal_resume(struct dhara_journal *j, dhara_error_t *err);
|
||||
|
||||
/* Obtain an upper bound on the number of user pages storable in the
|
||||
* journal.
|
||||
*/
|
||||
dhara_page_t dhara_journal_capacity(const struct dhara_journal *j);
|
||||
|
||||
/* Obtain an upper bound on the number of user pages consumed by the
|
||||
* journal.
|
||||
*/
|
||||
dhara_page_t dhara_journal_size(const struct dhara_journal *j);
|
||||
|
||||
/* Obtain a pointer to the cookie data */
|
||||
static inline uint8_t *dhara_journal_cookie(const struct dhara_journal *j)
|
||||
{
|
||||
return j->page_buf + DHARA_HEADER_SIZE;
|
||||
}
|
||||
|
||||
/* Obtain the locations of the first and last pages in the journal.
|
||||
*/
|
||||
static inline dhara_page_t dhara_journal_root(const struct dhara_journal *j)
|
||||
{
|
||||
return j->root;
|
||||
}
|
||||
|
||||
/* Read metadata associated with a page. This assumes that the page
|
||||
* provided is a valid data page. The actual page data is read via the
|
||||
* normal NAND interface.
|
||||
*/
|
||||
int dhara_journal_read_meta(struct dhara_journal *j, dhara_page_t p,
|
||||
uint8_t *buf, dhara_error_t *err);
|
||||
|
||||
/* Advance the tail to the next non-bad block and return the page that's
|
||||
* ready to read. If no page is ready, return DHARA_PAGE_NONE.
|
||||
*/
|
||||
dhara_page_t dhara_journal_peek(struct dhara_journal *j);
|
||||
|
||||
/* Remove the last page from the journal. This doesn't take permanent
|
||||
* effect until the next checkpoint.
|
||||
*/
|
||||
void dhara_journal_dequeue(struct dhara_journal *j);
|
||||
|
||||
/* Remove all pages form the journal. This doesn't take permanent effect
|
||||
* until the next checkpoint.
|
||||
*/
|
||||
void dhara_journal_clear(struct dhara_journal *j);
|
||||
|
||||
/* Append a page to the journal. Both raw page data and metadata must be
|
||||
* specified. The push operation is not persistent until a checkpoint is
|
||||
* reached.
|
||||
*
|
||||
* This operation may fail with the error code E_RECOVER. If this
|
||||
* occurs, the upper layer must complete the assisted recovery procedure
|
||||
* and then try again.
|
||||
*
|
||||
* This operation may be used as part of a recovery. If further errors
|
||||
* occur during recovery, E_RECOVER is returned, and the procedure must
|
||||
* be restarted.
|
||||
*/
|
||||
int dhara_journal_enqueue(struct dhara_journal *j,
|
||||
const uint8_t *data, const uint8_t *meta,
|
||||
dhara_error_t *err);
|
||||
|
||||
/* Copy an existing page to the front of the journal. New metadata must
|
||||
* be specified. This operation is not persistent until a checkpoint is
|
||||
* reached.
|
||||
*
|
||||
* This operation may fail with the error code E_RECOVER. If this
|
||||
* occurs, the upper layer must complete the assisted recovery procedure
|
||||
* and then try again.
|
||||
*
|
||||
* This operation may be used as part of a recovery. If further errors
|
||||
* occur during recovery, E_RECOVER is returned, and the procedure must
|
||||
* be restarted.
|
||||
*/
|
||||
int dhara_journal_copy(struct dhara_journal *j,
|
||||
dhara_page_t p, const uint8_t *meta,
|
||||
dhara_error_t *err);
|
||||
|
||||
/* Mark the journal dirty. */
|
||||
static inline void dhara_journal_mark_dirty(struct dhara_journal *j)
|
||||
{
|
||||
j->flags |= DHARA_JOURNAL_F_DIRTY;
|
||||
}
|
||||
|
||||
/* Is the journal checkpointed? If true, then all pages enqueued are now
|
||||
* persistent.
|
||||
*/
|
||||
static inline int dhara_journal_is_clean(const struct dhara_journal *j)
|
||||
{
|
||||
return !(j->flags & DHARA_JOURNAL_F_DIRTY);
|
||||
}
|
||||
|
||||
/* If an operation returns E_RECOVER, you must begin the recovery
|
||||
* procedure. You must then:
|
||||
*
|
||||
* - call dhara_journal_next_recoverable() to obtain the next block
|
||||
* to be recovered (if any). If there are no blocks remaining to be
|
||||
* recovered, DHARA_JOURNAL_PAGE_NONE is returned.
|
||||
*
|
||||
* - proceed to the next checkpoint. Once the journal is clean,
|
||||
* recovery will finish automatically.
|
||||
*
|
||||
* If any operation during recovery fails due to a bad block, E_RECOVER
|
||||
* is returned again, and recovery restarts. Do not add new data to the
|
||||
* journal (rewrites of recovered data are fine) until recovery is
|
||||
* complete.
|
||||
*/
|
||||
static inline int dhara_journal_in_recovery(const struct dhara_journal *j)
|
||||
{
|
||||
return j->flags & DHARA_JOURNAL_F_RECOVERY;
|
||||
}
|
||||
|
||||
dhara_page_t dhara_journal_next_recoverable(struct dhara_journal *j);
|
||||
|
||||
#endif
|
||||
530
Lib/dhara/map.c
Normal file
530
Lib/dhara/map.c
Normal file
@@ -0,0 +1,530 @@
|
||||
/* Dhara - NAND flash management layer
|
||||
* Copyright (C) 2013 Daniel Beer <dlbeer@gmail.com>
|
||||
*
|
||||
* Permission to use, copy, modify, and/or distribute this software for any
|
||||
* purpose with or without fee is hereby granted, provided that the above
|
||||
* copyright notice and this permission notice appear in all copies.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
*/
|
||||
|
||||
#include <string.h>
|
||||
#include "bytes.h"
|
||||
#include "map.h"
|
||||
|
||||
#define DHARA_RADIX_DEPTH (sizeof(dhara_sector_t) << 3)
|
||||
|
||||
static inline dhara_sector_t d_bit(int depth)
|
||||
{
|
||||
return ((dhara_sector_t)1) << (DHARA_RADIX_DEPTH - depth - 1);
|
||||
}
|
||||
|
||||
/************************************************************************
|
||||
* Metadata/cookie layout
|
||||
*/
|
||||
|
||||
static inline void ck_set_count(uint8_t *cookie, dhara_sector_t count)
|
||||
{
|
||||
dhara_w32(cookie, count);
|
||||
}
|
||||
|
||||
static inline dhara_sector_t ck_get_count(const uint8_t *cookie)
|
||||
{
|
||||
return dhara_r32(cookie);
|
||||
}
|
||||
|
||||
static inline void meta_clear(uint8_t *meta)
|
||||
{
|
||||
memset(meta, 0xff, DHARA_META_SIZE);
|
||||
}
|
||||
|
||||
static inline dhara_sector_t meta_get_id(const uint8_t *meta)
|
||||
{
|
||||
return dhara_r32(meta);
|
||||
}
|
||||
|
||||
static inline void meta_set_id(uint8_t *meta, dhara_sector_t id)
|
||||
{
|
||||
dhara_w32(meta, id);
|
||||
}
|
||||
|
||||
static inline dhara_page_t meta_get_alt(const uint8_t *meta, int level)
|
||||
{
|
||||
return dhara_r32(meta + 4 + (level << 2));
|
||||
}
|
||||
|
||||
static inline void meta_set_alt(uint8_t *meta, int level, dhara_page_t alt)
|
||||
{
|
||||
dhara_w32(meta + 4 + (level << 2), alt);
|
||||
}
|
||||
|
||||
/************************************************************************
|
||||
* Public interface
|
||||
*/
|
||||
|
||||
void dhara_map_init(struct dhara_map *m, const struct dhara_nand *n,
|
||||
uint8_t *page_buf, uint8_t gc_ratio)
|
||||
{
|
||||
if (!gc_ratio)
|
||||
gc_ratio = 1;
|
||||
|
||||
dhara_journal_init(&m->journal, n, page_buf);
|
||||
m->gc_ratio = gc_ratio;
|
||||
}
|
||||
|
||||
int dhara_map_resume(struct dhara_map *m, dhara_error_t *err)
|
||||
{
|
||||
if (dhara_journal_resume(&m->journal, err) < 0) {
|
||||
m->count = 0;
|
||||
return -1;
|
||||
}
|
||||
|
||||
m->count = ck_get_count(dhara_journal_cookie(&m->journal));
|
||||
return 0;
|
||||
}
|
||||
|
||||
void dhara_map_clear(struct dhara_map *m)
|
||||
{
|
||||
if (m->count) {
|
||||
m->count = 0;
|
||||
dhara_journal_clear(&m->journal);
|
||||
}
|
||||
}
|
||||
|
||||
dhara_sector_t dhara_map_capacity(const struct dhara_map *m)
|
||||
{
|
||||
const dhara_sector_t cap = dhara_journal_capacity(&m->journal);
|
||||
const dhara_sector_t reserve = cap / (m->gc_ratio + 1);
|
||||
const dhara_sector_t safety_margin =
|
||||
DHARA_MAX_RETRIES << m->journal.nand->log2_ppb;
|
||||
|
||||
if (reserve + safety_margin >= cap)
|
||||
return 0;
|
||||
|
||||
return cap - reserve - safety_margin;
|
||||
}
|
||||
|
||||
/* Trace the path from the root to the given sector, emitting
|
||||
* alt-pointers and alt-full bits in the given metadata buffer. This
|
||||
* also returns the physical page containing the given sector, if it
|
||||
* exists.
|
||||
*
|
||||
* If the page can't be found, a suitable path will be constructed
|
||||
* (containing PAGE_NONE alt-pointers), and DHARA_E_NOT_FOUND will be
|
||||
* returned.
|
||||
*/
|
||||
static int trace_path(struct dhara_map *m, dhara_sector_t target,
|
||||
dhara_page_t *loc, uint8_t *new_meta,
|
||||
dhara_error_t *err)
|
||||
{
|
||||
uint8_t meta[DHARA_META_SIZE];
|
||||
int depth = 0;
|
||||
dhara_page_t p = dhara_journal_root(&m->journal);
|
||||
|
||||
if (new_meta)
|
||||
meta_set_id(new_meta, target);
|
||||
|
||||
if (p == DHARA_PAGE_NONE)
|
||||
goto not_found;
|
||||
|
||||
if (dhara_journal_read_meta(&m->journal, p, meta, err) < 0)
|
||||
return -1;
|
||||
|
||||
while (depth < DHARA_RADIX_DEPTH) {
|
||||
const dhara_sector_t id = meta_get_id(meta);
|
||||
|
||||
if (id == DHARA_SECTOR_NONE)
|
||||
goto not_found;
|
||||
|
||||
if ((target ^ id) & d_bit(depth)) {
|
||||
if (new_meta)
|
||||
meta_set_alt(new_meta, depth, p);
|
||||
|
||||
p = meta_get_alt(meta, depth);
|
||||
if (p == DHARA_PAGE_NONE) {
|
||||
depth++;
|
||||
goto not_found;
|
||||
}
|
||||
|
||||
if (dhara_journal_read_meta(&m->journal, p,
|
||||
meta, err) < 0)
|
||||
return -1;
|
||||
} else {
|
||||
if (new_meta)
|
||||
meta_set_alt(new_meta, depth,
|
||||
meta_get_alt(meta, depth));
|
||||
}
|
||||
|
||||
depth++;
|
||||
}
|
||||
|
||||
if (loc)
|
||||
*loc = p;
|
||||
|
||||
return 0;
|
||||
|
||||
not_found:
|
||||
if (new_meta) {
|
||||
while (depth < DHARA_RADIX_DEPTH)
|
||||
meta_set_alt(new_meta, depth++, DHARA_SECTOR_NONE);
|
||||
}
|
||||
|
||||
dhara_set_error(err, DHARA_E_NOT_FOUND);
|
||||
return -1;
|
||||
}
|
||||
|
||||
int dhara_map_find(struct dhara_map *m, dhara_sector_t target,
|
||||
dhara_page_t *loc, dhara_error_t *err)
|
||||
{
|
||||
return trace_path(m, target, loc, NULL, err);
|
||||
}
|
||||
|
||||
int dhara_map_read(struct dhara_map *m, dhara_sector_t s,
|
||||
uint8_t *data, dhara_error_t *err)
|
||||
{
|
||||
const struct dhara_nand *n = m->journal.nand;
|
||||
dhara_error_t my_err;
|
||||
dhara_page_t p;
|
||||
|
||||
if (dhara_map_find(m, s, &p, &my_err) < 0) {
|
||||
if (my_err == DHARA_E_NOT_FOUND) {
|
||||
memset(data, 0xff, 1 << n->log2_page_size);
|
||||
return 0;
|
||||
}
|
||||
|
||||
dhara_set_error(err, my_err);
|
||||
return -1;
|
||||
}
|
||||
|
||||
return dhara_nand_read(n, p, 0, 1 << n->log2_page_size, data, err);
|
||||
}
|
||||
|
||||
/* Check the given page. If it's garbage, do nothing. Otherwise, rewrite
|
||||
* it at the front of the map. Return raw errors from the journal (do
|
||||
* not perform recovery).
|
||||
*/
|
||||
static int raw_gc(struct dhara_map *m, dhara_page_t src,
|
||||
dhara_error_t *err)
|
||||
{
|
||||
dhara_sector_t target;
|
||||
dhara_page_t current;
|
||||
dhara_error_t my_err;
|
||||
uint8_t meta[DHARA_META_SIZE];
|
||||
|
||||
if (dhara_journal_read_meta(&m->journal, src, meta, err) < 0)
|
||||
return -1;
|
||||
|
||||
/* Is the page just filler/garbage? */
|
||||
target = meta_get_id(meta);
|
||||
if (target == DHARA_SECTOR_NONE)
|
||||
return 0;
|
||||
|
||||
/* Find out where the sector once represented by this page
|
||||
* currently resides (if anywhere).
|
||||
*/
|
||||
if (trace_path(m, target, ¤t, meta, &my_err) < 0) {
|
||||
if (my_err == DHARA_E_NOT_FOUND)
|
||||
return 0;
|
||||
|
||||
dhara_set_error(err, my_err);
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* Is this page still the most current representative? If not,
|
||||
* do nothing.
|
||||
*/
|
||||
if (current != src)
|
||||
return 0;
|
||||
|
||||
/* Rewrite it at the front of the journal with updated metadata */
|
||||
ck_set_count(dhara_journal_cookie(&m->journal), m->count);
|
||||
if (dhara_journal_copy(&m->journal, src, meta, err) < 0)
|
||||
return -1;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int pad_queue(struct dhara_map *m, dhara_error_t *err)
|
||||
{
|
||||
dhara_page_t p = dhara_journal_root(&m->journal);
|
||||
uint8_t root_meta[DHARA_META_SIZE];
|
||||
|
||||
ck_set_count(dhara_journal_cookie(&m->journal), m->count);
|
||||
|
||||
if (p == DHARA_PAGE_NONE)
|
||||
return dhara_journal_enqueue(&m->journal, NULL, NULL, err);
|
||||
|
||||
if (dhara_journal_read_meta(&m->journal, p, root_meta, err) < 0)
|
||||
return -1;
|
||||
|
||||
return dhara_journal_copy(&m->journal, p, root_meta, err);
|
||||
}
|
||||
|
||||
/* Attempt to recover the journal */
|
||||
static int try_recover(struct dhara_map *m, dhara_error_t cause,
|
||||
dhara_error_t *err)
|
||||
{
|
||||
int restart_count = 0;
|
||||
|
||||
if (cause != DHARA_E_RECOVER) {
|
||||
dhara_set_error(err, cause);
|
||||
return -1;
|
||||
}
|
||||
|
||||
while (dhara_journal_in_recovery(&m->journal)) {
|
||||
dhara_page_t p = dhara_journal_next_recoverable(&m->journal);
|
||||
dhara_error_t my_err;
|
||||
int ret;
|
||||
|
||||
if (p == DHARA_PAGE_NONE)
|
||||
ret = pad_queue(m, &my_err);
|
||||
else
|
||||
ret = raw_gc(m, p, &my_err);
|
||||
|
||||
if (ret < 0) {
|
||||
if (my_err != DHARA_E_RECOVER) {
|
||||
dhara_set_error(err, my_err);
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (restart_count >= DHARA_MAX_RETRIES) {
|
||||
dhara_set_error(err, DHARA_E_TOO_BAD);
|
||||
return -1;
|
||||
}
|
||||
|
||||
restart_count++;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int auto_gc(struct dhara_map *m, dhara_error_t *err)
|
||||
{
|
||||
int i;
|
||||
|
||||
if (dhara_journal_size(&m->journal) < dhara_map_capacity(m))
|
||||
return 0;
|
||||
|
||||
for (i = 0; i <= m->gc_ratio; i++)
|
||||
if (dhara_map_gc(m, err) < 0)
|
||||
return -1;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int prepare_write(struct dhara_map *m, dhara_sector_t dst,
|
||||
uint8_t *meta, dhara_error_t *err)
|
||||
{
|
||||
dhara_error_t my_err;
|
||||
|
||||
if (auto_gc(m, err) < 0)
|
||||
return -1;
|
||||
|
||||
if (trace_path(m, dst, NULL, meta, &my_err) < 0) {
|
||||
if (my_err != DHARA_E_NOT_FOUND) {
|
||||
dhara_set_error(err, my_err);
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (m->count >= dhara_map_capacity(m)) {
|
||||
dhara_set_error(err, DHARA_E_MAP_FULL);
|
||||
return -1;
|
||||
}
|
||||
|
||||
m->count++;
|
||||
}
|
||||
|
||||
ck_set_count(dhara_journal_cookie(&m->journal), m->count);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int dhara_map_write(struct dhara_map *m, dhara_sector_t dst,
|
||||
const uint8_t *data, dhara_error_t *err)
|
||||
{
|
||||
for (;;) {
|
||||
uint8_t meta[DHARA_META_SIZE];
|
||||
dhara_error_t my_err;
|
||||
const dhara_sector_t old_count = m->count;
|
||||
|
||||
if (prepare_write(m, dst, meta, err) < 0)
|
||||
return -1;
|
||||
|
||||
if (!dhara_journal_enqueue(&m->journal, data, meta, &my_err))
|
||||
break;
|
||||
|
||||
m->count = old_count;
|
||||
|
||||
if (try_recover(m, my_err, err) < 0)
|
||||
return -1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int dhara_map_copy_page(struct dhara_map *m, dhara_page_t src,
|
||||
dhara_sector_t dst, dhara_error_t *err)
|
||||
{
|
||||
for (;;) {
|
||||
uint8_t meta[DHARA_META_SIZE];
|
||||
dhara_error_t my_err;
|
||||
const dhara_sector_t old_count = m->count;
|
||||
|
||||
if (prepare_write(m, dst, meta, err) < 0)
|
||||
return -1;
|
||||
|
||||
if (!dhara_journal_copy(&m->journal, src, meta, &my_err))
|
||||
break;
|
||||
|
||||
m->count = old_count;
|
||||
|
||||
if (try_recover(m, my_err, err) < 0)
|
||||
return -1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int dhara_map_copy_sector(struct dhara_map *m, dhara_sector_t src,
|
||||
dhara_sector_t dst, dhara_error_t *err)
|
||||
{
|
||||
dhara_error_t my_err;
|
||||
dhara_page_t p;
|
||||
|
||||
if (dhara_map_find(m, src, &p, &my_err) < 0) {
|
||||
if (my_err == DHARA_E_NOT_FOUND)
|
||||
return dhara_map_trim(m, dst, err);
|
||||
|
||||
dhara_set_error(err, my_err);
|
||||
return -1;
|
||||
}
|
||||
|
||||
return dhara_map_copy_page(m, p, dst, err);
|
||||
}
|
||||
|
||||
static int try_delete(struct dhara_map *m, dhara_sector_t s,
|
||||
dhara_error_t *err)
|
||||
{
|
||||
dhara_error_t my_err;
|
||||
uint8_t meta[DHARA_META_SIZE];
|
||||
dhara_page_t alt_page;
|
||||
uint8_t alt_meta[DHARA_META_SIZE];
|
||||
int level = DHARA_RADIX_DEPTH - 1;
|
||||
int i;
|
||||
|
||||
if (trace_path(m, s, NULL, meta, &my_err) < 0) {
|
||||
if (my_err == DHARA_E_NOT_FOUND)
|
||||
return 0;
|
||||
|
||||
dhara_set_error(err, my_err);
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* Select any of the closest cousins of this node which are
|
||||
* subtrees of at least the requested order.
|
||||
*/
|
||||
while (level >= 0) {
|
||||
alt_page = meta_get_alt(meta, level);
|
||||
if (alt_page != DHARA_PAGE_NONE)
|
||||
break;
|
||||
level--;
|
||||
}
|
||||
|
||||
/* Special case: deletion of last sector */
|
||||
if (level < 0) {
|
||||
m->count = 0;
|
||||
dhara_journal_clear(&m->journal);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Rewrite the cousin with an up-to-date path which doesn't
|
||||
* point to the original node.
|
||||
*/
|
||||
if (dhara_journal_read_meta(&m->journal, alt_page, alt_meta, err) < 0)
|
||||
return -1;
|
||||
|
||||
meta_set_id(meta, meta_get_id(alt_meta));
|
||||
|
||||
meta_set_alt(meta, level, DHARA_PAGE_NONE);
|
||||
for (i = level + 1; i < DHARA_RADIX_DEPTH; i++)
|
||||
meta_set_alt(meta, i, meta_get_alt(alt_meta, i));
|
||||
|
||||
meta_set_alt(meta, level, DHARA_PAGE_NONE);
|
||||
|
||||
ck_set_count(dhara_journal_cookie(&m->journal), m->count - 1);
|
||||
if (dhara_journal_copy(&m->journal, alt_page, meta, err) < 0)
|
||||
return -1;
|
||||
|
||||
m->count--;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int dhara_map_trim(struct dhara_map *m, dhara_sector_t s, dhara_error_t *err)
|
||||
{
|
||||
for (;;) {
|
||||
dhara_error_t my_err;
|
||||
|
||||
if (auto_gc(m, err) < 0)
|
||||
return -1;
|
||||
|
||||
if (!try_delete(m, s, &my_err))
|
||||
break;
|
||||
|
||||
if (try_recover(m, my_err, err) < 0)
|
||||
return -1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int dhara_map_sync(struct dhara_map *m, dhara_error_t *err)
|
||||
{
|
||||
while (!dhara_journal_is_clean(&m->journal)) {
|
||||
dhara_page_t p = dhara_journal_peek(&m->journal);
|
||||
dhara_error_t my_err;
|
||||
int ret;
|
||||
|
||||
if (p == DHARA_PAGE_NONE) {
|
||||
ret = pad_queue(m, &my_err);
|
||||
} else {
|
||||
ret = raw_gc(m, p, &my_err);
|
||||
if (!ret)
|
||||
dhara_journal_dequeue(&m->journal);
|
||||
}
|
||||
|
||||
if ((ret < 0) && (try_recover(m, my_err, err) < 0))
|
||||
return -1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int dhara_map_gc(struct dhara_map *m, dhara_error_t *err)
|
||||
{
|
||||
if (!m->count)
|
||||
return 0;
|
||||
|
||||
for (;;) {
|
||||
dhara_page_t tail = dhara_journal_peek(&m->journal);
|
||||
dhara_error_t my_err;
|
||||
|
||||
if (tail == DHARA_PAGE_NONE)
|
||||
break;
|
||||
|
||||
if (!raw_gc(m, tail, &my_err)) {
|
||||
dhara_journal_dequeue(&m->journal);
|
||||
break;
|
||||
}
|
||||
|
||||
if (try_recover(m, my_err, err) < 0)
|
||||
return -1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
114
Lib/dhara/map.h
Normal file
114
Lib/dhara/map.h
Normal file
@@ -0,0 +1,114 @@
|
||||
/* Dhara - NAND flash management layer
|
||||
* Copyright (C) 2013 Daniel Beer <dlbeer@gmail.com>
|
||||
*
|
||||
* Permission to use, copy, modify, and/or distribute this software for any
|
||||
* purpose with or without fee is hereby granted, provided that the above
|
||||
* copyright notice and this permission notice appear in all copies.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
*/
|
||||
|
||||
#ifndef DHARA_MAP_H_
|
||||
#define DHARA_MAP_H_
|
||||
|
||||
#include "journal.h"
|
||||
|
||||
/* The map is a journal indexing format. It maps virtual sectors to
|
||||
* pages of data in flash memory.
|
||||
*/
|
||||
typedef uint32_t dhara_sector_t;
|
||||
|
||||
/* This sector value is reserved */
|
||||
#define DHARA_SECTOR_NONE 0xffffffff
|
||||
|
||||
struct dhara_map {
|
||||
struct dhara_journal journal;
|
||||
|
||||
uint8_t gc_ratio;
|
||||
dhara_sector_t count;
|
||||
};
|
||||
|
||||
/* Initialize a map. You need to supply a buffer for page metadata, and
|
||||
* a garbage collection ratio. This is the ratio of garbage collection
|
||||
* operations to real writes when automatic collection is active.
|
||||
*
|
||||
* Smaller values lead to faster and more predictable IO, at the
|
||||
* expense of capacity. You should always initialize the same chip with
|
||||
* the same garbage collection ratio.
|
||||
*/
|
||||
void dhara_map_init(struct dhara_map *m, const struct dhara_nand *n,
|
||||
uint8_t *page_buf, uint8_t gc_ratio);
|
||||
|
||||
/* Recover stored state, if possible. If there is no valid stored state
|
||||
* on the chip, -1 is returned, and an empty map is initialized.
|
||||
*/
|
||||
int dhara_map_resume(struct dhara_map *m, dhara_error_t *err);
|
||||
|
||||
/* Clear the map (delete all sectors). */
|
||||
void dhara_map_clear(struct dhara_map *m);
|
||||
|
||||
/* Obtain the maximum capacity of the map. */
|
||||
dhara_sector_t dhara_map_capacity(const struct dhara_map *m);
|
||||
|
||||
/* Obtain the current number of allocated sectors. */
|
||||
static inline dhara_sector_t dhara_map_size(const struct dhara_map *m)
|
||||
{
|
||||
return m->count;
|
||||
}
|
||||
|
||||
/* Find the physical page which holds the current data for this sector.
|
||||
* Returns 0 on success or -1 if an error occurs. If the sector doesn't
|
||||
* exist, the error is E_NOT_FOUND.
|
||||
*/
|
||||
int dhara_map_find(struct dhara_map *m, dhara_sector_t s,
|
||||
dhara_page_t *loc, dhara_error_t *err);
|
||||
|
||||
/* Read from the given logical sector. If the sector is unmapped, a
|
||||
* blank page (0xff) will be returned.
|
||||
*/
|
||||
int dhara_map_read(struct dhara_map *m, dhara_sector_t s,
|
||||
uint8_t *data, dhara_error_t *err);
|
||||
|
||||
/* Write data to a logical sector. */
|
||||
int dhara_map_write(struct dhara_map *m, dhara_sector_t s,
|
||||
const uint8_t *data, dhara_error_t *err);
|
||||
|
||||
/* Copy any flash page to a logical sector. */
|
||||
int dhara_map_copy_page(struct dhara_map *m, dhara_page_t src,
|
||||
dhara_sector_t dst, dhara_error_t *err);
|
||||
|
||||
/* Copy one sector to another. If the source sector is unmapped, the
|
||||
* destination sector will be trimmed.
|
||||
*/
|
||||
int dhara_map_copy_sector(struct dhara_map *m, dhara_sector_t src,
|
||||
dhara_sector_t dst, dhara_error_t *err);
|
||||
|
||||
/* Delete a logical sector. You don't necessarily need to do this, but
|
||||
* it's a useful hint if you no longer require the sector's data to be
|
||||
* kept.
|
||||
*
|
||||
* If order is non-zero, it specifies that all sectors in the
|
||||
* (2**order)-aligned group of s are to be deleted.
|
||||
*/
|
||||
int dhara_map_trim(struct dhara_map *m, dhara_sector_t s,
|
||||
dhara_error_t *err);
|
||||
|
||||
/* Synchronize the map. Once this returns successfully, all changes to
|
||||
* date are persistent and durable. Conversely, there is no guarantee
|
||||
* that unsynchronized changes will be persistent.
|
||||
*/
|
||||
int dhara_map_sync(struct dhara_map *m, dhara_error_t *err);
|
||||
|
||||
/* Perform one garbage collection step. You can do this whenever you
|
||||
* like, but it's not necessary -- garbage collection happens
|
||||
* automatically and is interleaved with other operations.
|
||||
*/
|
||||
int dhara_map_gc(struct dhara_map *m, dhara_error_t *err);
|
||||
|
||||
#endif
|
||||
105
Lib/dhara/nand.h
Normal file
105
Lib/dhara/nand.h
Normal file
@@ -0,0 +1,105 @@
|
||||
/* Dhara - NAND flash management layer
|
||||
* Copyright (C) 2013 Daniel Beer <dlbeer@gmail.com>
|
||||
*
|
||||
* Permission to use, copy, modify, and/or distribute this software for any
|
||||
* purpose with or without fee is hereby granted, provided that the above
|
||||
* copyright notice and this permission notice appear in all copies.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
*/
|
||||
|
||||
#ifndef DHARA_NAND_H_
|
||||
#define DHARA_NAND_H_
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stddef.h>
|
||||
#include "error.h"
|
||||
|
||||
/* Each page in a NAND device is indexed, starting at 0. It's required
|
||||
* that there be a power-of-two number of pages in a eraseblock, so you can
|
||||
* view a page number is being a concatenation (in binary) of a block
|
||||
* number and the number of a page within a block.
|
||||
*/
|
||||
typedef uint32_t dhara_page_t;
|
||||
|
||||
/* Blocks are also indexed, starting at 0. */
|
||||
typedef uint32_t dhara_block_t;
|
||||
|
||||
/* Each NAND chip must be represented by one of these structures. It's
|
||||
* intended that this structure be embedded in a larger structure for
|
||||
* context.
|
||||
*
|
||||
* The functions declared below are not implemented -- they must be
|
||||
* provided and satisfy the documented conditions.
|
||||
*/
|
||||
struct dhara_nand {
|
||||
/* Base-2 logarithm of the page size. If your device supports
|
||||
* partial programming, you may want to subdivide the actual
|
||||
* pages into separate ECC-correctable regions and present those
|
||||
* as pages.
|
||||
*/
|
||||
uint8_t log2_page_size;
|
||||
|
||||
/* Base-2 logarithm of the number of pages within an eraseblock */
|
||||
uint8_t log2_ppb;
|
||||
|
||||
/* Total number of eraseblocks */
|
||||
unsigned int num_blocks;
|
||||
};
|
||||
|
||||
/* Is the given block bad? */
|
||||
int dhara_nand_is_bad(const struct dhara_nand *n, dhara_block_t b);
|
||||
|
||||
/* Mark bad the given block (or attempt to). No return value is
|
||||
* required, because there's nothing that can be done in response.
|
||||
*/
|
||||
void dhara_nand_mark_bad(const struct dhara_nand *n, dhara_block_t b);
|
||||
|
||||
/* Erase the given block. This function should return 0 on success or -1
|
||||
* on failure.
|
||||
*
|
||||
* The status reported by the chip should be checked. If an erase
|
||||
* operation fails, return -1 and set err to E_BAD_BLOCK.
|
||||
*/
|
||||
int dhara_nand_erase(const struct dhara_nand *n, dhara_block_t b,
|
||||
dhara_error_t *err);
|
||||
|
||||
/* Program the given page. The data pointer is a pointer to an entire
|
||||
* page ((1 << log2_page_size) bytes). The operation status should be
|
||||
* checked. If the operation fails, return -1 and set err to
|
||||
* E_BAD_BLOCK.
|
||||
*
|
||||
* Pages will be programmed sequentially within a block, and will not be
|
||||
* reprogrammed.
|
||||
*/
|
||||
int dhara_nand_prog(const struct dhara_nand *n, dhara_page_t p,
|
||||
const uint8_t *data,
|
||||
dhara_error_t *err);
|
||||
|
||||
/* Check that the given page is erased */
|
||||
int dhara_nand_is_free(const struct dhara_nand *n, dhara_page_t p);
|
||||
|
||||
/* Read a portion of a page. ECC must be handled by the NAND
|
||||
* implementation. Returns 0 on sucess or -1 if an error occurs. If an
|
||||
* uncorrectable ECC error occurs, return -1 and set err to E_ECC.
|
||||
*/
|
||||
int dhara_nand_read(const struct dhara_nand *n, dhara_page_t p,
|
||||
size_t offset, size_t length,
|
||||
uint8_t *data,
|
||||
dhara_error_t *err);
|
||||
|
||||
/* Read a page from one location and reprogram it in another location.
|
||||
* This might be done using the chip's internal buffers, but it must use
|
||||
* ECC.
|
||||
*/
|
||||
int dhara_nand_copy(const struct dhara_nand *n,
|
||||
dhara_page_t src, dhara_page_t dst,
|
||||
dhara_error_t *err);
|
||||
|
||||
#endif
|
||||
Reference in New Issue
Block a user