Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit e7e9a307 authored by Gao Xiang's avatar Gao Xiang Committed by Greg Kroah-Hartman
Browse files

staging: erofs: introduce workstation for decompression



This patch introduces another concept used by the unzip
subsystem called 'workstation'. It can be seen as a sparse
array that stores pointers pointed to data structures
related to the corresponding physical blocks.

All lookup cases are protected by RCU read lock. Besides,
reference count and spin_lock are also introduced to
manage its lifetime and serialize all update operations.

'workstation' is currently implemented on the in-kernel
radix tree approach for backward compatibility.
With the evolution of linux kernel, it could be migrated
into XArray implementation in the future.

Signed-off-by: default avatarGao Xiang <gaoxiang25@huawei.com>
Signed-off-by: default avatarGreg Kroah-Hartman <gregkh@linuxfoundation.org>
parent a1581312
Loading
Loading
Loading
Loading
+93 −0
Original line number Diff line number Diff line
@@ -79,6 +79,9 @@ struct erofs_sb_info {
#ifdef CONFIG_EROFS_FS_ZIP
	/* cluster size in bit shift */
	unsigned char clusterbits;

	/* the dedicated workstation for compression */
	struct radix_tree_root workstn_tree;
#endif

	u32 build_time_nsec;
@@ -149,6 +152,96 @@ static inline void *erofs_kmalloc(struct erofs_sb_info *sbi,
#define set_opt(sbi, option)	((sbi)->mount_opt |= EROFS_MOUNT_##option)
#define test_opt(sbi, option)	((sbi)->mount_opt & EROFS_MOUNT_##option)

#ifdef CONFIG_EROFS_FS_ZIP
#define erofs_workstn_lock(sbi)         xa_lock(&(sbi)->workstn_tree)
#define erofs_workstn_unlock(sbi)       xa_unlock(&(sbi)->workstn_tree)

/* basic unit of the workstation of a super_block */
struct erofs_workgroup {
	/* the workgroup index in the workstation */
	pgoff_t index;

	/* overall workgroup reference count */
	atomic_t refcount;
};

#define EROFS_LOCKED_MAGIC     (INT_MIN | 0xE0F510CCL)

static inline bool erofs_workgroup_try_to_freeze(
	struct erofs_workgroup *grp, int v)
{
#if defined(CONFIG_SMP) || defined(CONFIG_DEBUG_SPINLOCK)
	if (v != atomic_cmpxchg(&grp->refcount,
		v, EROFS_LOCKED_MAGIC))
		return false;
	preempt_disable();
#else
	preempt_disable();
	if (atomic_read(&grp->refcount) != v) {
		preempt_enable();
		return false;
	}
#endif
	return true;
}

static inline void erofs_workgroup_unfreeze(
	struct erofs_workgroup *grp, int v)
{
#if defined(CONFIG_SMP) || defined(CONFIG_DEBUG_SPINLOCK)
	atomic_set(&grp->refcount, v);
#endif
	preempt_enable();
}

static inline bool erofs_workgroup_get(struct erofs_workgroup *grp, int *ocnt)
{
	const int locked = (int)EROFS_LOCKED_MAGIC;
	int o;

repeat:
	o = atomic_read(&grp->refcount);

	/* spin if it is temporarily locked at the reclaim path */
	if (unlikely(o == locked)) {
#if defined(CONFIG_SMP) || defined(CONFIG_DEBUG_SPINLOCK)
		do
			cpu_relax();
		while (atomic_read(&grp->refcount) == locked);
#endif
		goto repeat;
	}

	if (unlikely(o <= 0))
		return -1;

	if (unlikely(atomic_cmpxchg(&grp->refcount, o, o + 1) != o))
		goto repeat;

	*ocnt = o;
	return 0;
}

#define __erofs_workgroup_get(grp)	atomic_inc(&(grp)->refcount)

extern int erofs_workgroup_put(struct erofs_workgroup *grp);

extern struct erofs_workgroup *erofs_find_workgroup(
	struct super_block *sb, pgoff_t index, bool *tag);

extern int erofs_register_workgroup(struct super_block *sb,
	struct erofs_workgroup *grp, bool tag);

extern unsigned long erofs_shrink_workstation(struct erofs_sb_info *sbi,
	unsigned long nr_shrink, bool cleanup);

static inline void erofs_workstation_cleanup_all(struct super_block *sb)
{
	erofs_shrink_workstation(EROFS_SB(sb), ~0UL, true);
}

#endif

/* we strictly follow PAGE_SIZE and no buffer head yet */
#define LOG_BLOCK_SIZE		PAGE_SHIFT

+9 −0
Original line number Diff line number Diff line
@@ -296,6 +296,10 @@ static int erofs_read_super(struct super_block *sb,
	if (!silent)
		infoln("root inode @ nid %llu", ROOT_NID(sbi));

#ifdef CONFIG_EROFS_FS_ZIP
	INIT_RADIX_TREE(&sbi->workstn_tree, GFP_ATOMIC);
#endif

	/* get the root inode */
	inode = erofs_iget(sb, ROOT_NID(sbi), true);
	if (IS_ERR(inode)) {
@@ -376,6 +380,11 @@ static void erofs_put_super(struct super_block *sb)
	__putname(sbi->dev_name);

	mutex_lock(&sbi->umount_mutex);

#ifdef CONFIG_EROFS_FS_ZIP
	erofs_workstation_cleanup_all(sb);
#endif

	erofs_unregister_super(sb);
	mutex_unlock(&sbi->umount_mutex);

+78 −3
Original line number Diff line number Diff line
@@ -29,6 +29,83 @@ struct page *erofs_allocpage(struct list_head *pool, gfp_t gfp)
	return page;
}

/* global shrink count (for all mounted EROFS instances) */
static atomic_long_t erofs_global_shrink_cnt;

#ifdef CONFIG_EROFS_FS_ZIP

/* radix_tree and the future XArray both don't use tagptr_t yet */
struct erofs_workgroup *erofs_find_workgroup(
	struct super_block *sb, pgoff_t index, bool *tag)
{
	struct erofs_sb_info *sbi = EROFS_SB(sb);
	struct erofs_workgroup *grp;
	int oldcount;

repeat:
	rcu_read_lock();
	grp = radix_tree_lookup(&sbi->workstn_tree, index);
	if (grp != NULL) {
		*tag = radix_tree_exceptional_entry(grp);
		grp = (void *)((unsigned long)grp &
			~RADIX_TREE_EXCEPTIONAL_ENTRY);

		if (erofs_workgroup_get(grp, &oldcount)) {
			/* prefer to relax rcu read side */
			rcu_read_unlock();
			goto repeat;
		}

		/* decrease refcount added by erofs_workgroup_put */
		if (unlikely(oldcount == 1))
			atomic_long_dec(&erofs_global_shrink_cnt);
		BUG_ON(index != grp->index);
	}
	rcu_read_unlock();
	return grp;
}

int erofs_register_workgroup(struct super_block *sb,
			     struct erofs_workgroup *grp,
			     bool tag)
{
	struct erofs_sb_info *sbi;
	int err;

	/* grp->refcount should not < 1 */
	BUG_ON(!atomic_read(&grp->refcount));

	err = radix_tree_preload(GFP_NOFS);
	if (err)
		return err;

	sbi = EROFS_SB(sb);
	erofs_workstn_lock(sbi);

	if (tag)
		grp = (void *)((unsigned long)grp |
			1UL << RADIX_TREE_EXCEPTIONAL_SHIFT);

	err = radix_tree_insert(&sbi->workstn_tree,
		grp->index, grp);

	if (!err) {
		__erofs_workgroup_get(grp);
	}

	erofs_workstn_unlock(sbi);
	radix_tree_preload_end();
	return err;
}

unsigned long erofs_shrink_workstation(struct erofs_sb_info *sbi,
				       unsigned long nr_shrink,
				       bool cleanup)
{
	return 0;
}

#endif

/* protected by 'erofs_sb_list_lock' */
static unsigned int shrinker_run_no;
@@ -37,9 +114,6 @@ static unsigned int shrinker_run_no;
static DEFINE_SPINLOCK(erofs_sb_list_lock);
static LIST_HEAD(erofs_sb_list);

/* global shrink count (for all mounted EROFS instances) */
static atomic_long_t erofs_global_shrink_cnt;

void erofs_register_super(struct super_block *sb)
{
	struct erofs_sb_info *sbi = EROFS_SB(sb);
@@ -112,6 +186,7 @@ unsigned long erofs_shrink_scan(struct shrinker *shrink,
		list_move_tail(&sbi->list, &erofs_sb_list);
		mutex_unlock(&sbi->umount_mutex);

		freed += erofs_shrink_workstation(sbi, nr, false);
		if (freed >= nr)
			break;
	}