增加文件系统

This commit is contained in:
2026-07-21 00:30:59 +08:00
parent e06d849007
commit fbe0726991
50 changed files with 12921 additions and 893 deletions

43
Lib/FatFs/diskio.h Normal file
View File

@@ -0,0 +1,43 @@
#ifndef _DISKIO_DEFINED
#define _DISKIO_DEFINED
#ifdef __cplusplus
extern "C" {
#endif
typedef BYTE DSTATUS;
typedef enum {
RES_OK = 0,
RES_ERROR,
RES_WRPRT,
RES_NOTRDY,
RES_PARERR
} DRESULT;
DSTATUS disk_initialize(BYTE pdrv);
DSTATUS disk_status(BYTE pdrv);
DRESULT disk_read(BYTE pdrv, BYTE* buff, LBA_t sector, UINT count);
DRESULT disk_write(BYTE pdrv, const BYTE* buff, LBA_t sector, UINT count);
DRESULT disk_ioctl(BYTE pdrv, BYTE cmd, void* buff);
#define STA_NOINIT 0x01
#define STA_NODISK 0x02
#define STA_PROTECT 0x04
#define CTRL_SYNC 0
#define GET_SECTOR_COUNT 1
#define GET_SECTOR_SIZE 2
#define GET_BLOCK_SIZE 3
#define CTRL_TRIM 4
#define CTRL_POWER 5
#define CTRL_LOCK 6
#define CTRL_EJECT 7
#define CTRL_FORMAT 8
#ifdef __cplusplus
}
#endif
#endif

7084
Lib/FatFs/ff.c Normal file

File diff suppressed because it is too large Load Diff

429
Lib/FatFs/ff.h Normal file
View File

