summaryrefslogtreecommitdiff
path: root/fs/sdfat/dfr.c
diff options
context:
space:
mode:
Diffstat (limited to 'fs/sdfat/dfr.c')
-rw-r--r--fs/sdfat/dfr.c1372
1 files changed, 1372 insertions, 0 deletions
diff --git a/fs/sdfat/dfr.c b/fs/sdfat/dfr.c
new file mode 100644
index 000000000000..abb4710b4340
--- /dev/null
+++ b/fs/sdfat/dfr.c
@@ -0,0 +1,1372 @@
+/*
+ * 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) + 1);
+
+ /*
+ * 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 */