root/fs/f2fs/shrinker.c
// SPDX-License-Identifier: GPL-2.0
/*
 * f2fs shrinker support
 *   the basic infra was copied from fs/ubifs/shrinker.c
 *
 * Copyright (c) 2015 Motorola Mobility
 * Copyright (c) 2015 Jaegeuk Kim <jaegeuk@kernel.org>
 */
#include <linux/fs.h>
#include <linux/f2fs_fs.h>

#include "f2fs.h"
#include "node.h"

static LIST_HEAD(f2fs_list);
static DEFINE_SPINLOCK(f2fs_list_lock);
static unsigned int shrinker_run_no;

static unsigned long __count_nat_entries(struct f2fs_sb_info *sbi)
{
        return NM_I(sbi)->nat_cnt[RECLAIMABLE_NAT];
}

static unsigned long __count_free_nids(struct f2fs_sb_info *sbi)
{
        long count = NM_I(sbi)->nid_cnt[FREE_NID] - MAX_FREE_NIDS;

        return count > 0 ? count : 0;
}

static unsigned long __count_extent_cache(struct f2fs_sb_info *sbi,
                                        enum extent_type type)
{
        struct extent_tree_info *eti = &sbi->extent_tree[type];

        return atomic_read(&eti->total_zombie_tree) +
                                atomic_read(&eti->total_ext_node);
}

unsigned long f2fs_shrink_count(struct shrinker *shrink,
                                struct shrink_control *sc)
{
        struct f2fs_sb_info *sbi;
        struct list_head *p;
        unsigned long count = 0;

        spin_lock(&f2fs_list_lock);
        p = f2fs_list.next;
        while (p != &f2fs_list) {
                sbi = list_entry(p, struct f2fs_sb_info, s_list);

                /* stop f2fs_put_super */
                if (!mutex_trylock(&sbi->umount_mutex)) {
                        p = p->next;
                        continue;
                }
                spin_unlock(&f2fs_list_lock);

                /* count read extent cache entries */
                count += __count_extent_cache(sbi, EX_READ);

                /* count block age extent cache entries */
                count += __count_extent_cache(sbi, EX_BLOCK_AGE);

                /* count clean nat cache entries */
                count += __count_nat_entries(sbi);

                /* count free nids cache entries */
                count += __count_free_nids(sbi);

                spin_lock(&f2fs_list_lock);
                p = p->next;
                mutex_unlock(&sbi->umount_mutex);
        }
        spin_unlock(&f2fs_list_lock);
        return count ?: SHRINK_EMPTY;
}

unsigned long f2fs_shrink_scan(struct shrinker *shrink,
                                struct shrink_control *sc)
{
        unsigned long nr = sc->nr_to_scan;
        struct f2fs_sb_info *sbi;
        struct list_head *p;
        unsigned int run_no;
        unsigned long freed = 0;

        spin_lock(&f2fs_list_lock);
        do {
                run_no = ++shrinker_run_no;
        } while (run_no == 0);
        p = f2fs_list.next;
        while (p != &f2fs_list) {
                sbi = list_entry(p, struct f2fs_sb_info, s_list);

                if (sbi->shrinker_run_no == run_no)
                        break;

                /* stop f2fs_put_super */
                if (!mutex_trylock(&sbi->umount_mutex)) {
                        p = p->next;
                        continue;
                }
                spin_unlock(&f2fs_list_lock);

                sbi->shrinker_run_no = run_no;

                /* shrink extent cache entries */
                freed += f2fs_shrink_age_extent_tree(sbi, nr >> 2);

                /* shrink read extent cache entries */
                freed += f2fs_shrink_read_extent_tree(sbi, nr >> 2);

                /* shrink clean nat cache entries */
                if (freed < nr)
                        freed += f2fs_try_to_free_nats(sbi, nr - freed);

                /* shrink free nids cache entries */
                if (freed < nr)
                        freed += f2fs_try_to_free_nids(sbi, nr - freed);

                spin_lock(&f2fs_list_lock);
                p = p->next;
                list_move_tail(&sbi->s_list, &f2fs_list);
                mutex_unlock(&sbi->umount_mutex);
                if (freed >= nr)
                        break;
        }
        spin_unlock(&f2fs_list_lock);
        return freed;
}

