| /* |
| * 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, §or); |
| |
| 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 */ |