@@ -0,0 +1,429 @@
/*----------------------------------------------------------------------------/
/ FatFs - Generic FAT Filesystem module R0.15 /
/-----------------------------------------------------------------------------/
/
/ Copyright (C) 2022, ChaN, all right reserved.
/
/ FatFs module is an open source software. Redistribution and use of FatFs in
/ source and binary forms, with or without modification, are permitted provided
/ that the following condition is met:
/
/ 1. Redistributions of source code must retain the above copyright notice,
/ this condition and the following disclaimer.
/
/ This software is provided by the copyright holder and contributors "AS IS"
/ and any warranties related to this software are DISCLAIMED.
/ The copyright owner or contributors be NOT LIABLE for any damages caused
/ by use of this software.
/
/----------------------------------------------------------------------------*/
#ifndef FF_DEFINED
#define FF_DEFINED 80286 /* Revision ID */
#ifdef __cplusplus
extern "C" {
#endif
#include "ffconf.h" /* FatFs configuration options */
#if FF_DEFINED != FFCONF_DEF
#error Wrong configuration file (ffconf.h).
#endif
/* Integer types used for FatFs API */
#if defined(_WIN32) /* Windows VC++ (for development only) */
#define FF_INTDEF 2
#include <windows.h>
typedef unsigned __int64 QWORD;
#include <float.h>
#define isnan(v) _isnan(v)
#define isinf(v) (!_finite(v))
#elif (defined(__STDC_VERSION__) && __STDC_VERSION__ >= 199901L) || defined(__cplusplus) /* C99 or later */
#define FF_INTDEF 2
#include <stdint.h>
typedef unsigned int UINT; /* int must be 16-bit or 32-bit */
typedef unsigned char BYTE; /* char must be 8-bit */
typedef uint16_t WORD; /* 16-bit unsigned integer */
typedef uint32_t DWORD; /* 32-bit unsigned integer */
typedef uint64_t QWORD; /* 64-bit unsigned integer */
typedef WORD WCHAR; /* UTF-16 character type */
#else /* Earlier than C99 */
#define FF_INTDEF 1
typedef unsigned int UINT; /* int must be 16-bit or 32-bit */
typedef unsigned char BYTE; /* char must be 8-bit */
typedef unsigned short WORD; /* 16-bit unsigned integer */
typedef unsigned long DWORD; /* 32-bit unsigned integer */
typedef WORD WCHAR; /* UTF-16 character type */
#endif
/* Type of file size and LBA variables */
#if FF_FS_EXFAT
#if FF_INTDEF != 2
#error exFAT feature wants C99 or later
#endif
typedef QWORD FSIZE_t;
#if FF_LBA64
typedef QWORD LBA_t;
#else
typedef DWORD LBA_t;
#endif
#else
#if FF_LBA64
#error exFAT needs to be enabled when enable 64-bit LBA
#endif
typedef DWORD FSIZE_t;
typedef DWORD LBA_t;
#endif
/* Type of path name strings on FatFs API (TCHAR) */
#if FF_USE_LFN && FF_LFN_UNICODE == 1 /* Unicode in UTF-16 encoding */
typedef WCHAR TCHAR;
#define _T(x) L ## x
#define _TEXT(x) L ## x
#elif FF_USE_LFN && FF_LFN_UNICODE == 2 /* Unicode in UTF-8 encoding */
typedef char TCHAR;
#define _T(x) u8 ## x
#define _TEXT(x) u8 ## x
#elif FF_USE_LFN && FF_LFN_UNICODE == 3 /* Unicode in UTF-32 encoding */
typedef DWORD TCHAR;
#define _T(x) U ## x
#define _TEXT(x) U ## x
#elif FF_USE_LFN && (FF_LFN_UNICODE < 0 || FF_LFN_UNICODE > 3)
#error Wrong FF_LFN_UNICODE setting
#else /* ANSI/OEM code in SBCS/DBCS */
typedef char TCHAR;
#define _T(x) x
#define _TEXT(x) x
#endif
/* Definitions of volume management */
#if FF_MULTI_PARTITION /* Multiple partition configuration */
typedef struct {
BYTE pd; /* Physical drive number */
BYTE pt; /* Partition: 0:Auto detect, 1-4:Forced partition) */
} PARTITION;
extern PARTITION VolToPart[]; /* Volume - Partition mapping table */
#endif
#if FF_STR_VOLUME_ID
#ifndef FF_VOLUME_STRS
extern const char* VolumeStr[FF_VOLUMES]; /* User defied volume ID */
#endif
#endif
/* Filesystem object structure (FATFS) */
typedef struct {
BYTE fs_type; /* Filesystem type (0:not mounted) */
BYTE pdrv; /* Volume hosting physical drive */
BYTE ldrv; /* Logical drive number (used only when FF_FS_REENTRANT) */
BYTE n_fats; /* Number of FATs (1 or 2) */
BYTE wflag; /* win[] status (b0:dirty) */
BYTE fsi_flag; /* FSINFO status (b7:disabled, b0:dirty) */
WORD id; /* Volume mount ID */
WORD n_rootdir; /* Number of root directory entries (FAT12/16) */
WORD csize; /* Cluster size [sectors] */
#if FF_MAX_SS != FF_MIN_SS
WORD ssize; /* Sector size (512, 1024, 2048 or 4096) */
#endif
#if FF_USE_LFN
WCHAR* lfnbuf; /* LFN working buffer */
#endif
#if FF_FS_EXFAT
BYTE* dirbuf; /* Directory entry block scratchpad buffer for exFAT */
#endif
#if !FF_FS_READONLY
DWORD last_clst; /* Last allocated cluster */
DWORD free_clst; /* Number of free clusters */
#endif
#if FF_FS_RPATH
DWORD cdir; /* Current directory start cluster (0:root) */
#if FF_FS_EXFAT
DWORD cdc_scl; /* Containing directory start cluster (invalid when cdir is 0) */
DWORD cdc_size; /* b31-b8:Size of containing directory, b7-b0: Chain status */
DWORD cdc_ofs; /* Offset in the containing directory (invalid when cdir is 0) */
#endif
#endif
DWORD n_fatent; /* Number of FAT entries (number of clusters + 2) */
DWORD fsize; /* Number of sectors per FAT */
LBA_t volbase; /* Volume base sector */
LBA_t fatbase; /* FAT base sector */
LBA_t dirbase; /* Root directory base sector (FAT12/16) or cluster (FAT32/exFAT) */
LBA_t database; /* Data base sector */
#if FF_FS_EXFAT
LBA_t bitbase; /* Allocation bitmap base sector */
#endif
LBA_t winsect; /* Current sector appearing in the win[] */
BYTE win[FF_MAX_SS]; /* Disk access window for Directory, FAT (and file data at tiny cfg) */
} FATFS;
/* Object ID and allocation information (FFOBJID) */
typedef struct {
FATFS* fs; /* Pointer to the hosting volume of this object */
WORD id; /* Hosting volume's mount ID */
BYTE attr; /* Object attribute */
BYTE stat; /* Object chain status (b1-0: =0:not contiguous, =2:contiguous, =3:fragmented in this session, b2:sub-directory stretched) */
DWORD sclust; /* Object data start cluster (0:no cluster or root directory) */
FSIZE_t objsize; /* Object size (valid when sclust != 0) */
#if FF_FS_EXFAT
DWORD n_cont; /* Size of first fragment - 1 (valid when stat == 3) */
DWORD n_frag; /* Size of last fragment needs to be written to FAT (valid when not zero) */
DWORD c_scl; /* Containing directory start cluster (valid when sclust != 0) */
DWORD c_size; /* b31-b8:Size of containing directory, b7-b0: Chain status (valid when c_scl != 0) */
DWORD c_ofs; /* Offset in the containing directory (valid when file object and sclust != 0) */
#endif
#if FF_FS_LOCK
UINT lockid; /* File lock ID origin from 1 (index of file semaphore table Files[]) */
#endif
} FFOBJID;
/* File object structure (FIL) */
typedef struct {
FFOBJID obj; /* Object identifier (must be the 1st member to detect invalid object pointer) */
BYTE flag; /* File status flags */
BYTE err; /* Abort flag (error code) */
FSIZE_t fptr; /* File read/write pointer (Zeroed on file open) */
DWORD clust; /* Current cluster of fpter (invalid when fptr is 0) */
LBA_t sect; /* Sector number appearing in buf[] (0:invalid) */
#if !FF_FS_READONLY
LBA_t dir_sect; /* Sector number containing the directory entry (not used at exFAT) */
BYTE* dir_ptr; /* Pointer to the directory entry in the win[] (not used at exFAT) */
#endif
#if FF_USE_FASTSEEK
DWORD* cltbl; /* Pointer to the cluster link map table (nulled on open, set by application) */
#endif
#if !FF_FS_TINY
BYTE buf[FF_MAX_SS]; /* File private data read/write window */
#endif
} FIL;
/* Directory object structure (DIR) */
typedef struct {
FFOBJID obj; /* Object identifier */
DWORD dptr; /* Current read/write offset */
DWORD clust; /* Current cluster */
LBA_t sect; /* Current sector (0:Read operation has terminated) */
BYTE* dir; /* Pointer to the directory item in the win[] */
BYTE fn[12]; /* SFN (in/out) {body[8],ext[3],status[1]} */
#if FF_USE_LFN
DWORD blk_ofs; /* Offset of current entry block being processed (0xFFFFFFFF:Invalid) */
#endif
#if FF_USE_FIND
const TCHAR* pat; /* Pointer to the name matching pattern */
#endif
} DIR;
/* File information structure (FILINFO) */
typedef struct {
FSIZE_t fsize; /* File size */
WORD fdate; /* Modified date */
WORD ftime; /* Modified time */
BYTE fattrib; /* File attribute */
#if FF_USE_LFN
TCHAR altname[FF_SFN_BUF + 1];/* Alternative file name */
TCHAR fname[FF_LFN_BUF + 1]; /* Primary file name */
#else
TCHAR fname[12 + 1]; /* File name */
#endif
} FILINFO;
/* Format parameter structure (MKFS_PARM) */
typedef struct {
BYTE fmt; /* Format option (FM_FAT, FM_FAT32, FM_EXFAT and FM_SFD) */
BYTE n_fat; /* Number of FATs */
UINT align; /* Data area alignment (sector) */
UINT n_root; /* Number of root directory entries */
DWORD au_size; /* Cluster size (byte) */
} MKFS_PARM;
/* File function return code (FRESULT) */
typedef enum {
FR_OK = 0, /* (0) Succeeded */
FR_DISK_ERR, /* (1) A hard error occurred in the low level disk I/O layer */
FR_INT_ERR, /* (2) Assertion failed */
FR_NOT_READY, /* (3) The physical drive cannot work */
FR_NO_FILE, /* (4) Could not find the file */
FR_NO_PATH, /* (5) Could not find the path */
FR_INVALID_NAME, /* (6) The path name format is invalid */
FR_DENIED, /* (7) Access denied due to prohibited access or directory full */
FR_EXIST, /* (8) Access denied due to prohibited access */
FR_INVALID_OBJECT, /* (9) The file/directory object is invalid */
FR_WRITE_PROTECTED, /* (10) The physical drive is write protected */
FR_INVALID_DRIVE, /* (11) The logical drive number is invalid */
FR_NOT_ENABLED, /* (12) The volume has no work area */
FR_NO_FILESYSTEM, /* (13) There is no valid FAT volume */
FR_MKFS_ABORTED, /* (14) The f_mkfs() aborted due to any problem */
FR_TIMEOUT, /* (15) Could not get a grant to access the volume within defined period */
FR_LOCKED, /* (16) The operation is rejected according to the file sharing policy */
FR_NOT_ENOUGH_CORE, /* (17) LFN working buffer could not be allocated */
FR_TOO_MANY_OPEN_FILES, /* (18) Number of open files > FF_FS_LOCK */
FR_INVALID_PARAMETER /* (19) Given parameter is invalid */
} FRESULT;
/*--------------------------------------------------------------*/
/* FatFs Module Application Interface */
/*--------------------------------------------------------------*/
FRESULT f_open (FIL* fp, const TCHAR* path, BYTE mode); /* Open or create a file */
FRESULT f_close (FIL* fp); /* Close an open file object */
FRESULT f_read (FIL* fp, void* buff, UINT btr, UINT* br); /* Read data from the file */
FRESULT f_write (FIL* fp, const void* buff, UINT btw, UINT* bw); /* Write data to the file */
FRESULT f_lseek (FIL* fp, FSIZE_t ofs); /* Move file pointer of the file object */
FRESULT f_truncate (FIL* fp); /* Truncate the file */
FRESULT f_sync (FIL* fp); /* Flush cached data of the writing file */
FRESULT f_opendir (DIR* dp, const TCHAR* path); /* Open a directory */
FRESULT f_closedir (DIR* dp); /* Close an open directory */
FRESULT f_readdir (DIR* dp, FILINFO* fno); /* Read a directory item */
FRESULT f_findfirst (DIR* dp, FILINFO* fno, const TCHAR* path, const TCHAR* pattern); /* Find first file */
FRESULT f_findnext (DIR* dp, FILINFO* fno); /* Find next file */
FRESULT f_mkdir (const TCHAR* path); /* Create a sub directory */
FRESULT f_unlink (const TCHAR* path); /* Delete an existing file or directory */
FRESULT f_rename (const TCHAR* path_old, const TCHAR* path_new); /* Rename/Move a file or directory */
FRESULT f_stat (const TCHAR* path, FILINFO* fno); /* Get file status */
FRESULT f_chmod (const TCHAR* path, BYTE attr, BYTE mask); /* Change attribute of a file/dir */
FRESULT f_utime (const TCHAR* path, const FILINFO* fno); /* Change timestamp of a file/dir */
FRESULT f_chdir (const TCHAR* path); /* Change current directory */
FRESULT f_chdrive (const TCHAR* path); /* Change current drive */
FRESULT f_getcwd (TCHAR* buff, UINT len); /* Get current directory */
FRESULT f_getfree (const TCHAR* path, DWORD* nclst, FATFS** fatfs); /* Get number of free clusters on the drive */
FRESULT f_getlabel (const TCHAR* path, TCHAR* label, DWORD* vsn); /* Get volume label */
FRESULT f_setlabel (const TCHAR* label); /* Set volume label */
FRESULT f_forward (FIL* fp, UINT(*func)(const BYTE*,UINT), UINT btf, UINT* bf); /* Forward data to the stream */
FRESULT f_expand (FIL* fp, FSIZE_t fsz, BYTE opt); /* Allocate a contiguous block to the file */
FRESULT f_mount (FATFS* fs, const TCHAR* path, BYTE opt); /* Mount/Unmount a logical drive */
FRESULT f_mkfs (const TCHAR* path, const MKFS_PARM* opt, void* work, UINT len); /* Create a FAT volume */
FRESULT f_fdisk (BYTE pdrv, const LBA_t ptbl[], void* work); /* Divide a physical drive into some partitions */
FRESULT f_setcp (WORD cp); /* Set current code page */
int f_putc (TCHAR c, FIL* fp); /* Put a character to the file */
int f_puts (const TCHAR* str, FIL* cp); /* Put a string to the file */
int f_printf (FIL* fp, const TCHAR* str, ...); /* Put a formatted string to the file */
TCHAR* f_gets (TCHAR* buff, int len, FIL* fp); /* Get a string from the file */
/* Some API fucntions are implemented as macro */
#define f_eof(fp) ((int)((fp)->fptr == (fp)->obj.objsize))
#define f_error(fp) ((fp)->err)
#define f_tell(fp) ((fp)->fptr)
#define f_size(fp) ((fp)->obj.objsize)
#define f_rewind(fp) f_lseek((fp), 0)
#define f_rewinddir(dp) f_readdir((dp), 0)
#define f_rmdir(path) f_unlink(path)
#define f_unmount(path) f_mount(0, path, 0)
/*--------------------------------------------------------------*/
/* Additional Functions */
/*--------------------------------------------------------------*/
/* RTC function (provided by user) */
#if !FF_FS_READONLY && !FF_FS_NORTC
DWORD get_fattime (void); /* Get current time */
#endif
/* LFN support functions (defined in ffunicode.c) */
#if FF_USE_LFN >= 1
WCHAR ff_oem2uni (WCHAR oem, WORD cp); /* OEM code to Unicode conversion */
WCHAR ff_uni2oem (DWORD uni, WORD cp); /* Unicode to OEM code conversion */
DWORD ff_wtoupper (DWORD uni); /* Unicode upper-case conversion */
#endif
/* O/S dependent functions (samples available in ffsystem.c) */
#if FF_USE_LFN == 3 /* Dynamic memory allocation */
void* ff_memalloc (UINT msize); /* Allocate memory block */
void ff_memfree (void* mblock); /* Free memory block */
#endif
#if FF_FS_REENTRANT /* Sync functions */
int ff_mutex_create (int vol); /* Create a sync object */
void ff_mutex_delete (int vol); /* Delete a sync object */
int ff_mutex_take (int vol); /* Lock sync object */
void ff_mutex_give (int vol); /* Unlock sync object */
#endif
/*--------------------------------------------------------------*/
/* Flags and Offset Address */
/*--------------------------------------------------------------*/
/* File access mode and open method flags (3rd argument of f_open) */
#define FA_READ 0x01
#define FA_WRITE 0x02
#define FA_OPEN_EXISTING 0x00
#define FA_CREATE_NEW 0x04
#define FA_CREATE_ALWAYS 0x08
#define FA_OPEN_ALWAYS 0x10
#define FA_OPEN_APPEND 0x30
/* Fast seek controls (2nd argument of f_lseek) */
#define CREATE_LINKMAP ((FSIZE_t)0 - 1)
/* Format options (2nd argument of f_mkfs) */
#define FM_FAT 0x01
#define FM_FAT32 0x02
#define FM_EXFAT 0x04
#define FM_ANY 0x07
#define FM_SFD 0x08
/* Filesystem type (FATFS.fs_type) */
#define FS_FAT12 1
#define FS_FAT16 2
#define FS_FAT32 3
#define FS_EXFAT 4
/* File attribute bits for directory entry (FILINFO.fattrib) */
#define AM_RDO 0x01 /* Read only */
#define AM_HID 0x02 /* Hidden */
#define AM_SYS 0x04 /* System */
#define AM_DIR 0x10 /* Directory */
#define AM_ARC 0x20 /* Archive */
#ifdef __cplusplus
}
#endif
#endif /* FF_DEFINED */

