exfat: add support ioctl and FITRIM function

Add FITRIM ioctl to enable discarding unused blocks while mounted.
As current exFAT doesn't have generic ioctl handler, add empty ioctl
function first, and add FITRIM handler.

Signed-off-by: Hyeongseok Kim <hyeongseok@gmail.com>
Reviewed-by: Chaitanya Kulkarni <chaitanya.kulkarni@wdc.com>
Acked-by: Sungjong Seo <sj1557.seo@samsung.com>
Signed-off-by: Namjae Jeon <namjae.jeon@samsung.com>
This commit is contained in:
Hyeongseok Kim 2021-03-04 09:20:35 +09:00 committed by Namjae Jeon
parent 5c2d728507
commit 654762df2e
4 changed files with 142 additions and 0 deletions

View File

@ -264,3 +264,83 @@ int exfat_count_used_clusters(struct super_block *sb, unsigned int *ret_count)
*ret_count = count;
return 0;
}
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;
}

View File

@ -4,6 +4,7 @@
*/
#include <linux/slab.h>
#include <linux/compat.h>
#include <linux/bio.h>
#include <linux/buffer_head.h>
@ -306,6 +307,10 @@ const struct file_operations exfat_dir_operations = {
.llseek = generic_file_llseek,
.read = generic_read_dir,
.iterate = exfat_iterate,
.unlocked_ioctl = exfat_ioctl,
#ifdef CONFIG_COMPAT
.compat_ioctl = exfat_compat_ioctl,
#endif
.fsync = exfat_file_fsync,
};

View File

@ -412,6 +412,7 @@ int exfat_set_bitmap(struct inode *inode, unsigned int clu);
void exfat_clear_bitmap(struct inode *inode, unsigned int clu, bool sync);
unsigned int exfat_find_free_bitmap(struct super_block *sb, unsigned int clu);
int exfat_count_used_clusters(struct super_block *sb, unsigned int *ret_count);
int exfat_trim_fs(struct inode *inode, struct fstrim_range *range);
/* file.c */
extern const struct file_operations exfat_file_operations;
@ -423,6 +424,9 @@ int exfat_getattr(struct user_namespace *mnt_userns, const struct path *path,
struct kstat *stat, unsigned int request_mask,
unsigned int query_flags);
int exfat_file_fsync(struct file *file, loff_t start, loff_t end, int datasync);
long exfat_ioctl(struct file *filp, unsigned int cmd, unsigned long arg);
long exfat_compat_ioctl(struct file *filp, unsigned int cmd,
unsigned long arg);
/* namei.c */
extern const struct dentry_operations exfat_dentry_ops;

View File

@ -4,6 +4,7 @@
*/
#include <linux/slab.h>
#include <linux/compat.h>
#include <linux/cred.h>
#include <linux/buffer_head.h>
#include <linux/blkdev.h>
@ -350,6 +351,54 @@ out:
return error;
}
static int exfat_ioctl_fitrim(struct inode *inode, unsigned long arg)
{
struct request_queue *q = bdev_get_queue(inode->i_sb->s_bdev);
struct fstrim_range range;
int ret = 0;
if (!capable(CAP_SYS_ADMIN))
return -EPERM;
if (!blk_queue_discard(q))
return -EOPNOTSUPP;
if (copy_from_user(&range, (struct fstrim_range __user *)arg, sizeof(range)))
return -EFAULT;
range.minlen = max_t(unsigned int, range.minlen,
q->limits.discard_granularity);
ret = exfat_trim_fs(inode, &range);
if (ret < 0)
return ret;
if (copy_to_user((struct fstrim_range __user *)arg, &range, sizeof(range)))
return -EFAULT;
return 0;
}
long exfat_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
{
struct inode *inode = file_inode(filp);
switch (cmd) {
case FITRIM:
return exfat_ioctl_fitrim(inode, arg);
default:
return -ENOTTY;
}
}
#ifdef CONFIG_COMPAT
long exfat_compat_ioctl(struct file *filp, unsigned int cmd,
unsigned long arg)
{
return exfat_ioctl(filp, cmd, (unsigned long)compat_ptr(arg));
}
#endif
int exfat_file_fsync(struct file *filp, loff_t start, loff_t end, int datasync)
{
struct inode *inode = filp->f_mapping->host;
@ -370,6 +419,10 @@ const struct file_operations exfat_file_operations = {
.llseek = generic_file_llseek,
.read_iter = generic_file_read_iter,
.write_iter = generic_file_write_iter,
.unlocked_ioctl = exfat_ioctl,
#ifdef CONFIG_COMPAT
.compat_ioctl = exfat_compat_ioctl,
#endif
.mmap = generic_file_mmap,
.fsync = exfat_file_fsync,
.splice_read = generic_file_splice_read,