Skip to content

Commit 654762d

Browse files
hyeongseok-kim901namjaejeon
authored andcommitted
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 <[email protected]> Reviewed-by: Chaitanya Kulkarni <[email protected]> Acked-by: Sungjong Seo <[email protected]> Signed-off-by: Namjae Jeon <[email protected]>
1 parent 5c2d728 commit 654762d

File tree

4 files changed

+142
-0
lines changed

4 files changed

+142
-0
lines changed

fs/exfat/balloc.c

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -264,3 +264,83 @@ int exfat_count_used_clusters(struct super_block *sb, unsigned int *ret_count)
264264
*ret_count = count;
265265
return 0;
266266
}
267+
268+
int exfat_trim_fs(struct inode *inode, struct fstrim_range *range)
269+
{
270+
unsigned int trim_begin, trim_end, count, next_free_clu;
271+
u64 clu_start, clu_end, trim_minlen, trimmed_total = 0;
272+
struct super_block *sb = inode->i_sb;
273+
struct exfat_sb_info *sbi = EXFAT_SB(sb);
274+
int err = 0;
275+
276+
clu_start = max_t(u64, range->start >> sbi->cluster_size_bits,
277+
EXFAT_FIRST_CLUSTER);
278+
clu_end = clu_start + (range->len >> sbi->cluster_size_bits) - 1;
279+
trim_minlen = range->minlen >> sbi->cluster_size_bits;
280+
281+
if (clu_start >= sbi->num_clusters || range->len < sbi->cluster_size)
282+
return -EINVAL;
283+
284+
if (clu_end >= sbi->num_clusters)
285+
clu_end = sbi->num_clusters - 1;
286+
287+
mutex_lock(&sbi->bitmap_lock);
288+
289+
trim_begin = trim_end = exfat_find_free_bitmap(sb, clu_start);
290+
if (trim_begin == EXFAT_EOF_CLUSTER)
291+
goto unlock;
292+
293+
next_free_clu = exfat_find_free_bitmap(sb, trim_end + 1);
294+
if (next_free_clu == EXFAT_EOF_CLUSTER)
295+
goto unlock;
296+
297+
do {
298+
if (next_free_clu == trim_end + 1) {
299+
/* extend trim range for continuous free cluster */
300+
trim_end++;
301+
} else {
302+
/* trim current range if it's larger than trim_minlen */
303+
count = trim_end - trim_begin + 1;
304+
if (count >= trim_minlen) {
305+
err = sb_issue_discard(sb,
306+
exfat_cluster_to_sector(sbi, trim_begin),
307+
count * sbi->sect_per_clus, GFP_NOFS, 0);
308+
if (err)
309+
goto unlock;
310+
311+
trimmed_total += count;
312+
}
313+
314+
/* set next start point of the free hole */
315+
trim_begin = trim_end = next_free_clu;
316+
}
317+
318+
if (next_free_clu >= clu_end)
319+
break;
320+
321+
if (fatal_signal_pending(current)) {
322+
err = -ERESTARTSYS;
323+
goto unlock;
324+
}
325+
326+
next_free_clu = exfat_find_free_bitmap(sb, next_free_clu + 1);
327+
} while (next_free_clu != EXFAT_EOF_CLUSTER &&
328+
next_free_clu > trim_end);
329+
330+
/* try to trim remainder */
331+
count = trim_end - trim_begin + 1;
332+
if (count >= trim_minlen) {
333+
err = sb_issue_discard(sb, exfat_cluster_to_sector(sbi, trim_begin),
334+
count * sbi->sect_per_clus, GFP_NOFS, 0);
335+
if (err)
336+
goto unlock;
337+
338+
trimmed_total += count;
339+
}
340+
341+
unlock:
342+
mutex_unlock(&sbi->bitmap_lock);
343+
range->len = trimmed_total << sbi->cluster_size_bits;
344+
345+
return err;
346+
}

fs/exfat/dir.c

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
*/
55

66
#include <linux/slab.h>
7+
#include <linux/compat.h>
78
#include <linux/bio.h>
89
#include <linux/buffer_head.h>
910

