kernel_samsung_a34x-permissive/fs/sdfat/dfr.c
2024-04-28 15:49:01 +02:00

1373 lines
34 KiB
C
Executable file

/*
* 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, see <http://www.gnu.org/licenses/>.
*/
/************************************************************************/
/* */
/* @PROJECT : exFAT & FAT12/16/32 File System */
/* @FILE : dfr.c */
/* @PURPOSE : Defragmentation support for SDFAT32 */
/* */
/*----------------------------------------------------------------------*/
/* NOTES */
/* */
/* */
/************************************************************************/
#include <linux/version.h>
#include <linux/list.h>
#include <linux/blkdev.h>
#include "sdfat.h"
#include "core.h"
#include "amap_smart.h"
#ifdef CONFIG_SDFAT_DFR
/**
* @fn defrag_get_info
* @brief get HW params for defrag daemon
* @return 0 on success, -errno otherwise
* @param sb super block
* @param arg defrag info arguments
* @remark protected by super_block
*/
int
defrag_get_info(
IN struct super_block *sb,
OUT struct defrag_info_arg *arg)
{
FS_INFO_T *fsi = &(SDFAT_SB(sb)->fsi);
AMAP_T *amap = SDFAT_SB(sb)->fsi.amap;
if (!arg)
return -EINVAL;
arg->sec_sz = sb->s_blocksize;
arg->clus_sz = fsi->cluster_size;
arg->total_sec = fsi->num_sectors;
arg->fat_offset_sec = fsi->FAT1_start_sector;
arg->fat_sz_sec = fsi->num_FAT_sectors;
arg->n_fat = (fsi->FAT1_start_sector == fsi->FAT2_start_sector) ? 1 : 2;
arg->sec_per_au = amap->option.au_size;
arg->hidden_sectors = amap->option.au_align_factor % amap->option.au_size;
return 0;
}
static int
__defrag_scan_dir(
IN struct super_block *sb,
IN DOS_DENTRY_T *dos_ep,
IN loff_t i_pos,
OUT struct defrag_trav_arg *arg)
{
FS_INFO_T *fsi = NULL;
UNI_NAME_T uniname;
unsigned int type = 0, start_clus = 0;
int err = -EPERM;
/* Check params */
ERR_HANDLE2((!sb || !dos_ep || !i_pos || !arg), err, -EINVAL);
fsi = &(SDFAT_SB(sb)->fsi);
/* Get given entry's type */
type = fsi->fs_func->get_entry_type((DENTRY_T *) dos_ep);
/* Check dos_ep */
if (!strncmp(dos_ep->name, DOS_CUR_DIR_NAME, DOS_NAME_LENGTH)) {
;
} else if (!strncmp(dos_ep->name, DOS_PAR_DIR_NAME, DOS_NAME_LENGTH)) {
;
} else if ((type == TYPE_DIR) || (type == TYPE_FILE)) {
/* Set start_clus */
SET32_HI(start_clus, le16_to_cpu(dos_ep->start_clu_hi));
SET32_LO(start_clus, le16_to_cpu(dos_ep->start_clu_lo));
arg->start_clus = start_clus;
/* Set type & i_pos */
if (type == TYPE_DIR)
arg->type = DFR_TRAV_TYPE_DIR;
else
arg->type = DFR_TRAV_TYPE_FILE;
arg->i_pos = i_pos;
/* Set name */
memset(&uniname, 0, sizeof(UNI_NAME_T));
get_uniname_from_dos_entry(sb, dos_ep, &uniname, 0x1);
/* FIXME :
* we should think that whether the size of arg->name
* is enough or not
*/
nls_uni16s_to_vfsname(sb, &uniname,
arg->name, sizeof(arg->name));
err = 0;
/* End case */
} else if (type == TYPE_UNUSED) {
err = -ENOENT;
} else {
;
}
error:
return err;
}
/**
* @fn defrag_scan_dir
* @brief scan given directory
* @return 0 on success, -errno otherwise
* @param sb super block
* @param args traverse args
* @remark protected by inode_lock, super_block and volume lock
*/
int
defrag_scan_dir(
IN struct super_block *sb,
INOUT struct defrag_trav_arg *args)
{
struct sdfat_sb_info *sbi = NULL;
FS_INFO_T *fsi = NULL;
struct defrag_trav_header *header = NULL;
DOS_DENTRY_T *dos_ep;
CHAIN_T chain;
int dot_found = 0, args_idx = DFR_TRAV_HEADER_IDX + 1, clus = 0, index = 0;
int err = 0, j = 0;
/* Check params */
ERR_HANDLE2((!sb || !args), err, -EINVAL);
sbi = SDFAT_SB(sb);
fsi = &(sbi->fsi);
header = (struct defrag_trav_header *) args;
/* Exceptional case for ROOT */
if (header->i_pos == DFR_TRAV_ROOT_IPOS) {
header->start_clus = fsi->root_dir;
dfr_debug("IOC_DFR_TRAV for ROOT: start_clus %08x", header->start_clus);
dot_found = 1;
}
chain.dir = header->start_clus;
chain.size = 0;
chain.flags = 0;
/* Check if this is directory */
if (!dot_found) {
FAT32_CHECK_CLUSTER(fsi, chain.dir, err);
ERR_HANDLE(err);
dos_ep = (DOS_DENTRY_T *) get_dentry_in_dir(sb, &chain, 0, NULL);
ERR_HANDLE2(!dos_ep, err, -EIO);
if (strncmp(dos_ep->name, DOS_CUR_DIR_NAME, DOS_NAME_LENGTH)) {
err = -EINVAL;
dfr_err("Scan: Not a directory, err %d", err);
goto error;
}
}
/* For more-scan case */
if ((header->stat == DFR_TRAV_STAT_MORE) &&
(header->start_clus == sbi->dfr_hint_clus) &&
(sbi->dfr_hint_idx > 0)) {
index = sbi->dfr_hint_idx;
for (j = 0; j < (sbi->dfr_hint_idx / fsi->dentries_per_clu); j++) {
/* Follow FAT-chain */
FAT32_CHECK_CLUSTER(fsi, chain.dir, err);
ERR_HANDLE(err);
err = fat_ent_get(sb, chain.dir, &(chain.dir));
ERR_HANDLE(err);
if (!IS_CLUS_EOF(chain.dir)) {
clus++;
index -= fsi->dentries_per_clu;
} else {
/**
* This directory modified. Stop scanning.
*/
err = -EINVAL;
dfr_err("Scan: SCAN_MORE failed, err %d", err);
goto error;
}
}
/* For first-scan case */
} else {
clus = 0;
index = 0;
}
scan_fat_chain:
/* Scan given directory and get info of children */
for ( ; index < fsi->dentries_per_clu; index++) {
DOS_DENTRY_T *dos_ep = NULL;
loff_t i_pos = 0;
/* Get dos_ep */
FAT32_CHECK_CLUSTER(fsi, chain.dir, err);
ERR_HANDLE(err);
dos_ep = (DOS_DENTRY_T *) get_dentry_in_dir(sb, &chain, index, NULL);
ERR_HANDLE2(!dos_ep, err, -EIO);
/* Make i_pos for this entry */
SET64_HI(i_pos, header->start_clus);
SET64_LO(i_pos, clus * fsi->dentries_per_clu + index);
err = __defrag_scan_dir(sb, dos_ep, i_pos, &args[args_idx]);
if (!err) {
/* More-scan case */
if (++args_idx >= (PAGE_SIZE / sizeof(struct defrag_trav_arg))) {
sbi->dfr_hint_clus = header->start_clus;
sbi->dfr_hint_idx = clus * fsi->dentries_per_clu + index + 1;
header->stat = DFR_TRAV_STAT_MORE;
header->nr_entries = args_idx;
goto error;
}
/* Error case */
} else if (err == -EINVAL) {
sbi->dfr_hint_clus = sbi->dfr_hint_idx = 0;
dfr_err("Scan: err %d", err);
goto error;
/* End case */
} else if (err == -ENOENT) {
sbi->dfr_hint_clus = sbi->dfr_hint_idx = 0;
err = 0;
goto done;
} else {
/* DO NOTHING */
}
err = 0;
}
/* Follow FAT-chain */
FAT32_CHECK_CLUSTER(fsi, chain.dir, err);
ERR_HANDLE(err);
err = fat_ent_get(sb, chain.dir, &(chain.dir));
ERR_HANDLE(err);
if (!IS_CLUS_EOF(chain.dir)) {
index = 0;
clus++;
goto scan_fat_chain;
}
done:
/* Update header */
header->stat = DFR_TRAV_STAT_DONE;
header->nr_entries = args_idx;
error:
return err;
}
static int
__defrag_validate_cluster_prev(
IN struct super_block *sb,
IN struct defrag_chunk_info *chunk)
{
FS_INFO_T *fsi = &(SDFAT_SB(sb)->fsi);
CHAIN_T dir;
DENTRY_T *ep = NULL;
unsigned int entry = 0, clus = 0;
int err = 0;
if (chunk->prev_clus == 0) {
/* For the first cluster of a file */
dir.dir = GET64_HI(chunk->i_pos);
dir.flags = 0x1; // Assume non-continuous
entry = GET64_LO(chunk->i_pos);
FAT32_CHECK_CLUSTER(fsi, dir.dir, err);
ERR_HANDLE(err);
ep = get_dentry_in_dir(sb, &dir, entry, NULL);
if (!ep) {
err = -EPERM;
goto error;
}
/* should call fat_get_entry_clu0(ep) */
clus = fsi->fs_func->get_entry_clu0(ep);
if (clus != chunk->d_clus) {
err = -ENXIO;
goto error;
}
} else {
/* Normal case */
FAT32_CHECK_CLUSTER(fsi, chunk->prev_clus, err);
ERR_HANDLE(err);
err = fat_ent_get(sb, chunk->prev_clus, &clus);
if (err)
goto error;
if (chunk->d_clus != clus)
err = -ENXIO;
}
error:
return err;
}
static int
__defrag_validate_cluster_next(
IN struct super_block *sb,
IN struct defrag_chunk_info *chunk)
{
FS_INFO_T *fsi = &(SDFAT_SB(sb)->fsi);
unsigned int clus = 0;
int err = 0;
/* Check next_clus */
FAT32_CHECK_CLUSTER(fsi, (chunk->d_clus + chunk->nr_clus - 1), err);
ERR_HANDLE(err);
err = fat_ent_get(sb, (chunk->d_clus + chunk->nr_clus - 1), &clus);
if (err)
goto error;
if (chunk->next_clus != (clus & FAT32_EOF))
err = -ENXIO;
error:
return err;
}
/**
* @fn __defrag_check_au
* @brief check if this AU is in use
* @return 0 if idle, 1 if busy
* @param sb super block
* @param clus physical cluster num
* @param limit # of used clusters from daemon
*/
static int
__defrag_check_au(
struct super_block *sb,
u32 clus,
u32 limit)
{
unsigned int nr_free = amap_get_freeclus(sb, clus);
#if defined(CONFIG_SDFAT_DFR_DEBUG) && defined(CONFIG_SDFAT_DBG_MSG)
if (nr_free < limit) {
AMAP_T *amap = SDFAT_SB(sb)->fsi.amap;
AU_INFO_T *au = GET_AU(amap, i_AU_of_CLU(amap, clus));
dfr_debug("AU[%d] nr_free %d, limit %d", au->idx, nr_free, limit);
}
#endif
return ((nr_free < limit) ? 1 : 0);
}
/**
* @fn defrag_validate_cluster
* @brief validate cluster info of given chunk
* @return 0 on success, -errno otherwise
* @param inode inode of given chunk
* @param chunk given chunk
* @param skip_prev flag to skip checking previous cluster info
* @remark protected by super_block and volume lock
*/
int
defrag_validate_cluster(
IN struct inode *inode,
IN struct defrag_chunk_info *chunk,
IN int skip_prev)
{
struct super_block *sb = inode->i_sb;
FILE_ID_T *fid = &(SDFAT_I(inode)->fid);
unsigned int clus = 0;
int err = 0, i = 0;
/* If this inode is unlink-ed, skip it */
if (fid->dir.dir == DIR_DELETED)
return -ENOENT;
/* Skip working-AU */
err = amap_check_working(sb, chunk->d_clus);
if (err)
return -EBUSY;
/* Check # of free_clus of belonged AU */
err = __defrag_check_au(inode->i_sb, chunk->d_clus, CLUS_PER_AU(sb) - chunk->au_clus);
if (err)
return -EINVAL;
/* Check chunk's clusters */
for (i = 0; i < chunk->nr_clus; i++) {
err = fsapi_map_clus(inode, chunk->f_clus + i, &clus, ALLOC_NOWHERE);
if (err || (chunk->d_clus + i != clus)) {
if (!err)
err = -ENXIO;
goto error;
}
}
/* Check next_clus */
err = __defrag_validate_cluster_next(sb, chunk);
ERR_HANDLE(err);
if (!skip_prev) {
/* Check prev_clus */
err = __defrag_validate_cluster_prev(sb, chunk);
ERR_HANDLE(err);
}
error:
return err;
}
/**
* @fn defrag_reserve_clusters
* @brief reserve clusters for defrag
* @return 0 on success, -errno otherwise
* @param sb super block
* @param nr_clus # of clusters to reserve
* @remark protected by super_block and volume lock
*/
int
defrag_reserve_clusters(
INOUT struct super_block *sb,
IN int nr_clus)
{
struct sdfat_sb_info *sbi = SDFAT_SB(sb);
FS_INFO_T *fsi = &(sbi->fsi);
if (!(sbi->options.improved_allocation & SDFAT_ALLOC_DELAY))
/* Nothing to do */
return 0;
/* Check error case */
if (fsi->used_clusters + fsi->reserved_clusters + nr_clus >= fsi->num_clusters - 2) {
return -ENOSPC;
} else if (fsi->reserved_clusters + nr_clus < 0) {
dfr_err("Reserve count: reserved_clusters %d, nr_clus %d",
fsi->reserved_clusters, nr_clus);
BUG_ON(fsi->reserved_clusters + nr_clus < 0);
}
sbi->dfr_reserved_clus += nr_clus;
fsi->reserved_clusters += nr_clus;
return 0;
}
/**
* @fn defrag_mark_ignore
* @brief mark corresponding AU to be ignored
* @return 0 on success, -errno otherwise
* @param sb super block
* @param clus given cluster num
* @remark protected by super_block
*/
int
defrag_mark_ignore(
INOUT struct super_block *sb,
IN unsigned int clus)
{
int err = 0;
if (SDFAT_SB(sb)->options.improved_allocation & SDFAT_ALLOC_SMART)
err = amap_mark_ignore(sb, clus);
if (err)
dfr_debug("err %d", err);
return err;
}
/**
* @fn defrag_unmark_ignore_all
* @brief unmark all ignored AUs
* @return void
* @param sb super block
* @remark protected by super_block
*/
void
defrag_unmark_ignore_all(struct super_block *sb)
{
if (SDFAT_SB(sb)->options.improved_allocation & SDFAT_ALLOC_SMART)
amap_unmark_ignore_all(sb);
}
/**
* @fn defrag_map_cluster
* @brief get_block function for defrag dests
* @return 0 on success, -errno otherwise
* @param inode inode
* @param clu_offset logical cluster offset
* @param clu mapped cluster (physical)
* @remark protected by super_block and volume lock
*/
int
defrag_map_cluster(
struct inode *inode,
unsigned int clu_offset,
unsigned int *clu)
{
struct super_block *sb = inode->i_sb;
struct sdfat_sb_info *sbi = SDFAT_SB(sb);
FS_INFO_T *fsi = &(SDFAT_SB(sb)->fsi);
#ifdef CONFIG_SDFAT_DFR_PACKING
AMAP_T *amap = SDFAT_SB(sb)->fsi.amap;
#endif
FILE_ID_T *fid = &(SDFAT_I(inode)->fid);
struct defrag_info *ino_dfr = &(SDFAT_I(inode)->dfr_info);
struct defrag_chunk_info *chunk = NULL;
CHAIN_T new_clu;
int i = 0, nr_new = 0, err = 0;
/* Get corresponding chunk */
for (i = 0; i < ino_dfr->nr_chunks; i++) {
chunk = &(ino_dfr->chunks[i]);
if ((chunk->f_clus <= clu_offset) && (clu_offset < chunk->f_clus + chunk->nr_clus)) {
/* For already allocated new_clus */
if (sbi->dfr_new_clus[chunk->new_idx + clu_offset - chunk->f_clus]) {
*clu = sbi->dfr_new_clus[chunk->new_idx + clu_offset - chunk->f_clus];
return 0;
}
break;
}
}
BUG_ON(!chunk);
fscore_set_vol_flags(sb, VOL_DIRTY, 0);
new_clu.dir = CLUS_EOF;
new_clu.size = 0;
new_clu.flags = fid->flags;
/* Allocate new cluster */
#ifdef CONFIG_SDFAT_DFR_PACKING
if (amap->n_clean_au * DFR_FULL_RATIO <= amap->n_au * DFR_DEFAULT_PACKING_RATIO)
err = fsi->fs_func->alloc_cluster(sb, 1, &new_clu, ALLOC_COLD_PACKING);
else
err = fsi->fs_func->alloc_cluster(sb, 1, &new_clu, ALLOC_COLD_ALIGNED);
#else
err = fsi->fs_func->alloc_cluster(sb, 1, &new_clu, ALLOC_COLD_ALIGNED);
#endif
if (err) {
dfr_err("Map: 1 %d", 0);
return err;
}
/* Decrease reserved cluster count */
defrag_reserve_clusters(sb, -1);
/* Add new_clus info in ino_dfr */
sbi->dfr_new_clus[chunk->new_idx + clu_offset - chunk->f_clus] = new_clu.dir;
/* Make FAT-chain for new_clus */
for (i = 0; i < chunk->nr_clus; i++) {
#if 0
if (sbi->dfr_new_clus[chunk->new_idx + i])
nr_new++;
else
break;
#else
if (!sbi->dfr_new_clus[chunk->new_idx + i])
break;
nr_new++;
#endif
}
if (nr_new == chunk->nr_clus) {
for (i = 0; i < chunk->nr_clus - 1; i++) {
FAT32_CHECK_CLUSTER(fsi, sbi->dfr_new_clus[chunk->new_idx + i], err);
BUG_ON(err);
if (fat_ent_set(sb,
sbi->dfr_new_clus[chunk->new_idx + i],
sbi->dfr_new_clus[chunk->new_idx + i + 1]))
return -EIO;
}
}
*clu = new_clu.dir;
return 0;
}
/**
* @fn defrag_writepage_end_io
* @brief check WB status of requested page
* @return void
* @param page page
*/
void
defrag_writepage_end_io(
INOUT struct page *page)
{
struct super_block *sb = page->mapping->host->i_sb;
struct sdfat_sb_info *sbi = SDFAT_SB(sb);
struct defrag_info *ino_dfr = &(SDFAT_I(page->mapping->host)->dfr_info);
unsigned int clus_start = 0, clus_end = 0;
int i = 0;
/* Check if this inode is on defrag */
if (atomic_read(&ino_dfr->stat) != DFR_INO_STAT_REQ)
return;
clus_start = page->index / PAGES_PER_CLUS(sb);
clus_end = clus_start + 1;
/* Check each chunk in given inode */
for (i = 0; i < ino_dfr->nr_chunks; i++) {
struct defrag_chunk_info *chunk = &(ino_dfr->chunks[i]);
unsigned int chunk_start = 0, chunk_end = 0;
chunk_start = chunk->f_clus;
chunk_end = chunk->f_clus + chunk->nr_clus;
if ((clus_start >= chunk_start) && (clus_end <= chunk_end)) {
int off = clus_start - chunk_start;
clear_bit((page->index & (PAGES_PER_CLUS(sb) - 1)),
(volatile unsigned long *)&(sbi->dfr_page_wb[chunk->new_idx + off]));
}
}
}
/**
* @fn __defrag_check_wb
* @brief check if WB for given chunk completed
* @return 0 on success, -errno otherwise
* @param sbi super block info
* @param chunk given chunk
*/
static int
__defrag_check_wb(
IN struct sdfat_sb_info *sbi,
IN struct defrag_chunk_info *chunk)
{
int err = 0, wb_i = 0, i = 0, nr_new = 0;
if (!sbi || !chunk)
return -EINVAL;
/* Check WB complete status first */
for (wb_i = 0; wb_i < chunk->nr_clus; wb_i++) {
if (atomic_read((atomic_t *)&(sbi->dfr_page_wb[chunk->new_idx + wb_i]))) {
err = -EBUSY;
break;
}
}
/**
* Check NEW_CLUS status.
* writepage_end_io cannot check whole WB complete status,
* so we need to check NEW_CLUS status.
*/
for (i = 0; i < chunk->nr_clus; i++)
if (sbi->dfr_new_clus[chunk->new_idx + i])
nr_new++;
if (nr_new == chunk->nr_clus) {
err = 0;
if ((wb_i != chunk->nr_clus) && (wb_i != chunk->nr_clus - 1))
dfr_debug("submit_fullpage_bio() called on a page (nr_clus %d, wb_i %d)",
chunk->nr_clus, wb_i);
BUG_ON(nr_new > chunk->nr_clus);
} else {
dfr_debug("nr_new %d, nr_clus %d", nr_new, chunk->nr_clus);
err = -EBUSY;
}
/* Update chunk's state */
if (!err)
chunk->stat |= DFR_CHUNK_STAT_WB;
return err;
}
static void
__defrag_check_fat_old(
IN struct super_block *sb,
IN struct inode *inode,
IN struct defrag_chunk_info *chunk)
{
FS_INFO_T *fsi = &(SDFAT_SB(sb)->fsi);
unsigned int clus = 0;
int err = 0, idx = 0, max_idx = 0;
/* Get start_clus */
clus = SDFAT_I(inode)->fid.start_clu;
/* Follow FAT-chain */
#define num_clusters(val) ((val) ? (s32)((val - 1) >> fsi->cluster_size_bits) + 1 : 0)
max_idx = num_clusters(SDFAT_I(inode)->i_size_ondisk);
for (idx = 0; idx < max_idx; idx++) {
FAT32_CHECK_CLUSTER(fsi, clus, err);
ERR_HANDLE(err);
err = fat_ent_get(sb, clus, &clus);
ERR_HANDLE(err);
if ((idx < max_idx - 1) && (IS_CLUS_EOF(clus) || IS_CLUS_FREE(clus))) {
dfr_err("FAT: inode %p, max_idx %d, idx %d, clus %08x, "
"f_clus %d, nr_clus %d", inode, max_idx,
idx, clus, chunk->f_clus, chunk->nr_clus);
BUG_ON(idx < max_idx - 1);
goto error;
}
}
error:
return;
}
static void
__defrag_check_fat_new(
IN struct super_block *sb,
IN struct inode *inode,
IN struct defrag_chunk_info *chunk)
{
struct sdfat_sb_info *sbi = SDFAT_SB(sb);
FS_INFO_T *fsi = &(SDFAT_SB(sb)->fsi);
unsigned int clus = 0;
int i = 0, err = 0;
/* Check start of FAT-chain */
if (chunk->prev_clus) {
FAT32_CHECK_CLUSTER(fsi, chunk->prev_clus, err);
BUG_ON(err);
err = fat_ent_get(sb, chunk->prev_clus, &clus);
BUG_ON(err);
} else {
clus = SDFAT_I(inode)->fid.start_clu;
}
if (sbi->dfr_new_clus[chunk->new_idx] != clus) {
dfr_err("FAT: inode %p, start_clus %08x, read_clus %08x",
inode, sbi->dfr_new_clus[chunk->new_idx], clus);
err = EIO;
goto error;
}
/* Check inside of FAT-chain */
if (chunk->nr_clus > 1) {
for (i = 0; i < chunk->nr_clus - 1; i++) {
FAT32_CHECK_CLUSTER(fsi, sbi->dfr_new_clus[chunk->new_idx + i], err);
BUG_ON(err);
err = fat_ent_get(sb, sbi->dfr_new_clus[chunk->new_idx + i], &clus);
BUG_ON(err);
if (sbi->dfr_new_clus[chunk->new_idx + i + 1] != clus) {
dfr_err("FAT: inode %p, new_clus %08x, read_clus %08x",
inode, sbi->dfr_new_clus[chunk->new_idx], clus);
err = EIO;
goto error;
}
}
clus = 0;
}
/* Check end of FAT-chain */
FAT32_CHECK_CLUSTER(fsi, sbi->dfr_new_clus[chunk->new_idx + chunk->nr_clus - 1], err);
BUG_ON(err);
err = fat_ent_get(sb, sbi->dfr_new_clus[chunk->new_idx + chunk->nr_clus - 1], &clus);
BUG_ON(err);
if ((chunk->next_clus & 0x0FFFFFFF) != (clus & 0x0FFFFFFF)) {
dfr_err("FAT: inode %p, next_clus %08x, read_clus %08x", inode, chunk->next_clus, clus);
err = EIO;
}
error:
BUG_ON(err);
}
/**
* @fn __defrag_update_dirent
* @brief update DIR entry for defrag req
* @return void
* @param sb super block
* @param chunk given chunk
*/
static void
__defrag_update_dirent(
struct super_block *sb,
struct defrag_chunk_info *chunk)
{
struct sdfat_sb_info *sbi = SDFAT_SB(sb);
FS_INFO_T *fsi = &SDFAT_SB(sb)->fsi;
CHAIN_T dir;
DOS_DENTRY_T *dos_ep;
unsigned int entry = 0;
unsigned long long sector = 0;
unsigned short hi = 0, lo = 0;
int err = 0;
dir.dir = GET64_HI(chunk->i_pos);
dir.flags = 0x1; // Assume non-continuous
entry = GET64_LO(chunk->i_pos);
FAT32_CHECK_CLUSTER(fsi, dir.dir, err);
BUG_ON(err);
dos_ep = (DOS_DENTRY_T *) get_dentry_in_dir(sb, &dir, entry, &sector);
hi = GET32_HI(sbi->dfr_new_clus[chunk->new_idx]);
lo = GET32_LO(sbi->dfr_new_clus[chunk->new_idx]);
dos_ep->start_clu_hi = cpu_to_le16(hi);
dos_ep->start_clu_lo = cpu_to_le16(lo);
dcache_modify(sb, sector);
}
/**
* @fn defrag_update_fat_prev
* @brief update FAT chain for defrag requests
* @return void
* @param sb super block
* @param force flag to force FAT update
* @remark protected by super_block and volume lock
*/
void
defrag_update_fat_prev(
struct super_block *sb,
int force)
{
struct sdfat_sb_info *sbi = SDFAT_SB(sb);
FS_INFO_T *fsi = &(sbi->fsi);
struct defrag_info *sb_dfr = &sbi->dfr_info, *ino_dfr = NULL;
int skip = 0, done = 0;
/* Check if FS_ERROR occurred */
if (sb->s_flags & MS_RDONLY) {
dfr_err("RDONLY partition (err %d)", -EPERM);
goto out;
}
list_for_each_entry(ino_dfr, &sb_dfr->entry, entry) {
struct inode *inode = &(container_of(ino_dfr, struct sdfat_inode_info, dfr_info)->vfs_inode);
struct sdfat_inode_info *ino_info = SDFAT_I(inode);
struct defrag_chunk_info *chunk_prev = NULL;
int i = 0, j = 0;
mutex_lock(&ino_dfr->lock);
BUG_ON(atomic_read(&ino_dfr->stat) != DFR_INO_STAT_REQ);
for (i = 0; i < ino_dfr->nr_chunks; i++) {
struct defrag_chunk_info *chunk = NULL;
int err = 0;
chunk = &(ino_dfr->chunks[i]);
BUG_ON(!chunk);
/* Do nothing for already passed chunk */
if (chunk->stat == DFR_CHUNK_STAT_PASS) {
done++;
continue;
}
/* Handle error case */
if (chunk->stat == DFR_CHUNK_STAT_ERR) {
err = -EINVAL;
goto error;
}
/* Double-check clusters */
if (chunk_prev &&
(chunk->f_clus == chunk_prev->f_clus + chunk_prev->nr_clus) &&
(chunk_prev->stat == DFR_CHUNK_STAT_PASS)) {
err = defrag_validate_cluster(inode, chunk, 1);
/* Handle continuous chunks in a file */
if (!err) {
chunk->prev_clus =
sbi->dfr_new_clus[chunk_prev->new_idx + chunk_prev->nr_clus - 1];
dfr_debug("prev->f_clus %d, prev->nr_clus %d, chunk->f_clus %d",
chunk_prev->f_clus, chunk_prev->nr_clus, chunk->f_clus);
}
} else {
err = defrag_validate_cluster(inode, chunk, 0);
}
if (err) {
dfr_err("Cluster validation: inode %p, chunk->f_clus %d, err %d",
inode, chunk->f_clus, err);
goto error;
}
/**
* Skip update_fat_prev if WB or update_fat_next not completed.
* Go to error case if FORCE set.
*/
if (__defrag_check_wb(sbi, chunk) || (chunk->stat != DFR_CHUNK_STAT_PREP)) {
if (force) {
err = -EPERM;
dfr_err("Skip case: inode %p, stat %x, f_clus %d, err %d",
inode, chunk->stat, chunk->f_clus, err);
goto error;
}
skip++;
continue;
}
#ifdef CONFIG_SDFAT_DFR_DEBUG
/* SPO test */
defrag_spo_test(sb, DFR_SPO_RANDOM, __func__);
#endif
/* Update chunk's previous cluster */
if (chunk->prev_clus == 0) {
/* For the first cluster of a file */
/* Update ino_info->fid.start_clu */
ino_info->fid.start_clu = sbi->dfr_new_clus[chunk->new_idx];
__defrag_update_dirent(sb, chunk);
} else {
FAT32_CHECK_CLUSTER(fsi, chunk->prev_clus, err);
BUG_ON(err);
if (fat_ent_set(sb,
chunk->prev_clus,
sbi->dfr_new_clus[chunk->new_idx])) {
err = -EIO;
goto error;
}
}
/* Clear extent cache */
extent_cache_inval_inode(inode);
/* Update FID info */
ino_info->fid.hint_bmap.off = CLUS_EOF;
ino_info->fid.hint_bmap.clu = 0;
/* Clear old FAT-chain */
for (j = 0; j < chunk->nr_clus; j++)
defrag_free_cluster(sb, chunk->d_clus + j);
/* Mark this chunk PASS */
chunk->stat = DFR_CHUNK_STAT_PASS;
__defrag_check_fat_new(sb, inode, chunk);
done++;
error:
if (err) {
/**
* chunk->new_idx != 0 means this chunk needs to be cleaned up
*/
if (chunk->new_idx) {
/* Free already allocated clusters */
for (j = 0; j < chunk->nr_clus; j++) {
if (sbi->dfr_new_clus[chunk->new_idx + j]) {
defrag_free_cluster(sb, sbi->dfr_new_clus[chunk->new_idx + j]);
sbi->dfr_new_clus[chunk->new_idx + j] = 0;
}
}
__defrag_check_fat_old(sb, inode, chunk);
}
/**
* chunk->new_idx == 0 means this chunk already cleaned up
*/
chunk->new_idx = 0;
chunk->stat = DFR_CHUNK_STAT_ERR;
}
chunk_prev = chunk;
}
BUG_ON(!mutex_is_locked(&ino_dfr->lock));
mutex_unlock(&ino_dfr->lock);
}
out:
if (skip) {
dfr_debug("%s skipped (nr_reqs %d, done %d, skip %d)",
__func__, sb_dfr->nr_chunks - 1, done, skip);
} else {
/* Make dfr_reserved_clus zero */
if (sbi->dfr_reserved_clus > 0) {
if (fsi->reserved_clusters < sbi->dfr_reserved_clus) {
dfr_err("Reserved count: reserved_clus %d, dfr_reserved_clus %d",
fsi->reserved_clusters, sbi->dfr_reserved_clus);
BUG_ON(fsi->reserved_clusters < sbi->dfr_reserved_clus);
}
defrag_reserve_clusters(sb, 0 - sbi->dfr_reserved_clus);
}
dfr_debug("%s done (nr_reqs %d, done %d)", __func__, sb_dfr->nr_chunks - 1, done);
}
}
/**
* @fn defrag_update_fat_next
* @brief update FAT chain for defrag requests
* @return void
* @param sb super block
* @remark protected by super_block and volume lock
*/
void
defrag_update_fat_next(
struct super_block *sb)
{
struct sdfat_sb_info *sbi = SDFAT_SB(sb);
FS_INFO_T *fsi = &(SDFAT_SB(sb)->fsi);
struct defrag_info *sb_dfr = &sbi->dfr_info, *ino_dfr = NULL;
struct defrag_chunk_info *chunk = NULL;
int done = 0, i = 0, j = 0, err = 0;
/* Check if FS_ERROR occurred */
if (sb->s_flags & MS_RDONLY) {
dfr_err("RDONLY partition (err %d)", -EROFS);
goto out;
}
list_for_each_entry(ino_dfr, &sb_dfr->entry, entry) {
for (i = 0; i < ino_dfr->nr_chunks; i++) {
int skip = 0;
chunk = &(ino_dfr->chunks[i]);
/* Do nothing if error occurred or update_fat_next already passed */
if (chunk->stat == DFR_CHUNK_STAT_ERR)
continue;
if (chunk->stat & DFR_CHUNK_STAT_FAT) {
done++;
continue;
}
/* Ship this chunk if get_block not passed for this chunk */
for (j = 0; j < chunk->nr_clus; j++) {
if (sbi->dfr_new_clus[chunk->new_idx + j] == 0) {
skip = 1;
break;
}
}
if (skip)
continue;
/* Update chunk's next cluster */
FAT32_CHECK_CLUSTER(fsi,
sbi->dfr_new_clus[chunk->new_idx + chunk->nr_clus - 1], err);
BUG_ON(err);
if (fat_ent_set(sb,
sbi->dfr_new_clus[chunk->new_idx + chunk->nr_clus - 1],
chunk->next_clus))
goto out;
#ifdef CONFIG_SDFAT_DFR_DEBUG
/* SPO test */
defrag_spo_test(sb, DFR_SPO_RANDOM, __func__);
#endif
/* Update chunk's state */
chunk->stat |= DFR_CHUNK_STAT_FAT;
done++;
}
}
out:
dfr_debug("%s done (nr_reqs %d, done %d)", __func__, sb_dfr->nr_chunks - 1, done);
}
/**
* @fn defrag_check_discard
* @brief check if we can send discard for this AU, if so, send discard
* @return void
* @param sb super block
* @remark protected by super_block and volume lock
*/
void
defrag_check_discard(
IN struct super_block *sb)
{
FS_INFO_T *fsi = &(SDFAT_SB(sb)->fsi);
AMAP_T *amap = SDFAT_SB(sb)->fsi.amap;
AU_INFO_T *au = NULL;
struct defrag_info *sb_dfr = &(SDFAT_SB(sb)->dfr_info);
unsigned int tmp[DFR_MAX_AU_MOVED];
int i = 0, j = 0;
BUG_ON(!amap);
if (!(SDFAT_SB(sb)->options.discard) ||
!(SDFAT_SB(sb)->options.improved_allocation & SDFAT_ALLOC_SMART))
return;
memset(tmp, 0, sizeof(int) * DFR_MAX_AU_MOVED);
for (i = REQ_HEADER_IDX + 1; i < sb_dfr->nr_chunks; i++) {
struct defrag_chunk_info *chunk = &(sb_dfr->chunks[i]);
int skip = 0;
au = GET_AU(amap, i_AU_of_CLU(amap, chunk->d_clus));
/* Send DISCARD for free AU */
if ((IS_AU_IGNORED(au, amap)) &&
(amap_get_freeclus(sb, chunk->d_clus) == CLUS_PER_AU(sb))) {
sector_t blk = 0, nr_blks = 0;
unsigned int au_align_factor = amap->option.au_align_factor % amap->option.au_size;
BUG_ON(au->idx == 0);
/* Avoid multiple DISCARD */
for (j = 0; j < DFR_MAX_AU_MOVED; j++) {
if (tmp[j] == au->idx) {
skip = 1;
break;
}
}
if (skip == 1)
continue;
/* Send DISCARD cmd */
blk = (sector_t) (((au->idx * CLUS_PER_AU(sb)) << fsi->sect_per_clus_bits)
- au_align_factor);
nr_blks = ((sector_t)CLUS_PER_AU(sb)) << fsi->sect_per_clus_bits;
dfr_debug("Send DISCARD for AU[%d] (blk %08zx)", au->idx, blk);
sb_issue_discard(sb, blk, nr_blks, GFP_NOFS, 0);
/* Save previous AU's index */
for (j = 0; j < DFR_MAX_AU_MOVED; j++) {
if (!tmp[j]) {
tmp[j] = au->idx;
break;
}
}
}
}
}
/**
* @fn defrag_free_cluster
* @brief free uneccessary cluster
* @return void
* @param sb super block
* @param clus physical cluster num
* @remark protected by super_block and volume lock
*/
int
defrag_free_cluster(
struct super_block *sb,
unsigned int clus)
{
FS_INFO_T *fsi = &SDFAT_SB(sb)->fsi;
unsigned int val = 0;
s32 err = 0;
FAT32_CHECK_CLUSTER(fsi, clus, err);
BUG_ON(err);
if (fat_ent_get(sb, clus, &val))
return -EIO;
if (val) {
if (fat_ent_set(sb, clus, 0))
return -EIO;
} else {
dfr_err("Free: Already freed, clus %08x, val %08x", clus, val);
BUG_ON(!val);
}
set_sb_dirty(sb);
fsi->used_clusters--;
if (fsi->amap)
amap_release_cluster(sb, clus);
return 0;
}
/**
* @fn defrag_check_defrag_required
* @brief check if defrag required
* @return 1 if required, 0 otherwise
* @param sb super block
* @param totalau # of total AUs
* @param cleanau # of clean AUs
* @param fullau # of full AUs
* @remark protected by super_block
*/
int
defrag_check_defrag_required(
IN struct super_block *sb,
OUT int *totalau,
OUT int *cleanau,
OUT int *fullau)
{
FS_INFO_T *fsi = &(SDFAT_SB(sb)->fsi);
AMAP_T *amap = NULL;
int clean_ratio = 0, frag_ratio = 0;
int ret = 0;
if (!sb || !(SDFAT_SB(sb)->options.defrag))
return 0;
/* Check DFR_DEFAULT_STOP_RATIO first */
fsi = &(SDFAT_SB(sb)->fsi);
if (fsi->used_clusters == (unsigned int)(~0)) {
if (fsi->fs_func->count_used_clusters(sb, &fsi->used_clusters))
return -EIO;
}
if (fsi->used_clusters * DFR_FULL_RATIO >= fsi->num_clusters * DFR_DEFAULT_STOP_RATIO) {
dfr_debug("used_clusters %d, num_clusters %d", fsi->used_clusters, fsi->num_clusters);
return 0;
}
/* Check clean/frag ratio */
amap = SDFAT_SB(sb)->fsi.amap;
BUG_ON(!amap);
clean_ratio = (amap->n_clean_au * 100) / amap->n_au;
if (amap->n_full_au)
frag_ratio = ((amap->n_au - amap->n_clean_au) * 100) / amap->n_full_au;
else
frag_ratio = ((amap->n_au - amap->n_clean_au) * 100) /
(fsi->used_clusters * CLUS_PER_AU(sb));
/*
* Wake-up defrag_daemon:
* when # of clean AUs too small, or frag_ratio exceeds the limit
*/
if ((clean_ratio < DFR_DEFAULT_WAKEUP_RATIO) ||
((clean_ratio < DFR_DEFAULT_CLEAN_RATIO) && (frag_ratio >= DFR_DEFAULT_FRAG_RATIO))) {
if (totalau)
*totalau = amap->n_au;
if (cleanau)
*cleanau = amap->n_clean_au;
if (fullau)
*fullau = amap->n_full_au;
ret = 1;
}
return ret;
}
/**
* @fn defrag_check_defrag_required
* @brief check defrag status on inode
* @return 1 if defrag in on, 0 otherwise
* @param inode inode
* @param start logical start addr
* @param end logical end addr
* @param cancel flag to cancel defrag
* @param caller caller info
*/
int
defrag_check_defrag_on(
INOUT struct inode *inode,
IN loff_t start,
IN loff_t end,
IN int cancel,
IN const char *caller)
{
struct super_block *sb = inode->i_sb;
struct sdfat_sb_info *sbi = SDFAT_SB(sb);
FS_INFO_T *fsi = &(sbi->fsi);
struct defrag_info *ino_dfr = &(SDFAT_I(inode)->dfr_info);
unsigned int clus_start = 0, clus_end = 0;
int ret = 0, i = 0;
if (!inode || (start == end))
return 0;
mutex_lock(&ino_dfr->lock);
/* Check if this inode is on defrag */
if (atomic_read(&ino_dfr->stat) == DFR_INO_STAT_REQ) {
clus_start = start >> (fsi->cluster_size_bits);
clus_end = (end >> (fsi->cluster_size_bits)) +
((end & (fsi->cluster_size - 1)) ? 1 : 0);
if (!ino_dfr->chunks)
goto error;
/* Check each chunk in given inode */
for (i = 0; i < ino_dfr->nr_chunks; i++) {
struct defrag_chunk_info *chunk = &(ino_dfr->chunks[i]);
unsigned int chunk_start = 0, chunk_end = 0;
/* Skip this chunk when error occurred or it already passed defrag process */
if ((chunk->stat == DFR_CHUNK_STAT_ERR) || (chunk->stat == DFR_CHUNK_STAT_PASS))
continue;
chunk_start = chunk->f_clus;
chunk_end = chunk->f_clus + chunk->nr_clus;
if (((clus_start >= chunk_start) && (clus_start < chunk_end)) ||
((clus_end > chunk_start) && (clus_end <= chunk_end)) ||
((clus_start < chunk_start) && (clus_end > chunk_end))) {
ret = 1;
if (cancel) {
chunk->stat = DFR_CHUNK_STAT_ERR;
dfr_debug("Defrag canceled: inode %p, start %08x, end %08x, caller %s",
inode, clus_start, clus_end, caller);
}
}
}
}
error:
BUG_ON(!mutex_is_locked(&ino_dfr->lock));
mutex_unlock(&ino_dfr->lock);
return ret;
}
#ifdef CONFIG_SDFAT_DFR_DEBUG
/**
* @fn defrag_spo_test
* @brief test SPO while defrag running
* @return void
* @param sb super block
* @param flag SPO debug flag
* @param caller caller info
*/
void
defrag_spo_test(
struct super_block *sb,
int flag,
const char *caller)
{
struct sdfat_sb_info *sbi = SDFAT_SB(sb);
if (!sb || !(SDFAT_SB(sb)->options.defrag))
return;
if (flag == sbi->dfr_spo_flag) {
dfr_err("Defrag SPO test (flag %d, caller %s)", flag, caller);
panic("Defrag SPO test");
}
}
#endif /* CONFIG_SDFAT_DFR_DEBUG */
#endif /* CONFIG_SDFAT_DFR */