增加文件系统
This commit is contained in:
43
Lib/FatFs/diskio.h
Normal file
43
Lib/FatFs/diskio.h
Normal 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
7084
Lib/FatFs/ff.c
Normal file
File diff suppressed because it is too large
Load Diff
429
Lib/FatFs/ff.h
Normal file
429
Lib/FatFs/ff.h
Normal 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
41
Lib/FatFs/ffconf.h
Normal 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
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