41
Lib/FatFs/ffconf.h Normal file
View File

@@ -0,0 +1,41 @@
#define FFCONF_DEF 80286
#define FF_FS_READONLY 0
#define FF_FS_MINIMIZE 0
#define FF_USE_FIND 0
#define FF_USE_MKFS 1
#define FF_USE_FASTSEEK 0
#define FF_USE_EXPAND 0
#define FF_USE_CHMOD 0
#define FF_USE_LABEL 0
#define FF_USE_FORWARD 0
#define FF_USE_STRFUNC 0
#define FF_PRINT_LLI 1
#define FF_PRINT_FLOAT 1
#define FF_STRF_ENCODE 3
#define FF_CODE_PAGE 437
#define FF_USE_LFN 0
#define FF_MAX_LFN 255
#define FF_LFN_UNICODE 0
#define FF_LFN_BUF 255
#define FF_SFN_BUF 12
#define FF_FS_RPATH 0
#define FF_VOLUMES 1
#define FF_STR_VOLUME_ID 0
#define FF_VOLUME_STRS "RAM","NAND","CF","SD","SD2","USB","USB2","USB3"
#define FF_MULTI_PARTITION 0
#define FF_MIN_SS 512
#define FF_MAX_SS 512
#define FF_LBA64 0
#define FF_MIN_GPT 0x10000000
#define FF_USE_TRIM 0
#define FF_FS_TINY 0
#define FF_FS_EXFAT 0
#define FF_FS_NORTC 1
#define FF_NORTC_MON 1
#define FF_NORTC_MDAY 1
#define FF_NORTC_YEAR 2026
#define FF_FS_NOFSINFO 0
#define FF_FS_LOCK 0
#define FF_FS_REENTRANT 0
#define FF_FS_TIMEOUT 1000

50
Lib/dhara/bytes.h Normal file
View 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
View 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
View 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
View 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
View 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, &current, 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
View 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
View 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