android_kernel_samsung_msm8976/fs/sdfat/core_fat.c

1433 lines
36 KiB
C

/*
* Copyright (C) 2012-2013 Samsung Electronics Co., Ltd.
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
* MA 02110-1301, USA.
*/
/************************************************************************/
/* */
/* PROJECT : exFAT & FAT12/16/32 File System */
/* FILE : core_fat.c */
/* PURPOSE : FAT-fs core code for sdFAT */
/* */
/*----------------------------------------------------------------------*/
/* NOTES */
/* */
/* */
/************************************************************************/
#include <linux/version.h>
#include <linux/blkdev.h>
#include <linux/workqueue.h>
#include <linux/kernel.h>
#include <linux/log2.h>
#include "sdfat.h"
#include "core.h"
#include <asm/byteorder.h>
#include <asm/unaligned.h>
/*----------------------------------------------------------------------*/
/* Constant & Macro Definitions */
/*----------------------------------------------------------------------*/
#define MAX_LFN_ORDER (20)
/*
* MAX_EST_AU_SECT should be changed according to 32/64bits.
* On 32bit, 4KB page supports 512 clusters per AU.
* But, on 64bit, 4KB page can handle a half of total list_head of 32bit's.
* Bcause the size of list_head structure on 64bit increases twofold over 32bit.
*/
#if (BITS_PER_LONG == 64)
//#define MAX_EST_AU_SECT (16384) /* upto 8MB */
#define MAX_EST_AU_SECT (32768) /* upto 16MB, used more page for list_head */
#else
#define MAX_EST_AU_SECT (32768) /* upto 16MB */
#endif
/*======================================================================*/
/* Local Function Declarations */
/*======================================================================*/
static s32 __extract_uni_name_from_ext_entry(EXT_DENTRY_T *, u16 *, s32);
/*----------------------------------------------------------------------*/
/* Global Variable Definitions */
/*----------------------------------------------------------------------*/
/*----------------------------------------------------------------------*/
/* Local Variable Definitions */
/*----------------------------------------------------------------------*/
/*======================================================================*/
/* Local Function Definitions */
/*======================================================================*/
static u32 __calc_default_au_size(struct super_block *sb)
{
struct block_device *bdev = sb->s_bdev;
struct gendisk *disk;
struct request_queue *queue;
struct queue_limits *limit;
unsigned int est_au_sect = MAX_EST_AU_SECT;
unsigned int est_au_size = 0;
unsigned int queue_au_size = 0;
sector_t total_sect = 0;
/* we assumed that sector size is 512 bytes */
disk = bdev->bd_disk;
if (!disk)
goto out;
queue = disk->queue;
if (!queue)
goto out;
limit = &queue->limits;
queue_au_size = limit->discard_granularity;
/* estimate function(x) =
* (total_sect / 2) * 512 / 1024
* => (total_sect >> 1) >> 1)
* => (total_sect >> 2)
* => estimated bytes size
*
* ex1) <= 8GB -> 4MB
* ex2) 16GB -> 8MB
* ex3) >= 32GB -> 16MB
*/
total_sect = disk->part0.nr_sects;
est_au_size = total_sect >> 2;
/* au_size assumed that bytes per sector is 512 */
est_au_sect = est_au_size >> 9;
MMSG("DBG1: total_sect(%llu) est_au_size(%u) est_au_sect(%u)\n",
(u64)total_sect, est_au_size, est_au_sect);
if (est_au_sect <= 8192) {
/* 4MB */
est_au_sect = 8192;
} else if (est_au_sect <= 16384) {
/* 8MB */
est_au_sect = 16384;
} else {
/* 8MB or 16MB */
est_au_sect = MAX_EST_AU_SECT;
}
MMSG("DBG2: total_sect(%llu) est_au_size(%u) est_au_sect(%u)\n",
(u64)total_sect, est_au_size, est_au_sect);
if (est_au_size < queue_au_size &&
queue_au_size <= (MAX_EST_AU_SECT << 9)) {
DMSG("use queue_au_size(%u) instead of est_au_size(%u)\n",
queue_au_size, est_au_size);
est_au_sect = queue_au_size >> 9;
}
out:
if (sb->s_blocksize != 512) {
ASSERT(sb->s_blocksize_bits > 9);
sdfat_log_msg(sb, KERN_INFO,
"adjustment est_au_size by logical block size(%lu)",
sb->s_blocksize);
est_au_sect >>= (sb->s_blocksize_bits - 9);
}
sdfat_log_msg(sb, KERN_INFO, "set default AU sectors : %u "
"(queue_au_size : %u KB, disk_size : %llu MB)",
est_au_sect, queue_au_size >> 10, (u64)(total_sect >> 11));
return est_au_sect;
}
/*
* Cluster Management Functions
*/
static s32 fat_alloc_cluster(struct super_block *sb, s32 num_alloc, CHAIN_T *p_chain, int dest)
{
s32 i, num_clusters = 0;
u32 new_clu, last_clu = CLUS_EOF, read_clu;
FS_INFO_T *fsi = &(SDFAT_SB(sb)->fsi);
new_clu = p_chain->dir;
if (IS_CLUS_EOF(new_clu))
new_clu = fsi->clu_srch_ptr;
else if (new_clu >= fsi->num_clusters)
new_clu = 2;
set_sb_dirty(sb);
p_chain->dir = CLUS_EOF;
for (i = CLUS_BASE; i < fsi->num_clusters; i++) {
if (fat_ent_get(sb, new_clu, &read_clu))
return -EIO;
if (IS_CLUS_FREE(read_clu)) {
if (fat_ent_set(sb, new_clu, CLUS_EOF))
return -EIO;
num_clusters++;
if (IS_CLUS_EOF(p_chain->dir)) {
p_chain->dir = new_clu;
} else {
if (fat_ent_set(sb, last_clu, new_clu))
return -EIO;
}
last_clu = new_clu;
if ((--num_alloc) == 0) {
fsi->clu_srch_ptr = new_clu;
if (fsi->used_clusters != (u32) ~0)
fsi->used_clusters += num_clusters;
return num_clusters;
}
}
if ((++new_clu) >= fsi->num_clusters)
new_clu = CLUS_BASE;
}
fsi->clu_srch_ptr = new_clu;
if (fsi->used_clusters != (u32) ~0)
fsi->used_clusters += num_clusters;
return num_clusters;
} /* end of fat_alloc_cluster */
static s32 fat_free_cluster(struct super_block *sb, CHAIN_T *p_chain, s32 do_relse)
{
s32 ret = -EIO;
s32 num_clusters = 0;
u32 clu, prev;
FS_INFO_T *fsi = &(SDFAT_SB(sb)->fsi);
s32 i;
u32 sector;
/* invalid cluster number */
if (IS_CLUS_FREE(p_chain->dir) || IS_CLUS_EOF(p_chain->dir))
return 0;
/* no cluster to truncate */
if (p_chain->size <= 0) {
DMSG("%s: cluster(%u) truncation is not required.",
__func__, p_chain->dir);
return 0;
}
/* check cluster validation */
if ((p_chain->dir < 2) && (p_chain->dir >= fsi->num_clusters)) {
EMSG("%s: invalid start cluster (%u)\n", __func__, p_chain->dir);
sdfat_debug_bug_on(1);
return -EIO;
}
set_sb_dirty(sb);
clu = p_chain->dir;
do {
if (do_relse) {
sector = CLUS_TO_SECT(fsi, clu);
for (i = 0; i < fsi->sect_per_clus; i++) {
if (dcache_release(sb, sector+i) == -EIO)
goto out;
}
}
prev = clu;
if (get_next_clus(sb, &clu))
goto out;
/* FAT validity check */
if (IS_CLUS_FREE(clu)) {
/* GRACEFUL ERROR HANDLING */
/* Broken FAT chain (Already FREE) */
sdfat_fs_error(sb, "%s : deleting FAT entry beyond EOF (clu[%u]->0)", __func__, prev);
goto out;
}
/* Free FAT chain */
if (fat_ent_set(sb, prev, CLUS_FREE))
goto out;
/* Update AMAP if needed */
if (fsi->amap)
amap_release_cluster(sb, prev);
num_clusters++;
} while (!IS_CLUS_EOF(clu));
/* success */
ret = 0;
out:
if (fsi->used_clusters != (u32) ~0)
fsi->used_clusters -= num_clusters;
return ret;
} /* end of fat_free_cluster */
static s32 fat_count_used_clusters(struct super_block *sb, u32* ret_count)
{
s32 i;
u32 clu, count = 0;
FS_INFO_T *fsi = &(SDFAT_SB(sb)->fsi);
for (i = CLUS_BASE; i < fsi->num_clusters; i++) {
if (fat_ent_get(sb, i, &clu))
return -EIO;
if (!IS_CLUS_FREE(clu))
count++;
}
*ret_count = count;
return 0;
} /* end of fat_count_used_clusters */
/*
* Directory Entry Management Functions
*/
static u32 fat_get_entry_type(DENTRY_T *p_entry)
{
DOS_DENTRY_T *ep = (DOS_DENTRY_T *) p_entry;
/* first byte of 32bytes dummy */
if (*(ep->name) == MSDOS_UNUSED)
return TYPE_UNUSED;
/* 0xE5 of Kanji Japanese is replaced to 0x05 */
else if (*(ep->name) == MSDOS_DELETED)
return TYPE_DELETED;
/* 11th byte of 32bytes dummy */
else if ((ep->attr & ATTR_EXTEND_MASK) == ATTR_EXTEND)
return TYPE_EXTEND;
else if (!(ep->attr & (ATTR_SUBDIR | ATTR_VOLUME)))
return TYPE_FILE;
else if ((ep->attr & (ATTR_SUBDIR | ATTR_VOLUME)) == ATTR_SUBDIR)
return TYPE_DIR;
else if ((ep->attr & (ATTR_SUBDIR | ATTR_VOLUME)) == ATTR_VOLUME)
return TYPE_VOLUME;
return TYPE_INVALID;
} /* end of fat_get_entry_type */
static void fat_set_entry_type(DENTRY_T *p_entry, u32 type)
{
DOS_DENTRY_T *ep = (DOS_DENTRY_T *) p_entry;
if (type == TYPE_UNUSED)
*(ep->name) = MSDOS_UNUSED; /* 0x0 */
else if (type == TYPE_DELETED)
*(ep->name) = MSDOS_DELETED; /* 0xE5 */
else if (type == TYPE_EXTEND)
ep->attr = ATTR_EXTEND;
else if (type == TYPE_DIR)
ep->attr = ATTR_SUBDIR;
else if (type == TYPE_FILE)
ep->attr = ATTR_ARCHIVE;
else if (type == TYPE_SYMLINK)
ep->attr = ATTR_ARCHIVE | ATTR_SYMLINK;
} /* end of fat_set_entry_type */
static u32 fat_get_entry_attr(DENTRY_T *p_entry)
{
DOS_DENTRY_T *ep = (DOS_DENTRY_T *) p_entry;
return((u32) ep->attr);
} /* end of fat_get_entry_attr */
static void fat_set_entry_attr(DENTRY_T *p_entry, u32 attr)
{
DOS_DENTRY_T *ep = (DOS_DENTRY_T *) p_entry;
ep->attr = (u8) attr;
} /* end of fat_set_entry_attr */
static u8 fat_get_entry_flag(DENTRY_T *p_entry)
{
return 0x01;
} /* end of fat_get_entry_flag */
static void fat_set_entry_flag(DENTRY_T *p_entry, u8 flags)
{
} /* end of fat_set_entry_flag */
static u32 fat_get_entry_clu0(DENTRY_T *p_entry)
{
DOS_DENTRY_T *ep = (DOS_DENTRY_T *) p_entry;
/* FIXME : is ok? */
return(((u32)(le16_to_cpu(ep->start_clu_hi)) << 16) | le16_to_cpu(ep->start_clu_lo));
} /* end of fat_get_entry_clu0 */
static void fat_set_entry_clu0(DENTRY_T *p_entry, u32 start_clu)
{
DOS_DENTRY_T *ep = (DOS_DENTRY_T *) p_entry;
ep->start_clu_lo = cpu_to_le16(CLUSTER_16(start_clu));
ep->start_clu_hi = cpu_to_le16(CLUSTER_16(start_clu >> 16));
} /* end of fat_set_entry_clu0 */
static u64 fat_get_entry_size(DENTRY_T *p_entry)
{
DOS_DENTRY_T *ep = (DOS_DENTRY_T *) p_entry;
return((u64) le32_to_cpu(ep->size));
} /* end of fat_get_entry_size */
static void fat_set_entry_size(DENTRY_T *p_entry, u64 size)
{
DOS_DENTRY_T *ep = (DOS_DENTRY_T *) p_entry;
ep->size = cpu_to_le32((u32)size);
} /* end of fat_set_entry_size */
static void fat_get_entry_time(DENTRY_T *p_entry, TIMESTAMP_T *tp, u8 mode)
{
u16 t = 0x00, d = 0x21;
DOS_DENTRY_T *ep = (DOS_DENTRY_T *) p_entry;
switch (mode) {
case TM_CREATE:
t = le16_to_cpu(ep->create_time);
d = le16_to_cpu(ep->create_date);
break;
case TM_MODIFY:
t = le16_to_cpu(ep->modify_time);
d = le16_to_cpu(ep->modify_date);
break;
}
tp->sec = (t & 0x001F) << 1;
tp->min = (t >> 5) & 0x003F;
tp->hour = (t >> 11);
tp->day = (d & 0x001F);
tp->mon = (d >> 5) & 0x000F;
tp->year = (d >> 9);
} /* end of fat_get_entry_time */
static void fat_set_entry_time(DENTRY_T *p_entry, TIMESTAMP_T *tp, u8 mode)
{
u16 t, d;
DOS_DENTRY_T *ep = (DOS_DENTRY_T *) p_entry;
t = (tp->hour << 11) | (tp->min << 5) | (tp->sec >> 1);
d = (tp->year << 9) | (tp->mon << 5) | tp->day;
switch (mode) {
case TM_CREATE:
ep->create_time = cpu_to_le16(t);
ep->create_date = cpu_to_le16(d);
break;
case TM_MODIFY:
ep->modify_time = cpu_to_le16(t);
ep->modify_date = cpu_to_le16(d);
break;
}
} /* end of fat_set_entry_time */
static void __init_dos_entry(struct super_block *sb, DOS_DENTRY_T *ep, u32 type, u32 start_clu)
{
TIMESTAMP_T tm, *tp;
fat_set_entry_type((DENTRY_T *) ep, type);
ep->start_clu_lo = cpu_to_le16(CLUSTER_16(start_clu));
ep->start_clu_hi = cpu_to_le16(CLUSTER_16(start_clu >> 16));
ep->size = 0;
tp = tm_now(SDFAT_SB(sb), &tm);
fat_set_entry_time((DENTRY_T *) ep, tp, TM_CREATE);
fat_set_entry_time((DENTRY_T *) ep, tp, TM_MODIFY);
ep->access_date = 0;
ep->create_time_ms = 0;
} /* end of __init_dos_entry */
static void __init_ext_entry(EXT_DENTRY_T *ep, s32 order, u8 chksum, u16 *uniname)
{
s32 i;
u8 end = false;
fat_set_entry_type((DENTRY_T *) ep, TYPE_EXTEND);
ep->order = (u8) order;
ep->sysid = 0;
ep->checksum = chksum;
ep->start_clu = 0;
/* unaligned name */
for (i = 0; i < 5; i++) {
if (!end) {
put_unaligned_le16(*uniname, &(ep->unicode_0_4[i<<1]));
if (*uniname == 0x0)
end = true;
else
uniname++;
} else {
put_unaligned_le16(0xFFFF, &(ep->unicode_0_4[i<<1]));
}
}
/* aligned name */
for (i = 0; i < 6; i ++) {
if (!end) {
ep->unicode_5_10[i] = cpu_to_le16(*uniname);
if (*uniname == 0x0)
end = true;
else
uniname++;
} else {
ep->unicode_5_10[i] = cpu_to_le16(0xFFFF);
}
}
/* aligned name */
for (i = 0; i < 2; i++) {
if (!end) {
ep->unicode_11_12[i] = cpu_to_le16(*uniname);
if (*uniname == 0x0)
end = true;
else
uniname++;
} else {
ep->unicode_11_12[i] = cpu_to_le16(0xFFFF);
}
}
} /* end of __init_ext_entry */
static s32 fat_init_dir_entry(struct super_block *sb, CHAIN_T *p_dir, s32 entry, u32 type,
u32 start_clu, u64 size)
{
u32 sector;
DOS_DENTRY_T *dos_ep;
dos_ep = (DOS_DENTRY_T *) get_dentry_in_dir(sb, p_dir, entry, &sector);
if (!dos_ep)
return -EIO;
__init_dos_entry(sb, dos_ep, type, start_clu);
dcache_modify(sb, sector);
return 0;
} /* end of fat_init_dir_entry */
static s32 fat_init_ext_entry(struct super_block *sb, CHAIN_T *p_dir, s32 entry, s32 num_entries,
UNI_NAME_T *p_uniname, DOS_NAME_T *p_dosname)
{
s32 i;
u32 sector;
u8 chksum;
u16 *uniname = p_uniname->name;
DOS_DENTRY_T *dos_ep;
EXT_DENTRY_T *ext_ep;
dos_ep = (DOS_DENTRY_T *) get_dentry_in_dir(sb, p_dir, entry, &sector);
if (!dos_ep)
return -EIO;
dos_ep->lcase = p_dosname->name_case;
memcpy(dos_ep->name, p_dosname->name, DOS_NAME_LENGTH);
if (dcache_modify(sb, sector))
return -EIO;
if ((--num_entries) > 0) {
chksum = calc_chksum_1byte((void *) dos_ep->name, DOS_NAME_LENGTH, 0);
for (i = 1; i < num_entries; i++) {
ext_ep = (EXT_DENTRY_T *) get_dentry_in_dir(sb, p_dir, entry-i, &sector);
if (!ext_ep)
return -EIO;
__init_ext_entry(ext_ep, i, chksum, uniname);
if (dcache_modify(sb, sector))
return -EIO;
uniname += 13;
}
ext_ep = (EXT_DENTRY_T *) get_dentry_in_dir(sb, p_dir, entry-i, &sector);
if (!ext_ep)
return -EIO;
__init_ext_entry(ext_ep, i+MSDOS_LAST_LFN, chksum, uniname);
if (dcache_modify(sb, sector))
return -EIO;
}
return 0;
} /* end of fat_init_ext_entry */
static s32 fat_delete_dir_entry(struct super_block *sb, CHAIN_T *p_dir, s32 entry, s32 order, s32 num_entries)
{
s32 i;
u32 sector;
DENTRY_T *ep;
for (i = num_entries-1; i >= order; i--) {
ep = get_dentry_in_dir(sb, p_dir, entry-i, &sector);
if (!ep)
return -EIO;
fat_set_entry_type(ep, TYPE_DELETED);
if (dcache_modify(sb, sector))
return -EIO;
}
return 0;
}
/* return values of fat_find_dir_entry()
* >= 0 : return dir entiry position with the name in dir
* -EEXIST : (root dir, ".") it is the root dir itself
* -ENOENT : entry with the name does not exist
* -EIO : I/O error
*/
static inline s32 __get_dentries_per_clu(FS_INFO_T *fsi, s32 clu)
{
if (IS_CLUS_FREE(clu)) /* FAT16 root_dir */
return fsi->dentries_in_root;
return fsi->dentries_per_clu;
}
static s32 fat_find_dir_entry(struct super_block *sb, FILE_ID_T *fid, CHAIN_T *p_dir, UNI_NAME_T *p_uniname, s32 num_entries, DOS_NAME_T *p_dosname, u32 type)
{
s32 i, rewind = 0, dentry = 0, end_eidx = 0;
s32 chksum = 0, lfn_ord = 0, lfn_len = 0;
s32 dentries_per_clu, num_empty = 0;
u32 entry_type;
u16 entry_uniname[14], *uniname = NULL;
CHAIN_T clu;
DENTRY_T *ep;
HINT_T *hint_stat = &fid->hint_stat;
HINT_FEMP_T candi_empty;
FS_INFO_T *fsi = &(SDFAT_SB(sb)->fsi);
/*
* REMARK:
* DOT and DOTDOT are handled by VFS layer
*/
dentries_per_clu = __get_dentries_per_clu(fsi, p_dir->dir);
clu.dir = p_dir->dir;
clu.flags = p_dir->flags;
if (hint_stat->eidx) {
clu.dir = hint_stat->clu;
dentry = hint_stat->eidx;
end_eidx = dentry;
}
candi_empty.eidx = -1;
MMSG("lookup dir= %s\n", p_dosname->name);
rewind:
while (!IS_CLUS_EOF(clu.dir)) {
i = dentry % dentries_per_clu;
for (; i < dentries_per_clu; i++, dentry++) {
if (rewind && (dentry == end_eidx))
goto not_found;
ep = get_dentry_in_dir(sb, &clu, i, NULL);
if (!ep)
return -EIO;
entry_type = fat_get_entry_type(ep);
/*
* Most directory entries have long name,
* So, we check extend directory entry first.
*/
if (entry_type == TYPE_EXTEND) {
EXT_DENTRY_T *ext_ep = (EXT_DENTRY_T *)ep;
u32 cur_ord = (u32)ext_ep->order;
u32 cur_chksum = (s32)ext_ep->checksum;
s32 len = 13;
u16 unichar;
num_empty = 0;
candi_empty.eidx = -1;
/* check whether new lfn or not */
if (cur_ord & MSDOS_LAST_LFN) {
cur_ord &= ~(MSDOS_LAST_LFN);
chksum = cur_chksum;
len = (13 * (cur_ord-1));
uniname = (p_uniname->name + len);
lfn_ord = cur_ord + 1;
lfn_len = 0;
/* check minimum name length */
if (cur_ord &&
(len > p_uniname->name_len)) {
/* MISMATCHED NAME LENGTH */
lfn_len = -1;
}
len = 0;
}
/* invalid lfn order */
if ( !cur_ord || (cur_ord > MAX_LFN_ORDER) ||
((cur_ord + 1) != lfn_ord) )
goto reset_dentry_set;
/* check checksum of directory entry set */
if (cur_chksum != chksum)
goto reset_dentry_set;
/* update order for next dentry */
lfn_ord = cur_ord;
/* check whether mismatched lfn or not */
if (lfn_len == -1) {
/* MISMATCHED LFN DENTRY SET */
continue;
}
if(!uniname) {
sdfat_fs_error(sb,
"%s : abnormal dentry "
"(start_clu[%u], "
"idx[%u])", __func__,
p_dir->dir, dentry);
sdfat_debug_bug_on(1);
return -EIO;
}
/* update position of name buffer */
uniname -= len;
/* get utf16 characters saved on this entry */
len = __extract_uni_name_from_ext_entry(ext_ep, entry_uniname, lfn_ord);
/* replace last char to null */
unichar = *(uniname+len);
*(uniname+len) = (u16)0x0;
/* uniname ext_dentry unit compare repeatdly */
if (nls_cmp_uniname(sb, uniname, entry_uniname)) {
/* DO HANDLE WRONG NAME */
lfn_len = -1;
} else {
/* add matched chars length */
lfn_len += len;
}
/* restore previous character */
*(uniname+len) = unichar;
/* jump to check next dentry */
continue;
} else if ((entry_type == TYPE_FILE) || (entry_type == TYPE_DIR)) {
DOS_DENTRY_T *dos_ep = (DOS_DENTRY_T *)ep;
u32 cur_chksum = (s32)calc_chksum_1byte(
(void *) dos_ep->name,
DOS_NAME_LENGTH, 0);
num_empty = 0;
candi_empty.eidx = -1;
MMSG("checking dir= %c%c%c%c%c%c%c%c%c%c%c\n",
dos_ep->name[0], dos_ep->name[1],
dos_ep->name[2], dos_ep->name[3],
dos_ep->name[4], dos_ep->name[5],
dos_ep->name[6], dos_ep->name[7],
dos_ep->name[8], dos_ep->name[9],
dos_ep->name[10]);
/*
* if there is no valid long filename,
* we should check short filename.
*/
if (!lfn_len || (cur_chksum != chksum)) {
/* check shortname */
if ( (p_dosname->name[0] != '\0') &&
!nls_cmp_sfn(sb,
p_dosname->name,
dos_ep->name) ) {
goto found;
}
/* check name length */
} else if ( (lfn_len > 0) &&
((s32)p_uniname->name_len ==
lfn_len) ) {
goto found;
}
/* DO HANDLE MISMATCHED SFN, FALL THROUGH */
} else if ((entry_type == TYPE_UNUSED) || (entry_type == TYPE_DELETED)) {
num_empty++;
if (candi_empty.eidx == -1) {
if (num_empty == 1) {
candi_empty.cur.dir = clu.dir;
candi_empty.cur.size = clu.size;
candi_empty.cur.flags = clu.flags;
}
if (num_empty >= num_entries) {
candi_empty.eidx = dentry - (num_empty - 1);
ASSERT(0 <= candi_empty.eidx);
candi_empty.count = num_empty;
if ((fid->hint_femp.eidx == -1) ||
(candi_empty.eidx <= fid->hint_femp.eidx)) {
memcpy(&fid->hint_femp,
&candi_empty,
sizeof(HINT_FEMP_T));
}
}
}
if (entry_type == TYPE_UNUSED)
goto not_found;
/* FALL THROUGH */
}
reset_dentry_set:
/* TYPE_DELETED, TYPE_VOLUME OR MISMATCHED SFN */
lfn_ord = 0;
lfn_len = 0;
chksum = 0;
}
if (IS_CLUS_FREE(p_dir->dir))
break; /* FAT16 root_dir */
if (get_next_clus_safe(sb, &clu.dir))
return -EIO;
}
not_found:
/* we started at not 0 index,so we should try to find target
* from 0 index to the index we started at.
*/
if (!rewind && end_eidx) {
rewind = 1;
dentry = 0;
clu.dir = p_dir->dir;
/* reset dentry set */
lfn_ord = 0;
lfn_len = 0;
chksum = 0;
/* reset empty hint_*/
num_empty = 0;
candi_empty.eidx = -1;
goto rewind;
}
/* initialized hint_stat */
hint_stat->clu = p_dir->dir;
hint_stat->eidx = 0;
return -ENOENT;
found:
/* next dentry we'll find is out of this cluster */
if (!((dentry + 1) % dentries_per_clu)) {
int ret = 0;
/* FAT16 root_dir */
if (IS_CLUS_FREE(p_dir->dir))
clu.dir = CLUS_EOF;
else
ret = get_next_clus_safe(sb, &clu.dir);
if (ret || IS_CLUS_EOF(clu.dir)) {
/* just initialized hint_stat */
hint_stat->clu = p_dir->dir;
hint_stat->eidx = 0;
return dentry;
}
}
hint_stat->clu = clu.dir;
hint_stat->eidx = dentry + 1;
return dentry;
} /* end of fat_find_dir_entry */
/* returns -EIO on error */
static s32 fat_count_ext_entries(struct super_block *sb, CHAIN_T *p_dir, s32 entry, DENTRY_T *p_entry)
{
s32 count = 0;
u8 chksum;
DOS_DENTRY_T *dos_ep = (DOS_DENTRY_T *) p_entry;
EXT_DENTRY_T *ext_ep;
chksum = calc_chksum_1byte((void *) dos_ep->name, DOS_NAME_LENGTH, 0);
for (entry--; entry >= 0; entry--) {
ext_ep = (EXT_DENTRY_T*)get_dentry_in_dir(sb,p_dir,entry,NULL);
if (!ext_ep)
return -EIO;
if ( (fat_get_entry_type((DENTRY_T*)ext_ep) == TYPE_EXTEND) &&
(ext_ep->checksum == chksum) ) {
count++;
if (ext_ep->order > MSDOS_LAST_LFN)
return count;
} else {
return count;
}
}
return count;
}
/*
* Name Conversion Functions
*/
static s32 __extract_uni_name_from_ext_entry(EXT_DENTRY_T *ep, u16 *uniname, s32 order)
{
s32 i, len = 0;
for (i = 0; i < 5; i++) {
*uniname = get_unaligned_le16(&(ep->unicode_0_4[i<<1]));
if (*uniname == 0x0)
return(len);
uniname++;
len++;
}
if (order < 20) {
for (i = 0; i < 6; i++) {
/* FIXME : unaligned? */
*uniname = le16_to_cpu(ep->unicode_5_10[i]);
if (*uniname == 0x0)
return(len);
uniname++;
len++;
}
} else {
for (i = 0; i < 4; i++) {
/* FIXME : unaligned? */
*uniname = le16_to_cpu(ep->unicode_5_10[i]);
if (*uniname == 0x0)
return(len);
uniname++;
len++;
}
*uniname = 0x0; /* uniname[MAX_NAME_LENGTH] */
return(len);
}
for (i = 0; i < 2; i++) {
/* FIXME : unaligned? */
*uniname = le16_to_cpu(ep->unicode_11_12[i]);
if (*uniname == 0x0)
return(len);
uniname++;
len++;
}
*uniname = 0x0;
return(len);
} /* end of __extract_uni_name_from_ext_entry */
static void fat_get_uniname_from_ext_entry(struct super_block *sb, CHAIN_T *p_dir, s32 entry, u16 *uniname)
{
u32 i;
u16 *name = uniname;
u32 chksum;
DOS_DENTRY_T *dos_ep =
(DOS_DENTRY_T *)get_dentry_in_dir(sb, p_dir, entry, NULL);
if (unlikely(!dos_ep))
goto invalid_lfn;
chksum = (u32)calc_chksum_1byte(
(void *) dos_ep->name,
DOS_NAME_LENGTH, 0);
for (entry--, i = 1; entry >= 0; entry--, i++) {
EXT_DENTRY_T *ep;
ep = (EXT_DENTRY_T *)get_dentry_in_dir(sb, p_dir, entry, NULL);
if (!ep)
goto invalid_lfn;
if (fat_get_entry_type((DENTRY_T *) ep) != TYPE_EXTEND)
goto invalid_lfn;
if (chksum != (u32)ep->checksum)
goto invalid_lfn;
if (i != (u32)(ep->order & ~(MSDOS_LAST_LFN)))
goto invalid_lfn;
__extract_uni_name_from_ext_entry(ep, name, (s32)i);
if (ep->order & MSDOS_LAST_LFN)
return;
name += 13;
}
invalid_lfn:
*uniname = (u16)0x0;
return;
} /* end of fat_get_uniname_from_ext_entry */
/* Find if the shortname exists
and check if there are free entries
*/
static s32 __fat_find_shortname_entry(struct super_block *sb, CHAIN_T *p_dir, u8 *p_dosname, s32 *offset, __attribute__((unused))int n_entry_needed)
{
u32 type;
s32 i, dentry = 0;
s32 dentries_per_clu;
DENTRY_T *ep = NULL;
DOS_DENTRY_T *dos_ep = NULL;
CHAIN_T clu = *p_dir;
FS_INFO_T *fsi = &(SDFAT_SB(sb)->fsi);
if (offset)
*offset = -1;
if (IS_CLUS_FREE(clu.dir)) /* FAT16 root_dir */
dentries_per_clu = fsi->dentries_in_root;
else
dentries_per_clu = fsi->dentries_per_clu;
while(!IS_CLUS_EOF(clu.dir)) {
for (i = 0; i < dentries_per_clu; i++, dentry++) {
ep = get_dentry_in_dir(sb, &clu, i, NULL);
if (!ep)
return -EIO;
type = fat_get_entry_type(ep);
if ((type == TYPE_FILE) || (type == TYPE_DIR)) {
dos_ep = (DOS_DENTRY_T *)ep;
if (!nls_cmp_sfn(sb, p_dosname, dos_ep->name)) {
if (offset)
*offset = dentry;
return 0;
}
}
}
/* fat12/16 root dir */
if (IS_CLUS_FREE(clu.dir))
break;
if (get_next_clus_safe(sb, &clu.dir))
return -EIO;
}
return -ENOENT;
}
#ifdef CONFIG_SDFAT_FAT32_SHORTNAME_SEQ
static void __fat_attach_count_to_dos_name(u8 *dosname, s32 count)
{
s32 i, j, length;
s8 str_count[6];
snprintf(str_count, sizeof(str_count), "~%d", count);
length = strlen(str_count);
i = j = 0;
while (j <= (8 - length)) {
i = j;
if (dosname[j] == ' ')
break;
if (dosname[j] & 0x80)
j += 2;
else
j++;
}
for (j = 0; j < length; i++, j++)
dosname[i] = (u8) str_count[j];
if (i == 7)
dosname[7] = ' ';
} /* end of __fat_attach_count_to_dos_name */
#endif
s32 fat_generate_dos_name_new(struct super_block *sb, CHAIN_T *p_dir, DOS_NAME_T *p_dosname, s32 n_entry_needed)
{
s32 i;
s32 baselen, err;
u8 work[DOS_NAME_LENGTH], buf[5];
u8 tail;
baselen = 8;
memset(work, ' ', DOS_NAME_LENGTH);
memcpy(work, p_dosname->name, DOS_NAME_LENGTH);
while(baselen && (work[--baselen] == ' '));
if (baselen > 6)
baselen = 6;
BUG_ON(baselen < 0);
#ifdef CONFIG_SDFAT_FAT32_SHORTNAME_SEQ
/* example) namei_exfat.c -> NAMEI_~1 - NAMEI_~9 */
work[baselen] = '~';
for (i = 1; i < 10; i++) {
// '0' + i = 1 ~ 9 ASCII
work[baselen + 1] = '0' + i;
err = __fat_find_shortname_entry(sb, p_dir, work, NULL, n_entry_needed);
if (err == -ENOENT) {
/* void return */
__fat_attach_count_to_dos_name(p_dosname->name, i);
return 0;
}
/* any other error */
if (err)
return err;
}
#endif
i = jiffies;
tail = (jiffies >> 16) & 0x7;
if (baselen > 2)
baselen = 2;
BUG_ON(baselen < 0);
work[baselen + 4] = '~';
// 1 ~ 8 ASCII
work[baselen + 5] = '1' + tail;
while (1) {
snprintf(buf, sizeof(buf), "%04X", i & 0xffff);
memcpy(&work[baselen], buf, 4);
err = __fat_find_shortname_entry(sb, p_dir, work, NULL, n_entry_needed);
if (err == -ENOENT) {
memcpy(p_dosname->name, work, DOS_NAME_LENGTH);
break;
}
/* any other error */
if (err)
return err;
i -= 11;
}
return 0;
} /* end of generate_dos_name_new */
static s32 fat_calc_num_entries(UNI_NAME_T *p_uniname)
{
s32 len;
len = p_uniname->name_len;
if (len == 0)
return 0;
/* 1 dos name entry + extended entries */
return((len-1) / 13 + 2);
} /* end of calc_num_enties */
/*
* File Operation Functions
*/
static FS_FUNC_T fat_fs_func = {
.alloc_cluster = fat_alloc_cluster,
.free_cluster = fat_free_cluster,
.count_used_clusters = fat_count_used_clusters,
.init_dir_entry = fat_init_dir_entry,
.init_ext_entry = fat_init_ext_entry,
.find_dir_entry = fat_find_dir_entry,
.delete_dir_entry = fat_delete_dir_entry,
.get_uniname_from_ext_entry = fat_get_uniname_from_ext_entry,
.count_ext_entries = fat_count_ext_entries,
.calc_num_entries = fat_calc_num_entries,
.get_entry_type = fat_get_entry_type,
.set_entry_type = fat_set_entry_type,
.get_entry_attr = fat_get_entry_attr,
.set_entry_attr = fat_set_entry_attr,
.get_entry_flag = fat_get_entry_flag,
.set_entry_flag = fat_set_entry_flag,
.get_entry_clu0 = fat_get_entry_clu0,
.set_entry_clu0 = fat_set_entry_clu0,
.get_entry_size = fat_get_entry_size,
.set_entry_size = fat_set_entry_size,
.get_entry_time = fat_get_entry_time,
.set_entry_time = fat_set_entry_time,
};
static FS_FUNC_T amap_fat_fs_func = {
.alloc_cluster = amap_fat_alloc_cluster,
.free_cluster = fat_free_cluster,
.count_used_clusters = fat_count_used_clusters,
.init_dir_entry = fat_init_dir_entry,
.init_ext_entry = fat_init_ext_entry,
.find_dir_entry = fat_find_dir_entry,
.delete_dir_entry = fat_delete_dir_entry,
.get_uniname_from_ext_entry = fat_get_uniname_from_ext_entry,
.count_ext_entries = fat_count_ext_entries,
.calc_num_entries = fat_calc_num_entries,
.get_entry_type = fat_get_entry_type,
.set_entry_type = fat_set_entry_type,
.get_entry_attr = fat_get_entry_attr,
.set_entry_attr = fat_set_entry_attr,
.get_entry_flag = fat_get_entry_flag,
.set_entry_flag = fat_set_entry_flag,
.get_entry_clu0 = fat_get_entry_clu0,
.set_entry_clu0 = fat_set_entry_clu0,
.get_entry_size = fat_get_entry_size,
.set_entry_size = fat_set_entry_size,
.get_entry_time = fat_get_entry_time,
.set_entry_time = fat_set_entry_time,
.get_au_stat = amap_get_au_stat,
};
s32 mount_fat16(struct super_block *sb, pbr_t *p_pbr)
{
s32 num_reserved, num_root_sectors;
bpb16_t *p_bpb = &(p_pbr->bpb.f16);
FS_INFO_T *fsi = &(SDFAT_SB(sb)->fsi);
if (!p_bpb->num_fats) {
sdfat_msg(sb, KERN_ERR, "bogus number of FAT structure");
return -EINVAL;
}
num_root_sectors = get_unaligned_le16(p_bpb->num_root_entries) << DENTRY_SIZE_BITS;
num_root_sectors = ((num_root_sectors-1) >> sb->s_blocksize_bits) + 1;
fsi->sect_per_clus = p_bpb->sect_per_clus;
fsi->sect_per_clus_bits = ilog2(p_bpb->sect_per_clus);
fsi->cluster_size_bits = fsi->sect_per_clus_bits + sb->s_blocksize_bits;
fsi->cluster_size = 1 << fsi->cluster_size_bits;
fsi->num_FAT_sectors = le16_to_cpu(p_bpb->num_fat_sectors);
fsi->FAT1_start_sector = le16_to_cpu(p_bpb->num_reserved);
if (p_bpb->num_fats == 1)
fsi->FAT2_start_sector = fsi->FAT1_start_sector;
else
fsi->FAT2_start_sector = fsi->FAT1_start_sector + fsi->num_FAT_sectors;
fsi->root_start_sector = fsi->FAT2_start_sector + fsi->num_FAT_sectors;
fsi->data_start_sector = fsi->root_start_sector + num_root_sectors;
fsi->num_sectors = get_unaligned_le16(p_bpb->num_sectors);
if (!fsi->num_sectors)
fsi->num_sectors = le32_to_cpu(p_bpb->num_huge_sectors);
if (!fsi->num_sectors) {
sdfat_msg(sb, KERN_ERR, "bogus number of total sector count");
return -EINVAL;
}
num_reserved = fsi->data_start_sector;
fsi->num_clusters = ((fsi->num_sectors - num_reserved) >> fsi->sect_per_clus_bits) + CLUS_BASE;
/* because the cluster index starts with 2 */
fsi->vol_type = FAT16;
if (fsi->num_clusters < FAT12_THRESHOLD)
fsi->vol_type = FAT12;
fsi->vol_id = get_unaligned_le32(p_bpb->vol_serial);
fsi->root_dir = 0;
fsi->dentries_in_root = get_unaligned_le16(p_bpb->num_root_entries);
if (!fsi->dentries_in_root) {
sdfat_msg(sb, KERN_ERR, "bogus number of max dentry count "
"of the root directory");
return -EINVAL;
}
fsi->dentries_per_clu = 1 << (fsi->cluster_size_bits - DENTRY_SIZE_BITS);
fsi->vol_flag = VOL_CLEAN;
fsi->clu_srch_ptr = 2;
fsi->used_clusters = (u32) ~0;
fsi->fs_func = &fat_fs_func;
fat_ent_ops_init(sb);
if (p_bpb->state & FAT_VOL_DIRTY) {
fsi->vol_flag |= VOL_DIRTY;
sdfat_log_msg(sb, KERN_WARNING, "Volume was not properly "
"unmounted. Some data may be corrupt. "
"Please run fsck.");
}
return 0;
} /* end of mount_fat16 */
static sector_t __calc_hidden_sect(struct super_block *sb)
{
struct block_device *bdev = sb->s_bdev;
sector_t hidden = 0;
if (!bdev)
goto out;
hidden = bdev->bd_part->start_sect;
/* a disk device, not a partition */
if (!hidden) {
ASSERT(bdev == bdev->bd_contains);
ASSERT(!bdev->bd_part->partno);
goto out;
}
if (sb->s_blocksize_bits != 9) {
ASSERT(sb->s_blocksize_bits > 9);
hidden >>= (sb->s_blocksize_bits - 9);
}
out:
sdfat_log_msg(sb, KERN_INFO, "start_sect of partition : %lld",
(s64)hidden);
return hidden;
}
s32 mount_fat32(struct super_block *sb, pbr_t *p_pbr)
{
s32 num_reserved;
pbr32_t *p_bpb = (pbr32_t *)p_pbr;
FS_INFO_T *fsi = &(SDFAT_SB(sb)->fsi);
if (!p_bpb->bpb.num_fats) {
sdfat_msg(sb, KERN_ERR, "bogus number of FAT structure");
return -EINVAL;
}
fsi->sect_per_clus = p_bpb->bpb.sect_per_clus;
fsi->sect_per_clus_bits = ilog2(p_bpb->bpb.sect_per_clus);
fsi->cluster_size_bits = fsi->sect_per_clus_bits + sb->s_blocksize_bits;
fsi->cluster_size = 1 << fsi->cluster_size_bits;
fsi->num_FAT_sectors = le32_to_cpu(p_bpb->bpb.num_fat32_sectors);
fsi->FAT1_start_sector = le16_to_cpu(p_bpb->bpb.num_reserved);
if (p_bpb->bpb.num_fats == 1)
fsi->FAT2_start_sector = fsi->FAT1_start_sector;
else
fsi->FAT2_start_sector = fsi->FAT1_start_sector + fsi->num_FAT_sectors;
fsi->root_start_sector = fsi->FAT2_start_sector + fsi->num_FAT_sectors;
fsi->data_start_sector = fsi->root_start_sector;
/* SPEC violation for compatibility */
fsi->num_sectors = get_unaligned_le16(p_bpb->bpb.num_sectors);
if (!fsi->num_sectors)
fsi->num_sectors = le32_to_cpu(p_bpb->bpb.num_huge_sectors);
/* 2nd check */
if (!fsi->num_sectors) {
sdfat_msg(sb, KERN_ERR, "bogus number of total sector count");
return -EINVAL;
}
num_reserved = fsi->data_start_sector;
fsi->num_clusters = ((fsi->num_sectors-num_reserved) >> fsi->sect_per_clus_bits) + 2;
/* because the cluster index starts with 2 */
fsi->vol_type = FAT32;
fsi->vol_id = get_unaligned_le32(p_bpb->bsx.vol_serial);
fsi->root_dir = le32_to_cpu(p_bpb->bpb.root_cluster);
fsi->dentries_in_root = 0;
fsi->dentries_per_clu = 1 << (fsi->cluster_size_bits - DENTRY_SIZE_BITS);
fsi->vol_flag = VOL_CLEAN;
fsi->clu_srch_ptr = 2;
fsi->used_clusters = (u32) ~0;
fsi->fs_func = &fat_fs_func;
/* Delayed / smart allocation related init */
fsi->reserved_clusters = 0;
/* Should be initialized before calling amap_create() */
fat_ent_ops_init(sb);
/* AU Map Creation */
if (SDFAT_SB(sb)->options.improved_allocation & SDFAT_ALLOC_SMART) {
u32 hidden_sectors = le32_to_cpu(p_bpb->bpb.num_hid_sectors);
u32 calc_hid_sect = 0;
int ret;
/* calculate hidden sector size */
calc_hid_sect = __calc_hidden_sect(sb);
if (calc_hid_sect != hidden_sectors) {
sdfat_log_msg(sb, KERN_WARNING, "abnormal hidden "
"sector : bpb(%u) != ondisk(%u)",
hidden_sectors, calc_hid_sect);
if (SDFAT_SB(sb)->options.adj_hidsect) {
sdfat_log_msg(sb, KERN_INFO,
"adjustment hidden sector : "
"bpb(%u) -> ondisk(%u)",
hidden_sectors, calc_hid_sect);
hidden_sectors = calc_hid_sect;
}
}
SDFAT_SB(sb)->options.amap_opt.misaligned_sect = hidden_sectors;
/* calculate AU size if it's not set */
if (!SDFAT_SB(sb)->options.amap_opt.sect_per_au) {
SDFAT_SB(sb)->options.amap_opt.sect_per_au =
__calc_default_au_size(sb);
}
ret = amap_create(sb,
SDFAT_SB(sb)->options.amap_opt.pack_ratio,
SDFAT_SB(sb)->options.amap_opt.sect_per_au,
SDFAT_SB(sb)->options.amap_opt.misaligned_sect);
if (ret) {
sdfat_log_msg(sb, KERN_WARNING, "failed to create AMAP."
" disabling smart allocation. (err:%d)", ret);
SDFAT_SB(sb)->options.improved_allocation &= ~(SDFAT_ALLOC_SMART);
} else {
fsi->fs_func = &amap_fat_fs_func;
}
}
/* Check dependency of mount options */
if (SDFAT_SB(sb)->options.improved_allocation !=
(SDFAT_ALLOC_DELAY | SDFAT_ALLOC_SMART) ) {
sdfat_log_msg(sb, KERN_INFO, "disabling defragmentation because"
" smart, delay options are disabled");
SDFAT_SB(sb)->options.defrag = 0;
}
if (p_bpb->bsx.state & FAT_VOL_DIRTY) {
fsi->vol_flag |= VOL_DIRTY;
sdfat_log_msg(sb, KERN_WARNING, "Volume was not properly "
"unmounted. Some data may be corrupt. "
"Please run fsck.");
}
return 0;
} /* end of mount_fat32 */
/* end of core_fat.c */