unsigned int f2fs_donate_files(void)
{
        struct f2fs_sb_info *sbi;
        struct list_head *p;
        unsigned int donate_files = 0;

        spin_lock(&f2fs_list_lock);
        p = f2fs_list.next;
        while (p != &f2fs_list) {
                sbi = list_entry(p, struct f2fs_sb_info, s_list);

                /* stop f2fs_put_super */
                if (!mutex_trylock(&sbi->umount_mutex)) {
                        p = p->next;
                        continue;
                }
                spin_unlock(&f2fs_list_lock);

                donate_files += sbi->donate_files;

                spin_lock(&f2fs_list_lock);
                p = p->next;
                mutex_unlock(&sbi->umount_mutex);
        }
        spin_unlock(&f2fs_list_lock);

        return donate_files;
}

static unsigned int do_reclaim_caches(struct f2fs_sb_info *sbi,
                                unsigned int reclaim_caches_kb)
{
        struct inode *inode;
        struct f2fs_inode_info *fi;
        unsigned int nfiles = sbi->donate_files;
        pgoff_t npages = reclaim_caches_kb >> (PAGE_SHIFT - 10);

        while (npages && nfiles--) {
                pgoff_t len;

                spin_lock(&sbi->inode_lock[DONATE_INODE]);
                if (list_empty(&sbi->inode_list[DONATE_INODE])) {
                        spin_unlock(&sbi->inode_lock[DONATE_INODE]);
                        break;
                }
                fi = list_first_entry(&sbi->inode_list[DONATE_INODE],
                                        struct f2fs_inode_info, gdonate_list);
                list_move_tail(&fi->gdonate_list, &sbi->inode_list[DONATE_INODE]);
                inode = igrab(&fi->vfs_inode);
                spin_unlock(&sbi->inode_lock[DONATE_INODE]);

                if (!inode)
                        continue;

                inode_lock(inode);
                if (!is_inode_flag_set(inode, FI_DONATE_FINISHED)) {
                        len = fi->donate_end - fi->donate_start + 1;
                        npages = npages < len ? 0 : npages - len;

                        invalidate_inode_pages2_range(inode->i_mapping,
                                        fi->donate_start, fi->donate_end);
                        set_inode_flag(inode, FI_DONATE_FINISHED);
                }
                inode_unlock(inode);

                iput(inode);
                cond_resched();
        }
        return npages << (PAGE_SHIFT - 10);
}

void f2fs_reclaim_caches(unsigned int reclaim_caches_kb)
{
        struct f2fs_sb_info *sbi;
        struct list_head *p;

        spin_lock(&f2fs_list_lock);
        p = f2fs_list.next;
        while (p != &f2fs_list && reclaim_caches_kb) {
                sbi = list_entry(p, struct f2fs_sb_info, s_list);

                /* stop f2fs_put_super */
                if (!mutex_trylock(&sbi->umount_mutex)) {
                        p = p->next;
                        continue;
                }
                spin_unlock(&f2fs_list_lock);

                reclaim_caches_kb = do_reclaim_caches(sbi, reclaim_caches_kb);

                spin_lock(&f2fs_list_lock);
                p = p->next;
                mutex_unlock(&sbi->umount_mutex);
        }
        spin_unlock(&f2fs_list_lock);
}

void f2fs_join_shrinker(struct f2fs_sb_info *sbi)
{
        spin_lock(&f2fs_list_lock);
        list_add_tail(&sbi->s_list, &f2fs_list);
        spin_unlock(&f2fs_list_lock);
}

void f2fs_leave_shrinker(struct f2fs_sb_info *sbi)
{
        f2fs_shrink_read_extent_tree(sbi, __count_extent_cache(sbi, EX_READ));
        f2fs_shrink_age_extent_tree(sbi,
                                __count_extent_cache(sbi, EX_BLOCK_AGE));

        spin_lock(&f2fs_list_lock);
        list_del_init(&sbi->s_list);
        spin_unlock(&f2fs_list_lock);
}