162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only
262306a36Sopenharmony_ci/*
362306a36Sopenharmony_ci * Copyright (C) 2018 HUAWEI, Inc.
462306a36Sopenharmony_ci *             https://www.huawei.com/
562306a36Sopenharmony_ci */
662306a36Sopenharmony_ci#include "internal.h"
762306a36Sopenharmony_ci
862306a36Sopenharmony_cistruct page *erofs_allocpage(struct page **pagepool, gfp_t gfp)
962306a36Sopenharmony_ci{
1062306a36Sopenharmony_ci	struct page *page = *pagepool;
1162306a36Sopenharmony_ci
1262306a36Sopenharmony_ci	if (page) {
1362306a36Sopenharmony_ci		DBG_BUGON(page_ref_count(page) != 1);
1462306a36Sopenharmony_ci		*pagepool = (struct page *)page_private(page);
1562306a36Sopenharmony_ci	} else {
1662306a36Sopenharmony_ci		page = alloc_page(gfp);
1762306a36Sopenharmony_ci	}
1862306a36Sopenharmony_ci	return page;
1962306a36Sopenharmony_ci}
2062306a36Sopenharmony_ci
2162306a36Sopenharmony_civoid erofs_release_pages(struct page **pagepool)
2262306a36Sopenharmony_ci{
2362306a36Sopenharmony_ci	while (*pagepool) {
2462306a36Sopenharmony_ci		struct page *page = *pagepool;
2562306a36Sopenharmony_ci
2662306a36Sopenharmony_ci		*pagepool = (struct page *)page_private(page);
2762306a36Sopenharmony_ci		put_page(page);
2862306a36Sopenharmony_ci	}
2962306a36Sopenharmony_ci}
3062306a36Sopenharmony_ci
3162306a36Sopenharmony_ci#ifdef CONFIG_EROFS_FS_ZIP
3262306a36Sopenharmony_ci/* global shrink count (for all mounted EROFS instances) */
3362306a36Sopenharmony_cistatic atomic_long_t erofs_global_shrink_cnt;
3462306a36Sopenharmony_ci
3562306a36Sopenharmony_cistatic bool erofs_workgroup_get(struct erofs_workgroup *grp)
3662306a36Sopenharmony_ci{
3762306a36Sopenharmony_ci	if (lockref_get_not_zero(&grp->lockref))
3862306a36Sopenharmony_ci		return true;
3962306a36Sopenharmony_ci
4062306a36Sopenharmony_ci	spin_lock(&grp->lockref.lock);
4162306a36Sopenharmony_ci	if (__lockref_is_dead(&grp->lockref)) {
4262306a36Sopenharmony_ci		spin_unlock(&grp->lockref.lock);
4362306a36Sopenharmony_ci		return false;
4462306a36Sopenharmony_ci	}
4562306a36Sopenharmony_ci
4662306a36Sopenharmony_ci	if (!grp->lockref.count++)
4762306a36Sopenharmony_ci		atomic_long_dec(&erofs_global_shrink_cnt);
4862306a36Sopenharmony_ci	spin_unlock(&grp->lockref.lock);
4962306a36Sopenharmony_ci	return true;
5062306a36Sopenharmony_ci}
5162306a36Sopenharmony_ci
5262306a36Sopenharmony_cistruct erofs_workgroup *erofs_find_workgroup(struct super_block *sb,
5362306a36Sopenharmony_ci					     pgoff_t index)
5462306a36Sopenharmony_ci{
5562306a36Sopenharmony_ci	struct erofs_sb_info *sbi = EROFS_SB(sb);
5662306a36Sopenharmony_ci	struct erofs_workgroup *grp;
5762306a36Sopenharmony_ci
5862306a36Sopenharmony_cirepeat:
5962306a36Sopenharmony_ci	rcu_read_lock();
6062306a36Sopenharmony_ci	grp = xa_load(&sbi->managed_pslots, index);
6162306a36Sopenharmony_ci	if (grp) {
6262306a36Sopenharmony_ci		if (!erofs_workgroup_get(grp)) {
6362306a36Sopenharmony_ci			/* prefer to relax rcu read side */
6462306a36Sopenharmony_ci			rcu_read_unlock();
6562306a36Sopenharmony_ci			goto repeat;
6662306a36Sopenharmony_ci		}
6762306a36Sopenharmony_ci
6862306a36Sopenharmony_ci		DBG_BUGON(index != grp->index);
6962306a36Sopenharmony_ci	}
7062306a36Sopenharmony_ci	rcu_read_unlock();
7162306a36Sopenharmony_ci	return grp;
7262306a36Sopenharmony_ci}
7362306a36Sopenharmony_ci
7462306a36Sopenharmony_cistruct erofs_workgroup *erofs_insert_workgroup(struct super_block *sb,
7562306a36Sopenharmony_ci					       struct erofs_workgroup *grp)
7662306a36Sopenharmony_ci{
7762306a36Sopenharmony_ci	struct erofs_sb_info *const sbi = EROFS_SB(sb);
7862306a36Sopenharmony_ci	struct erofs_workgroup *pre;
7962306a36Sopenharmony_ci
8062306a36Sopenharmony_ci	DBG_BUGON(grp->lockref.count < 1);
8162306a36Sopenharmony_cirepeat:
8262306a36Sopenharmony_ci	xa_lock(&sbi->managed_pslots);
8362306a36Sopenharmony_ci	pre = __xa_cmpxchg(&sbi->managed_pslots, grp->index,
8462306a36Sopenharmony_ci			   NULL, grp, GFP_NOFS);
8562306a36Sopenharmony_ci	if (pre) {
8662306a36Sopenharmony_ci		if (xa_is_err(pre)) {
8762306a36Sopenharmony_ci			pre = ERR_PTR(xa_err(pre));
8862306a36Sopenharmony_ci		} else if (!erofs_workgroup_get(pre)) {
8962306a36Sopenharmony_ci			/* try to legitimize the current in-tree one */
9062306a36Sopenharmony_ci			xa_unlock(&sbi->managed_pslots);
9162306a36Sopenharmony_ci			cond_resched();
9262306a36Sopenharmony_ci			goto repeat;
9362306a36Sopenharmony_ci		}
9462306a36Sopenharmony_ci		grp = pre;
9562306a36Sopenharmony_ci	}
9662306a36Sopenharmony_ci	xa_unlock(&sbi->managed_pslots);
9762306a36Sopenharmony_ci	return grp;
9862306a36Sopenharmony_ci}
9962306a36Sopenharmony_ci
10062306a36Sopenharmony_cistatic void  __erofs_workgroup_free(struct erofs_workgroup *grp)
10162306a36Sopenharmony_ci{
10262306a36Sopenharmony_ci	atomic_long_dec(&erofs_global_shrink_cnt);
10362306a36Sopenharmony_ci	erofs_workgroup_free_rcu(grp);
10462306a36Sopenharmony_ci}
10562306a36Sopenharmony_ci
10662306a36Sopenharmony_civoid erofs_workgroup_put(struct erofs_workgroup *grp)
10762306a36Sopenharmony_ci{
10862306a36Sopenharmony_ci	if (lockref_put_or_lock(&grp->lockref))
10962306a36Sopenharmony_ci		return;
11062306a36Sopenharmony_ci
11162306a36Sopenharmony_ci	DBG_BUGON(__lockref_is_dead(&grp->lockref));
11262306a36Sopenharmony_ci	if (grp->lockref.count == 1)
11362306a36Sopenharmony_ci		atomic_long_inc(&erofs_global_shrink_cnt);
11462306a36Sopenharmony_ci	--grp->lockref.count;
11562306a36Sopenharmony_ci	spin_unlock(&grp->lockref.lock);
11662306a36Sopenharmony_ci}
11762306a36Sopenharmony_ci
11862306a36Sopenharmony_cistatic bool erofs_try_to_release_workgroup(struct erofs_sb_info *sbi,
11962306a36Sopenharmony_ci					   struct erofs_workgroup *grp)
12062306a36Sopenharmony_ci{
12162306a36Sopenharmony_ci	int free = false;
12262306a36Sopenharmony_ci
12362306a36Sopenharmony_ci	spin_lock(&grp->lockref.lock);
12462306a36Sopenharmony_ci	if (grp->lockref.count)
12562306a36Sopenharmony_ci		goto out;
12662306a36Sopenharmony_ci
12762306a36Sopenharmony_ci	/*
12862306a36Sopenharmony_ci	 * Note that all cached pages should be detached before deleted from
12962306a36Sopenharmony_ci	 * the XArray. Otherwise some cached pages could be still attached to
13062306a36Sopenharmony_ci	 * the orphan old workgroup when the new one is available in the tree.
13162306a36Sopenharmony_ci	 */
13262306a36Sopenharmony_ci	if (erofs_try_to_free_all_cached_pages(sbi, grp))
13362306a36Sopenharmony_ci		goto out;
13462306a36Sopenharmony_ci
13562306a36Sopenharmony_ci	/*
13662306a36Sopenharmony_ci	 * It's impossible to fail after the workgroup is freezed,
13762306a36Sopenharmony_ci	 * however in order to avoid some race conditions, add a
13862306a36Sopenharmony_ci	 * DBG_BUGON to observe this in advance.
13962306a36Sopenharmony_ci	 */
14062306a36Sopenharmony_ci	DBG_BUGON(__xa_erase(&sbi->managed_pslots, grp->index) != grp);
14162306a36Sopenharmony_ci
14262306a36Sopenharmony_ci	lockref_mark_dead(&grp->lockref);
14362306a36Sopenharmony_ci	free = true;
14462306a36Sopenharmony_ciout:
14562306a36Sopenharmony_ci	spin_unlock(&grp->lockref.lock);
14662306a36Sopenharmony_ci	if (free)
14762306a36Sopenharmony_ci		__erofs_workgroup_free(grp);
14862306a36Sopenharmony_ci	return free;
14962306a36Sopenharmony_ci}
15062306a36Sopenharmony_ci
15162306a36Sopenharmony_cistatic unsigned long erofs_shrink_workstation(struct erofs_sb_info *sbi,
15262306a36Sopenharmony_ci					      unsigned long nr_shrink)
15362306a36Sopenharmony_ci{
15462306a36Sopenharmony_ci	struct erofs_workgroup *grp;
15562306a36Sopenharmony_ci	unsigned int freed = 0;
15662306a36Sopenharmony_ci	unsigned long index;
15762306a36Sopenharmony_ci
15862306a36Sopenharmony_ci	xa_lock(&sbi->managed_pslots);
15962306a36Sopenharmony_ci	xa_for_each(&sbi->managed_pslots, index, grp) {
16062306a36Sopenharmony_ci		/* try to shrink each valid workgroup */
16162306a36Sopenharmony_ci		if (!erofs_try_to_release_workgroup(sbi, grp))
16262306a36Sopenharmony_ci			continue;
16362306a36Sopenharmony_ci		xa_unlock(&sbi->managed_pslots);
16462306a36Sopenharmony_ci
16562306a36Sopenharmony_ci		++freed;
16662306a36Sopenharmony_ci		if (!--nr_shrink)
16762306a36Sopenharmony_ci			return freed;
16862306a36Sopenharmony_ci		xa_lock(&sbi->managed_pslots);
16962306a36Sopenharmony_ci	}
17062306a36Sopenharmony_ci	xa_unlock(&sbi->managed_pslots);
17162306a36Sopenharmony_ci	return freed;
17262306a36Sopenharmony_ci}
17362306a36Sopenharmony_ci
17462306a36Sopenharmony_ci/* protected by 'erofs_sb_list_lock' */
17562306a36Sopenharmony_cistatic unsigned int shrinker_run_no;
17662306a36Sopenharmony_ci
17762306a36Sopenharmony_ci/* protects the mounted 'erofs_sb_list' */
17862306a36Sopenharmony_cistatic DEFINE_SPINLOCK(erofs_sb_list_lock);
17962306a36Sopenharmony_cistatic LIST_HEAD(erofs_sb_list);
18062306a36Sopenharmony_ci
18162306a36Sopenharmony_civoid erofs_shrinker_register(struct super_block *sb)
18262306a36Sopenharmony_ci{
18362306a36Sopenharmony_ci	struct erofs_sb_info *sbi = EROFS_SB(sb);
18462306a36Sopenharmony_ci
18562306a36Sopenharmony_ci	mutex_init(&sbi->umount_mutex);
18662306a36Sopenharmony_ci
18762306a36Sopenharmony_ci	spin_lock(&erofs_sb_list_lock);
18862306a36Sopenharmony_ci	list_add(&sbi->list, &erofs_sb_list);
18962306a36Sopenharmony_ci	spin_unlock(&erofs_sb_list_lock);
19062306a36Sopenharmony_ci}
19162306a36Sopenharmony_ci
19262306a36Sopenharmony_civoid erofs_shrinker_unregister(struct super_block *sb)
19362306a36Sopenharmony_ci{
19462306a36Sopenharmony_ci	struct erofs_sb_info *const sbi = EROFS_SB(sb);
19562306a36Sopenharmony_ci
19662306a36Sopenharmony_ci	mutex_lock(&sbi->umount_mutex);
19762306a36Sopenharmony_ci	/* clean up all remaining workgroups in memory */
19862306a36Sopenharmony_ci	erofs_shrink_workstation(sbi, ~0UL);
19962306a36Sopenharmony_ci
20062306a36Sopenharmony_ci	spin_lock(&erofs_sb_list_lock);
20162306a36Sopenharmony_ci	list_del(&sbi->list);
20262306a36Sopenharmony_ci	spin_unlock(&erofs_sb_list_lock);
20362306a36Sopenharmony_ci	mutex_unlock(&sbi->umount_mutex);
20462306a36Sopenharmony_ci}
20562306a36Sopenharmony_ci
20662306a36Sopenharmony_cistatic unsigned long erofs_shrink_count(struct shrinker *shrink,
20762306a36Sopenharmony_ci					struct shrink_control *sc)
20862306a36Sopenharmony_ci{
20962306a36Sopenharmony_ci	return atomic_long_read(&erofs_global_shrink_cnt);
21062306a36Sopenharmony_ci}
21162306a36Sopenharmony_ci
21262306a36Sopenharmony_cistatic unsigned long erofs_shrink_scan(struct shrinker *shrink,
21362306a36Sopenharmony_ci				       struct shrink_control *sc)
21462306a36Sopenharmony_ci{
21562306a36Sopenharmony_ci	struct erofs_sb_info *sbi;
21662306a36Sopenharmony_ci	struct list_head *p;
21762306a36Sopenharmony_ci
21862306a36Sopenharmony_ci	unsigned long nr = sc->nr_to_scan;
21962306a36Sopenharmony_ci	unsigned int run_no;
22062306a36Sopenharmony_ci	unsigned long freed = 0;
22162306a36Sopenharmony_ci
22262306a36Sopenharmony_ci	spin_lock(&erofs_sb_list_lock);
22362306a36Sopenharmony_ci	do {
22462306a36Sopenharmony_ci		run_no = ++shrinker_run_no;
22562306a36Sopenharmony_ci	} while (run_no == 0);
22662306a36Sopenharmony_ci
22762306a36Sopenharmony_ci	/* Iterate over all mounted superblocks and try to shrink them */
22862306a36Sopenharmony_ci	p = erofs_sb_list.next;
22962306a36Sopenharmony_ci	while (p != &erofs_sb_list) {
23062306a36Sopenharmony_ci		sbi = list_entry(p, struct erofs_sb_info, list);
23162306a36Sopenharmony_ci
23262306a36Sopenharmony_ci		/*
23362306a36Sopenharmony_ci		 * We move the ones we do to the end of the list, so we stop
23462306a36Sopenharmony_ci		 * when we see one we have already done.
23562306a36Sopenharmony_ci		 */
23662306a36Sopenharmony_ci		if (sbi->shrinker_run_no == run_no)
23762306a36Sopenharmony_ci			break;
23862306a36Sopenharmony_ci
23962306a36Sopenharmony_ci		if (!mutex_trylock(&sbi->umount_mutex)) {
24062306a36Sopenharmony_ci			p = p->next;
24162306a36Sopenharmony_ci			continue;
24262306a36Sopenharmony_ci		}
24362306a36Sopenharmony_ci
24462306a36Sopenharmony_ci		spin_unlock(&erofs_sb_list_lock);
24562306a36Sopenharmony_ci		sbi->shrinker_run_no = run_no;
24662306a36Sopenharmony_ci
24762306a36Sopenharmony_ci		freed += erofs_shrink_workstation(sbi, nr - freed);
24862306a36Sopenharmony_ci
24962306a36Sopenharmony_ci		spin_lock(&erofs_sb_list_lock);
25062306a36Sopenharmony_ci		/* Get the next list element before we move this one */
25162306a36Sopenharmony_ci		p = p->next;
25262306a36Sopenharmony_ci
25362306a36Sopenharmony_ci		/*
25462306a36Sopenharmony_ci		 * Move this one to the end of the list to provide some
25562306a36Sopenharmony_ci		 * fairness.
25662306a36Sopenharmony_ci		 */
25762306a36Sopenharmony_ci		list_move_tail(&sbi->list, &erofs_sb_list);
25862306a36Sopenharmony_ci		mutex_unlock(&sbi->umount_mutex);
25962306a36Sopenharmony_ci
26062306a36Sopenharmony_ci		if (freed >= nr)
26162306a36Sopenharmony_ci			break;
26262306a36Sopenharmony_ci	}
26362306a36Sopenharmony_ci	spin_unlock(&erofs_sb_list_lock);
26462306a36Sopenharmony_ci	return freed;
26562306a36Sopenharmony_ci}
26662306a36Sopenharmony_ci
26762306a36Sopenharmony_cistatic struct shrinker erofs_shrinker_info = {
26862306a36Sopenharmony_ci	.scan_objects = erofs_shrink_scan,
26962306a36Sopenharmony_ci	.count_objects = erofs_shrink_count,
27062306a36Sopenharmony_ci	.seeks = DEFAULT_SEEKS,
27162306a36Sopenharmony_ci};
27262306a36Sopenharmony_ci
27362306a36Sopenharmony_ciint __init erofs_init_shrinker(void)
27462306a36Sopenharmony_ci{
27562306a36Sopenharmony_ci	return register_shrinker(&erofs_shrinker_info, "erofs-shrinker");
27662306a36Sopenharmony_ci}
27762306a36Sopenharmony_ci
27862306a36Sopenharmony_civoid erofs_exit_shrinker(void)
27962306a36Sopenharmony_ci{
28062306a36Sopenharmony_ci	unregister_shrinker(&erofs_shrinker_info);
28162306a36Sopenharmony_ci}
28262306a36Sopenharmony_ci#endif	/* !CONFIG_EROFS_FS_ZIP */
283