2020-03-02 06:21:38 +00:00
|
|
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
|
|
|
/*
|
|
|
|
* Copyright (C) 2012-2013 Samsung Electronics Co., Ltd.
|
|
|
|
*/
|
|
|
|
|
|
|
|
#include <linux/blkdev.h>
|
|
|
|
#include <linux/slab.h>
|
2023-12-05 15:58:37 +00:00
|
|
|
#include <linux/bitmap.h>
|
2020-03-02 06:21:38 +00:00
|
|
|
#include <linux/buffer_head.h>
|
|
|
|
|
|
|
|
#include "exfat_raw.h"
|
|
|
|
#include "exfat_fs.h"
|
|
|
|
|
2023-12-05 15:58:37 +00:00
|
|
|
#if BITS_PER_LONG == 32
|
|
|
|
#define __le_long __le32
|
|
|
|
#define lel_to_cpu(A) le32_to_cpu(A)
|
2023-12-07 23:47:01 +00:00
|
|
|
#define cpu_to_lel(A) cpu_to_le32(A)
|
2023-12-05 15:58:37 +00:00
|
|
|
#elif BITS_PER_LONG == 64
|
|
|
|
#define __le_long __le64
|
|
|
|
#define lel_to_cpu(A) le64_to_cpu(A)
|
2023-12-07 23:47:01 +00:00
|
|
|
#define cpu_to_lel(A) cpu_to_le64(A)
|
2023-12-05 15:58:37 +00:00
|
|
|
#else
|
|
|
|
#error "BITS_PER_LONG not 32 or 64"
|
|
|
|
#endif
|
|
|
|
|
2020-03-02 06:21:38 +00:00
|
|
|
/*
|
|
|
|
* Allocation Bitmap Management Functions
|
|
|
|
*/
|
|
|
|
static int exfat_allocate_bitmap(struct super_block *sb,
|
|
|
|
struct exfat_dentry *ep)
|
|
|
|
{
|
|
|
|
struct exfat_sb_info *sbi = EXFAT_SB(sb);
|
|
|
|
long long map_size;
|
|
|
|
unsigned int i, need_map_size;
|
|
|
|
sector_t sector;
|
|
|
|
|
|
|
|
sbi->map_clu = le32_to_cpu(ep->dentry.bitmap.start_clu);
|
|
|
|
map_size = le64_to_cpu(ep->dentry.bitmap.size);
|
|
|
|
need_map_size = ((EXFAT_DATA_CLUSTER_COUNT(sbi) - 1) / BITS_PER_BYTE)
|
|
|
|
+ 1;
|
|
|
|
if (need_map_size != map_size) {
|
2020-04-24 04:31:12 +00:00
|
|
|
exfat_err(sb, "bogus allocation bitmap size(need : %u, cur : %lld)",
|
|
|
|
need_map_size, map_size);
|
2020-03-02 06:21:38 +00:00
|
|
|
/*
|
|
|
|
* Only allowed when bogus allocation
|
|
|
|
* bitmap size is large
|
|
|
|
*/
|
|
|
|
if (need_map_size > map_size)
|
|
|
|
return -EIO;
|
|
|
|
}
|
|
|
|
sbi->map_sectors = ((need_map_size - 1) >>
|
|
|
|
(sb->s_blocksize_bits)) + 1;
|
2023-07-05 07:15:15 +00:00
|
|
|
sbi->vol_amap = kvmalloc_array(sbi->map_sectors,
|
2020-03-02 06:21:38 +00:00
|
|
|
sizeof(struct buffer_head *), GFP_KERNEL);
|
|
|
|
if (!sbi->vol_amap)
|
|
|
|
return -ENOMEM;
|
|
|
|
|
|
|
|
sector = exfat_cluster_to_sector(sbi, sbi->map_clu);
|
|
|
|
for (i = 0; i < sbi->map_sectors; i++) {
|
|
|
|
sbi->vol_amap[i] = sb_bread(sb, sector + i);
|
|
|
|
if (!sbi->vol_amap[i]) {
|
|
|
|
/* release all buffers and free vol_amap */
|
|
|
|
int j = 0;
|
|
|
|
|
|
|
|
while (j < i)
|
|
|
|
brelse(sbi->vol_amap[j++]);
|
|
|
|
|
2023-07-05 07:15:15 +00:00
|
|
|
kvfree(sbi->vol_amap);
|
2020-03-02 06:21:38 +00:00
|
|
|
sbi->vol_amap = NULL;
|
|
|
|
return -EIO;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
int exfat_load_bitmap(struct super_block *sb)
|
|
|
|
{
|
|
|
|
unsigned int i, type;
|
|
|
|
struct exfat_chain clu;
|
|
|
|
struct exfat_sb_info *sbi = EXFAT_SB(sb);
|
|
|
|
|
|
|
|
exfat_chain_set(&clu, sbi->root_dir, 0, ALLOC_FAT_CHAIN);
|
|
|
|
while (clu.dir != EXFAT_EOF_CLUSTER) {
|
|
|
|
for (i = 0; i < sbi->dentries_per_clu; i++) {
|
|
|
|
struct exfat_dentry *ep;
|
|
|
|
struct buffer_head *bh;
|
|
|
|
|
2021-12-16 12:58:37 +00:00
|
|
|
ep = exfat_get_dentry(sb, &clu, i, &bh);
|
2020-03-02 06:21:38 +00:00
|
|
|
if (!ep)
|
|
|
|
return -EIO;
|
|
|
|
|
|
|
|
type = exfat_get_entry_type(ep);
|
|
|
|
if (type == TYPE_UNUSED)
|
|
|
|
break;
|
|
|
|
if (type != TYPE_BITMAP)
|
|
|
|
continue;
|
|
|
|
if (ep->dentry.bitmap.flags == 0x0) {
|
|
|
|
int err;
|
|
|
|
|
|
|
|
err = exfat_allocate_bitmap(sb, ep);
|
|
|
|
brelse(bh);
|
|
|
|
return err;
|
|
|
|
}
|
|
|
|
brelse(bh);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (exfat_get_next_cluster(sb, &clu.dir))
|
|
|
|
return -EIO;
|
|
|
|
}
|
|
|
|
|
|
|
|
return -EINVAL;
|
|
|
|
}
|
|
|
|
|
|
|
|
void exfat_free_bitmap(struct exfat_sb_info *sbi)
|
|
|
|
{
|
|
|
|
int i;
|
|
|
|
|
|
|
|
for (i = 0; i < sbi->map_sectors; i++)
|
|
|
|
__brelse(sbi->vol_amap[i]);
|
|
|
|
|
2023-07-05 07:15:15 +00:00
|
|
|
kvfree(sbi->vol_amap);
|
2020-03-02 06:21:38 +00:00
|
|
|
}
|
|
|
|
|
2021-03-15 04:12:55 +00:00
|
|
|
int exfat_set_bitmap(struct inode *inode, unsigned int clu, bool sync)
|
2020-03-02 06:21:38 +00:00
|
|
|
{
|
|
|
|
int i, b;
|
|
|
|
unsigned int ent_idx;
|
|
|
|
struct super_block *sb = inode->i_sb;
|
|
|
|
struct exfat_sb_info *sbi = EXFAT_SB(sb);
|
|
|
|
|
2022-05-16 23:13:08 +00:00
|
|
|
if (!is_valid_cluster(sbi, clu))
|
|
|
|
return -EINVAL;
|
|
|
|
|
2020-03-02 06:21:38 +00:00
|
|
|
ent_idx = CLUSTER_TO_BITMAP_ENT(clu);
|
|
|
|
i = BITMAP_OFFSET_SECTOR_INDEX(sb, ent_idx);
|
|
|
|
b = BITMAP_OFFSET_BIT_IN_SECTOR(sb, ent_idx);
|
|
|
|
|
|
|
|
set_bit_le(b, sbi->vol_amap[i]->b_data);
|
2021-03-15 04:12:55 +00:00
|
|
|
exfat_update_bh(sbi->vol_amap[i], sync);
|
2020-03-02 06:21:38 +00:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
exfat: improve performance of exfat_free_cluster when using dirsync mount option
There are stressful update of cluster allocation bitmap when using
dirsync mount option which is doing sync buffer on every cluster bit
clearing. This could result in performance degradation when deleting
big size file.
Fix to update only when the bitmap buffer index is changed would make
less disk access, improving performance especially for truncate operation.
Testing with Samsung 256GB sdcard, mounted with dirsync option
(mount -t exfat /dev/block/mmcblk0p1 /temp/mount -o dirsync)
Remove 4GB file, blktrace result.
[Before] : 39 secs.
Total (blktrace):
Reads Queued: 0, 0KiB Writes Queued: 32775, 16387KiB
Read Dispatches: 0, 0KiB Write Dispatches: 32775, 16387KiB
Reads Requeued: 0 Writes Requeued: 0
Reads Completed: 0, 0KiB Writes Completed: 32775, 16387KiB
Read Merges: 0, 0KiB Write Merges: 0, 0KiB
IO unplugs: 2 Timer unplugs: 0
[After] : 1 sec.
Total (blktrace):
Reads Queued: 0, 0KiB Writes Queued: 13, 6KiB
Read Dispatches: 0, 0KiB Write Dispatches: 13, 6KiB
Reads Requeued: 0 Writes Requeued: 0
Reads Completed: 0, 0KiB Writes Completed: 13, 6KiB
Read Merges: 0, 0KiB Write Merges: 0, 0KiB
IO unplugs: 1 Timer unplugs: 0
Signed-off-by: Hyeongseok Kim <hyeongseok@gmail.com>
Acked-by: Sungjong Seo <sj1557.seo@samsung.com>
Signed-off-by: Namjae Jeon <namjae.jeon@samsung.com>
2021-02-01 01:02:46 +00:00
|
|
|
void exfat_clear_bitmap(struct inode *inode, unsigned int clu, bool sync)
|
2020-03-02 06:21:38 +00:00
|
|
|
{
|
|
|
|
int i, b;
|
|
|
|
unsigned int ent_idx;
|
|
|
|
struct super_block *sb = inode->i_sb;
|
|
|
|
struct exfat_sb_info *sbi = EXFAT_SB(sb);
|
|
|
|
struct exfat_mount_options *opts = &sbi->options;
|
|
|
|
|
2022-05-16 23:13:08 +00:00
|
|
|
if (!is_valid_cluster(sbi, clu))
|
|
|
|
return;
|
|
|
|
|
2020-03-02 06:21:38 +00:00
|
|
|
ent_idx = CLUSTER_TO_BITMAP_ENT(clu);
|
|
|
|
i = BITMAP_OFFSET_SECTOR_INDEX(sb, ent_idx);
|
|
|
|
b = BITMAP_OFFSET_BIT_IN_SECTOR(sb, ent_idx);
|
|
|
|
|
|
|
|
clear_bit_le(b, sbi->vol_amap[i]->b_data);
|
exfat: improve performance of exfat_free_cluster when using dirsync mount option
There are stressful update of cluster allocation bitmap when using
dirsync mount option which is doing sync buffer on every cluster bit
clearing. This could result in performance degradation when deleting
big size file.
Fix to update only when the bitmap buffer index is changed would make
less disk access, improving performance especially for truncate operation.
Testing with Samsung 256GB sdcard, mounted with dirsync option
(mount -t exfat /dev/block/mmcblk0p1 /temp/mount -o dirsync)
Remove 4GB file, blktrace result.
[Before] : 39 secs.
Total (blktrace):
Reads Queued: 0, 0KiB Writes Queued: 32775, 16387KiB
Read Dispatches: 0, 0KiB Write Dispatches: 32775, 16387KiB
Reads Requeued: 0 Writes Requeued: 0
Reads Completed: 0, 0KiB Writes Completed: 32775, 16387KiB
Read Merges: 0, 0KiB Write Merges: 0, 0KiB
IO unplugs: 2 Timer unplugs: 0
[After] : 1 sec.
Total (blktrace):
Reads Queued: 0, 0KiB Writes Queued: 13, 6KiB
Read Dispatches: 0, 0KiB Write Dispatches: 13, 6KiB
Reads Requeued: 0 Writes Requeued: 0
Reads Completed: 0, 0KiB Writes Completed: 13, 6KiB
Read Merges: 0, 0KiB Write Merges: 0, 0KiB
IO unplugs: 1 Timer unplugs: 0
Signed-off-by: Hyeongseok Kim <hyeongseok@gmail.com>
Acked-by: Sungjong Seo <sj1557.seo@samsung.com>
Signed-off-by: Namjae Jeon <namjae.jeon@samsung.com>
2021-02-01 01:02:46 +00:00
|
|
|
exfat_update_bh(sbi->vol_amap[i], sync);
|
2020-03-02 06:21:38 +00:00
|
|
|
|
|
|
|
if (opts->discard) {
|
|
|
|
int ret_discard;
|
|
|
|
|
|
|
|
ret_discard = sb_issue_discard(sb,
|
2021-03-04 00:15:34 +00:00
|
|
|
exfat_cluster_to_sector(sbi, clu),
|
2020-03-02 06:21:38 +00:00
|
|
|
(1 << sbi->sect_per_clus_bits), GFP_NOFS, 0);
|
|
|
|
|
|
|
|
if (ret_discard == -EOPNOTSUPP) {
|
2020-04-24 04:31:12 +00:00
|
|
|
exfat_err(sb, "discard not supported by device, disabling");
|
2020-03-02 06:21:38 +00:00
|
|
|
opts->discard = 0;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* If the value of "clu" is 0, it means cluster 2 which is the first cluster of
|
|
|
|
* the cluster heap.
|
|
|
|
*/
|
|
|
|
unsigned int exfat_find_free_bitmap(struct super_block *sb, unsigned int clu)
|
|
|
|
{
|
|
|
|
unsigned int i, map_i, map_b, ent_idx;
|
|
|
|
unsigned int clu_base, clu_free;
|
2023-12-07 23:47:01 +00:00
|
|
|
unsigned long clu_bits, clu_mask;
|
2020-03-02 06:21:38 +00:00
|
|
|
struct exfat_sb_info *sbi = EXFAT_SB(sb);
|
2023-12-07 23:47:01 +00:00
|
|
|
__le_long bitval;
|
2020-03-02 06:21:38 +00:00
|
|
|
|
|
|
|
WARN_ON(clu < EXFAT_FIRST_CLUSTER);
|
2023-12-07 23:47:01 +00:00
|
|
|
ent_idx = ALIGN_DOWN(CLUSTER_TO_BITMAP_ENT(clu), BITS_PER_LONG);
|
|
|
|
clu_base = BITMAP_ENT_TO_CLUSTER(ent_idx);
|
2020-03-02 06:21:38 +00:00
|
|
|
clu_mask = IGNORED_BITS_REMAINED(clu, clu_base);
|
|
|
|
|
|
|
|
map_i = BITMAP_OFFSET_SECTOR_INDEX(sb, ent_idx);
|
|
|
|
map_b = BITMAP_OFFSET_BYTE_IN_SECTOR(sb, ent_idx);
|
|
|
|
|
|
|
|
for (i = EXFAT_FIRST_CLUSTER; i < sbi->num_clusters;
|
2023-12-07 23:47:01 +00:00
|
|
|
i += BITS_PER_LONG) {
|
|
|
|
bitval = *(__le_long *)(sbi->vol_amap[map_i]->b_data + map_b);
|
2020-03-02 06:21:38 +00:00
|
|
|
if (clu_mask > 0) {
|
2023-12-07 23:47:01 +00:00
|
|
|
bitval |= cpu_to_lel(clu_mask);
|
2020-03-02 06:21:38 +00:00
|
|
|
clu_mask = 0;
|
|
|
|
}
|
2023-12-07 23:47:01 +00:00
|
|
|
if (lel_to_cpu(bitval) != ULONG_MAX) {
|
|
|
|
clu_bits = lel_to_cpu(bitval);
|
|
|
|
clu_free = clu_base + ffz(clu_bits);
|
2020-03-02 06:21:38 +00:00
|
|
|
if (clu_free < sbi->num_clusters)
|
|
|
|
return clu_free;
|
|
|
|
}
|
2023-12-07 23:47:01 +00:00
|
|
|
clu_base += BITS_PER_LONG;
|
|
|
|
map_b += sizeof(long);
|
2020-03-02 06:21:38 +00:00
|
|
|
|
2023-12-07 23:47:01 +00:00
|
|
|
if (map_b >= sb->s_blocksize ||
|
2020-03-02 06:21:38 +00:00
|
|
|
clu_base >= sbi->num_clusters) {
|
|
|
|
if (++map_i >= sbi->map_sectors) {
|
|
|
|
clu_base = EXFAT_FIRST_CLUSTER;
|
|
|
|
map_i = 0;
|
|
|
|
}
|
|
|
|
map_b = 0;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return EXFAT_EOF_CLUSTER;
|
|
|
|
}
|
|
|
|
|
|
|
|
int exfat_count_used_clusters(struct super_block *sb, unsigned int *ret_count)
|
|
|
|
{
|
|
|
|
struct exfat_sb_info *sbi = EXFAT_SB(sb);
|
|
|
|
unsigned int count = 0;
|
|
|
|
unsigned int i, map_i = 0, map_b = 0;
|
|
|
|
unsigned int total_clus = EXFAT_DATA_CLUSTER_COUNT(sbi);
|
2023-12-05 15:58:37 +00:00
|
|
|
unsigned int last_mask = total_clus & (BITS_PER_LONG - 1);
|
|
|
|
unsigned long *bitmap, clu_bits;
|
2020-03-02 06:21:38 +00:00
|
|
|
|
|
|
|
total_clus &= ~last_mask;
|
2023-12-05 15:58:37 +00:00
|
|
|
for (i = 0; i < total_clus; i += BITS_PER_LONG) {
|
|
|
|
bitmap = (void *)(sbi->vol_amap[map_i]->b_data + map_b);
|
|
|
|
count += hweight_long(*bitmap);
|
|
|
|
map_b += sizeof(long);
|
|
|
|
if (map_b >= (unsigned int)sb->s_blocksize) {
|
2020-03-02 06:21:38 +00:00
|
|
|
map_i++;
|
|
|
|
map_b = 0;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (last_mask) {
|
2023-12-05 15:58:37 +00:00
|
|
|
bitmap = (void *)(sbi->vol_amap[map_i]->b_data + map_b);
|
|
|
|
clu_bits = lel_to_cpu(*(__le_long *)bitmap);
|
|
|
|
count += hweight_long(clu_bits & BITMAP_LAST_WORD_MASK(last_mask));
|
2020-03-02 06:21:38 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
*ret_count = count;
|
|
|
|
return 0;
|
|
|
|
}
|
2021-03-04 00:20:35 +00:00
|
|
|
|
|
|
|
int exfat_trim_fs(struct inode *inode, struct fstrim_range *range)
|
|
|
|
{
|
|
|
|
unsigned int trim_begin, trim_end, count, next_free_clu;
|
|
|
|
u64 clu_start, clu_end, trim_minlen, trimmed_total = 0;
|
|
|
|
struct super_block *sb = inode->i_sb;
|
|
|
|
struct exfat_sb_info *sbi = EXFAT_SB(sb);
|
|
|
|
int err = 0;
|
|
|
|
|
|
|
|
clu_start = max_t(u64, range->start >> sbi->cluster_size_bits,
|
|
|
|
EXFAT_FIRST_CLUSTER);
|
|
|
|
clu_end = clu_start + (range->len >> sbi->cluster_size_bits) - 1;
|
|
|
|
trim_minlen = range->minlen >> sbi->cluster_size_bits;
|
|
|
|
|
|
|
|
if (clu_start >= sbi->num_clusters || range->len < sbi->cluster_size)
|
|
|
|
return -EINVAL;
|
|
|
|
|
|
|
|
if (clu_end >= sbi->num_clusters)
|
|
|
|
clu_end = sbi->num_clusters - 1;
|
|
|
|
|
|
|
|
mutex_lock(&sbi->bitmap_lock);
|
|
|
|
|
|
|
|
trim_begin = trim_end = exfat_find_free_bitmap(sb, clu_start);
|
|
|
|
if (trim_begin == EXFAT_EOF_CLUSTER)
|
|
|
|
goto unlock;
|
|
|
|
|
|
|
|
next_free_clu = exfat_find_free_bitmap(sb, trim_end + 1);
|
|
|
|
if (next_free_clu == EXFAT_EOF_CLUSTER)
|
|
|
|
goto unlock;
|
|
|
|
|
|
|
|
do {
|
|
|
|
if (next_free_clu == trim_end + 1) {
|
|
|
|
/* extend trim range for continuous free cluster */
|
|
|
|
trim_end++;
|
|
|
|
} else {
|
|
|
|
/* trim current range if it's larger than trim_minlen */
|
|
|
|
count = trim_end - trim_begin + 1;
|
|
|
|
if (count >= trim_minlen) {
|
|
|
|
err = sb_issue_discard(sb,
|
|
|
|
exfat_cluster_to_sector(sbi, trim_begin),
|
|
|
|
count * sbi->sect_per_clus, GFP_NOFS, 0);
|
|
|
|
if (err)
|
|
|
|
goto unlock;
|
|
|
|
|
|
|
|
trimmed_total += count;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* set next start point of the free hole */
|
|
|
|
trim_begin = trim_end = next_free_clu;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (next_free_clu >= clu_end)
|
|
|
|
break;
|
|
|
|
|
|
|
|
if (fatal_signal_pending(current)) {
|
|
|
|
err = -ERESTARTSYS;
|
|
|
|
goto unlock;
|
|
|
|
}
|
|
|
|
|
|
|
|
next_free_clu = exfat_find_free_bitmap(sb, next_free_clu + 1);
|
|
|
|
} while (next_free_clu != EXFAT_EOF_CLUSTER &&
|
|
|
|
next_free_clu > trim_end);
|
|
|
|
|
|
|
|
/* try to trim remainder */
|
|
|
|
count = trim_end - trim_begin + 1;
|
|
|
|
if (count >= trim_minlen) {
|
|
|
|
err = sb_issue_discard(sb, exfat_cluster_to_sector(sbi, trim_begin),
|
|
|
|
count * sbi->sect_per_clus, GFP_NOFS, 0);
|
|
|
|
if (err)
|
|
|
|
goto unlock;
|
|
|
|
|
|
|
|
trimmed_total += count;
|
|
|
|
}
|
|
|
|
|
|
|
|
unlock:
|
|
|
|
mutex_unlock(&sbi->bitmap_lock);
|
|
|
|
range->len = trimmed_total << sbi->cluster_size_bits;
|
|
|
|
|
|
|
|
return err;
|
|
|
|
}
|