@@ -306,6 +307,10 @@ const struct file_operations exfat_dir_operations = {
306307
.llseek = generic_file_llseek,
307308
.read = generic_read_dir,
308309
.iterate = exfat_iterate,
310+
.unlocked_ioctl = exfat_ioctl,
311+
#ifdef CONFIG_COMPAT
312+
.compat_ioctl = exfat_compat_ioctl,
313+
#endif
309314
.fsync = exfat_file_fsync,
310315
};
311316

fs/exfat/exfat_fs.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -412,6 +412,7 @@ int exfat_set_bitmap(struct inode *inode, unsigned int clu);
412412
void exfat_clear_bitmap(struct inode *inode, unsigned int clu, bool sync);
413413
unsigned int exfat_find_free_bitmap(struct super_block *sb, unsigned int clu);
414414
int exfat_count_used_clusters(struct super_block *sb, unsigned int *ret_count);
415+
int exfat_trim_fs(struct inode *inode, struct fstrim_range *range);
415416

416417
/* file.c */
417418
extern const struct file_operations exfat_file_operations;
@@ -423,6 +424,9 @@ int exfat_getattr(struct user_namespace *mnt_userns, const struct path *path,
423424
struct kstat *stat, unsigned int request_mask,
424425
unsigned int query_flags);
425426
int exfat_file_fsync(struct file *file, loff_t start, loff_t end, int datasync);
427+
long exfat_ioctl(struct file *filp, unsigned int cmd, unsigned long arg);
428+
long exfat_compat_ioctl(struct file *filp, unsigned int cmd,
429+
unsigned long arg);
426430

427431
/* namei.c */
428432
extern const struct dentry_operations exfat_dentry_ops;

fs/exfat/file.c

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
*/
55

66
#include <linux/slab.h>
7+
#include <linux/compat.h>
78
#include <linux/cred.h>
89
#include <linux/buffer_head.h>
910
#include <linux/blkdev.h>
@@ -350,6 +351,54 @@ int exfat_setattr(struct user_namespace *mnt_userns, struct dentry *dentry,
350351
return error;
351352
}
352353

354+
static int exfat_ioctl_fitrim(struct inode *inode, unsigned long arg)
355+
{
356+
struct request_queue *q = bdev_get_queue(inode->i_sb->s_bdev);
357+
struct fstrim_range range;
358+
int ret = 0;
359+
360+
if (!capable(CAP_SYS_ADMIN))
361+
return -EPERM;
362+
363+
if (!blk_queue_discard(q))
364+
return -EOPNOTSUPP;
365+
366+
if (copy_from_user(&range, (struct fstrim_range __user *)arg, sizeof(range)))
367+
return -EFAULT;
368+
369+
range.minlen = max_t(unsigned int, range.minlen,
370+
q->limits.discard_granularity);
371+
372+
ret = exfat_trim_fs(inode, &range);
373+
if (ret < 0)
374+
return ret;
375+
376+
if (copy_to_user((struct fstrim_range __user *)arg, &range, sizeof(range)))
377+
return -EFAULT;
378+
379+
return 0;
380+
}
381+
382+
long exfat_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
383+
{
384+
struct inode *inode = file_inode(filp);
385+
386+
switch (cmd) {
387+
case FITRIM:
388+
return exfat_ioctl_fitrim(inode, arg);
389+
default:
390+
return -ENOTTY;
391+
}
392+
}
393+
394+
#ifdef CONFIG_COMPAT
395+
long exfat_compat_ioctl(struct file *filp, unsigned int cmd,
396+
unsigned long arg)
397+
{
398+
return exfat_ioctl(filp, cmd, (unsigned long)compat_ptr(arg));
399+
}
400+
#endif
401+
353402
int exfat_file_fsync(struct file *filp, loff_t start, loff_t end, int datasync)
354403
{
355404
struct inode *inode = filp->f_mapping->host;
@@ -370,6 +419,10 @@ const struct file_operations exfat_file_operations = {
370419
.llseek = generic_file_llseek,
371420
.read_iter = generic_file_read_iter,
372421
.write_iter = generic_file_write_iter,
422+
.unlocked_ioctl = exfat_ioctl,
423+
#ifdef CONFIG_COMPAT
424+
.compat_ioctl = exfat_compat_ioctl,
425+
#endif
373426
.mmap = generic_file_mmap,
374427
.fsync = exfat_file_fsync,
375428
.splice_read = generic_file_splice_read,

0 commit comments

Comments
 (0)