diff options
| -rw-r--r-- | fs/ext4/ext4.h | 2 | ||||
| -rw-r--r-- | fs/ext4/mballoc.c | 185 | ||||
| -rw-r--r-- | fs/ext4/super.c | 1 |
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, | |||
| 1694 | extern int ext4_mb_get_buddy_cache_lock(struct super_block *, ext4_group_t); | 1694 | extern int ext4_mb_get_buddy_cache_lock(struct super_block *, ext4_group_t); |
| 1695 | extern void ext4_mb_put_buddy_cache_lock(struct super_block *, | 1695 | extern void ext4_mb_put_buddy_cache_lock(struct super_block *, |
| 1696 | ext4_group_t, int); | 1696 | ext4_group_t, int); |
| 1697 | extern int ext4_trim_fs(struct super_block *, struct fstrim_range *); | ||
| 1698 | |||
| 1697 | /* inode.c */ | 1699 | /* inode.c */ |
| 1698 | struct buffer_head *ext4_getblk(handle_t *, struct inode *, | 1700 | struct 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 | */ | ||
| 4701 | static 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 | */ | ||
| 4747 | ext4_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 | */ | ||
| 4815 | int 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 | ||
| 1194 | static const struct super_operations ext4_nojournal_sops = { | 1195 | static const struct super_operations ext4_nojournal_sops = { |
