aboutsummaryrefslogtreecommitdiffstats
path: root/fs/ext4
diff options
context:
space:
mode:
Diffstat (limited to 'fs/ext4')
-rw-r--r--fs/ext4/ext4.h2
-rw-r--r--fs/ext4/mballoc.c185
-rw-r--r--fs/ext4/super.c1
3 files changed, 188 insertions, 0 deletions
diff --git a/fs/ext4/ext4.h b/fs/ext4/ext4.h
index ca9fda64dd4f..98dde2b7dd6f 100644
--- a/fs/ext4/ext4.h
+++ b/fs/ext4/ext4.h
@@ -1694,6 +1694,8 @@ extern int ext4_mb_add_groupinfo(struct super_block *sb,
1694extern int ext4_mb_get_buddy_cache_lock(struct super_block *, ext4_group_t); 1694extern int ext4_mb_get_buddy_cache_lock(struct super_block *, ext4_group_t);
1695extern void ext4_mb_put_buddy_cache_lock(struct super_block *, 1695extern void ext4_mb_put_buddy_cache_lock(struct super_block *,
1696 ext4_group_t, int); 1696 ext4_group_t, int);
1697extern int ext4_trim_fs(struct super_block *, struct fstrim_range *);
1698
1697/* inode.c */ 1699/* inode.c */
1698struct buffer_head *ext4_getblk(handle_t *, struct inode *, 1700struct buffer_head *ext4_getblk(handle_t *, struct inode *,
1699 ext4_lblk_t, int, int *); 1701 ext4_lblk_t, int, int *);
diff --git a/fs/ext4/mballoc.c b/fs/ext4/mballoc.c
index 11c2eec386ef..e3bcc06b4906 100644
--- a/fs/ext4/mballoc.c
+++ b/fs/ext4/mballoc.c
@@ -4685,3 +4685,188 @@ error_return:
4685 ext4_std_error(sb, err); 4685 ext4_std_error(sb, err);
4686 return; 4686 return;
4687} 4687}
4688
4689/**
4690 * ext4_trim_extent -- function to TRIM one single free extent in the group
4691 * @sb: super block for the file system
4692 * @start: starting block of the free extent in the alloc. group
4693 * @count: number of blocks to TRIM
4694 * @group: alloc. group we are working with
4695 * @e4b: ext4 buddy for the group
4696 *
4697 * Trim "count" blocks starting at "start" in the "group". To assure that no
4698 * one will allocate those blocks, mark it as used in buddy bitmap. This must
4699 * be called with under the group lock.
4700 */
4701static int ext4_trim_extent(struct super_block *sb, int start, int count,
4702 ext4_group_t group, struct ext4_buddy *e4b)
4703{
4704 struct ext4_free_extent ex;
4705 int ret = 0;
4706
4707 assert_spin_locked(ext4_group_lock_ptr(sb, group));
4708
4709 ex.fe_start = start;
4710 ex.fe_group = group;
4711 ex.fe_len = count;
4712
4713 /*
4714 * Mark blocks used, so no one can reuse them while
4715 * being trimmed.
4716 */
4717 mb_mark_used(e4b, &ex);
4718 ext4_unlock_group(sb, group);
4719
4720 ret = ext4_issue_discard(sb, group, start, count);
4721 if (ret)
4722 ext4_std_error(sb, ret);
4723
4724 ext4_lock_group(sb, group);
4725 mb_free_blocks(NULL, e4b, start, ex.fe_len);
4726 return ret;
4727}
4728
4729/**
4730 * ext4_trim_all_free -- function to trim all free space in alloc. group
4731 * @sb: super block for file system
4732 * @e4b: ext4 buddy
4733 * @start: first group block to examine
4734 * @max: last group block to examine
4735 * @minblocks: minimum extent block count
4736 *
4737 * ext4_trim_all_free walks through group's buddy bitmap searching for free
4738 * extents. When the free block is found, ext4_trim_extent is called to TRIM
4739 * the extent.
4740 *
4741 *
4742 * ext4_trim_all_free walks through group's block bitmap searching for free
4743 * extents. When the free extent is found, mark it as used in group buddy
4744 * bitmap. Then issue a TRIM command on this extent and free the extent in
4745 * the group buddy bitmap. This is done until whole group is scanned.
4746 */
4747ext4_grpblk_t ext4_trim_all_free(struct super_block *sb, struct ext4_buddy *e4b,
4748 ext4_grpblk_t start, ext4_grpblk_t max, ext4_grpblk_t minblocks)
4749{
4750 void *bitmap;
4751 ext4_grpblk_t next, count = 0;
4752 ext4_group_t group;
4753 int ret = 0;
4754
4755 BUG_ON(e4b == NULL);
4756
4757 bitmap = e4b->bd_bitmap;
4758 group = e4b->bd_group;
4759 start = (e4b->bd_info->bb_first_free > start) ?
4760 e4b->bd_info->bb_first_free : start;
4761 ext4_lock_group(sb, group);
4762
4763 while (start < max) {
4764 start = mb_find_next_zero_bit(bitmap, max, start);
4765 if (start >= max)
4766 break;
4767 next = mb_find_next_bit(bitmap, max, start);
4768
4769 if ((next - start) >= minblocks) {
4770 ret = ext4_trim_extent(sb, start,
4771 next - start, group, e4b);
4772 if (ret < 0)
4773 break;
4774 count += next - start;
4775 }
4776 start = next + 1;
4777
4778 if (fatal_signal_pending(current)) {
4779 count = -ERESTARTSYS;
4780 break;
4781 }
4782
4783 if (need_resched()) {
4784 ext4_unlock_group(sb, group);
4785 cond_resched();
4786 ext4_lock_group(sb, group);
4787 }
4788
4789 if ((e4b->bd_info->bb_free - count) < minblocks)
4790 break;
4791 }
4792 ext4_unlock_group(sb, group);
4793
4794 ext4_debug("trimmed %d blocks in the group %d\n",
4795 count, group);
4796
4797 if (ret < 0)
4798 count = ret;
4799
4800 return count;
4801}
4802
4803/**
4804 * ext4_trim_fs() -- trim ioctl handle function
4805 * @sb: superblock for filesystem
4806 * @range: fstrim_range structure
4807 *
4808 * start: First Byte to trim
4809 * len: number of Bytes to trim from start
4810 * minlen: minimum extent length in Bytes
4811 * ext4_trim_fs goes through all allocation groups containing Bytes from
4812 * start to start+len. For each such a group ext4_trim_all_free function
4813 * is invoked to trim all free space.
4814 */
4815int ext4_trim_fs(struct super_block *sb, struct fstrim_range *range)
4816{
4817 struct ext4_buddy e4b;
4818 ext4_group_t first_group, last_group;
4819 ext4_group_t group, ngroups = ext4_get_groups_count(sb);
4820 ext4_grpblk_t cnt = 0, first_block, last_block;
4821 uint64_t start, len, minlen, trimmed;
4822 int ret = 0;
4823
4824 start = range->start >> sb->s_blocksize_bits;
4825 len = range->len >> sb->s_blocksize_bits;
4826 minlen = range->minlen >> sb->s_blocksize_bits;
4827 trimmed = 0;
4828
4829 if (unlikely(minlen > EXT4_BLOCKS_PER_GROUP(sb)))
4830 return -EINVAL;
4831
4832 /* Determine first and last group to examine based on start and len */
4833 ext4_get_group_no_and_offset(sb, (ext4_fsblk_t) start,
4834 &first_group, &first_block);
4835 ext4_get_group_no_and_offset(sb, (ext4_fsblk_t) (start + len),
4836 &last_group, &last_block);
4837 last_group = (last_group > ngroups - 1) ? ngroups - 1 : last_group;
4838 last_block = EXT4_BLOCKS_PER_GROUP(sb);
4839
4840 if (first_group > last_group)
4841 return -EINVAL;
4842
4843 for (group = first_group; group <= last_group; group++) {
4844 ret = ext4_mb_load_buddy(sb, group, &e4b);
4845 if (ret) {
4846 ext4_error(sb, "Error in loading buddy "
4847 "information for %u", group);
4848 break;
4849 }
4850
4851 if (len >= EXT4_BLOCKS_PER_GROUP(sb))
4852 len -= (EXT4_BLOCKS_PER_GROUP(sb) - first_block);
4853 else
4854 last_block = len;
4855
4856 if (e4b.bd_info->bb_free >= minlen) {
4857 cnt = ext4_trim_all_free(sb, &e4b, first_block,
4858 last_block, minlen);
4859 if (cnt < 0) {
4860 ret = cnt;
4861 ext4_mb_unload_buddy(&e4b);
4862 break;
4863 }
4864 }
4865 ext4_mb_unload_buddy(&e4b);
4866 trimmed += cnt;
4867 first_block = 0;
4868 }
4869 range->len = trimmed * sb->s_blocksize;
4870
4871 return ret;
4872}
diff --git a/fs/ext4/super.c b/fs/ext4/super.c
index e13b3c3534d7..01e60aa6c478 100644
--- a/fs/ext4/super.c
+++ b/fs/ext4/super.c
@@ -1189,6 +1189,7 @@ static const struct super_operations ext4_sops = {
1189 .quota_write = ext4_quota_write, 1189 .quota_write = ext4_quota_write,
1190#endif 1190#endif
1191 .bdev_try_to_free_page = bdev_try_to_free_page, 1191 .bdev_try_to_free_page = bdev_try_to_free_page,
1192 .trim_fs = ext4_trim_fs
1192}; 1193};
1193 1194
1194static const struct super_operations ext4_nojournal_sops = { 1195static const struct super_operations ext4_nojournal_sops = {