diff options
Diffstat (limited to 'mm/backing-dev.c')
| -rw-r--r-- | mm/backing-dev.c | 120 | 
1 files changed, 100 insertions, 20 deletions
diff --git a/mm/backing-dev.c b/mm/backing-dev.c index e8e89158adec..d9daa3e422d0 100644 --- a/mm/backing-dev.c +++ b/mm/backing-dev.c @@ -1,6 +1,7 @@  // SPDX-License-Identifier: GPL-2.0-only  #include <linux/wait.h> +#include <linux/rbtree.h>  #include <linux/backing-dev.h>  #include <linux/kthread.h>  #include <linux/freezer.h> @@ -22,10 +23,12 @@ EXPORT_SYMBOL_GPL(noop_backing_dev_info);  static struct class *bdi_class;  /* - * bdi_lock protects updates to bdi_list. bdi_list has RCU reader side - * locking. + * bdi_lock protects bdi_tree and updates to bdi_list. bdi_list has RCU + * reader side locking.   */  DEFINE_SPINLOCK(bdi_lock); +static u64 bdi_id_cursor; +static struct rb_root bdi_tree = RB_ROOT;  LIST_HEAD(bdi_list);  /* bdi_wq serves all asynchronous writeback tasks */ @@ -615,13 +618,12 @@ out_put:  }  /** - * wb_get_create - get wb for a given memcg, create if necessary + * wb_get_lookup - get wb for a given memcg   * @bdi: target bdi   * @memcg_css: cgroup_subsys_state of the target memcg (must have positive ref) - * @gfp: allocation mask to use   * - * Try to get the wb for @memcg_css on @bdi.  If it doesn't exist, try to - * create one.  The returned wb has its refcount incremented. + * Try to get the wb for @memcg_css on @bdi.  The returned wb has its + * refcount incremented.   *   * This function uses css_get() on @memcg_css and thus expects its refcnt   * to be positive on invocation.  IOW, rcu_read_lock() protection on @@ -638,6 +640,39 @@ out_put:   * each lookup.  On mismatch, the existing wb is discarded and a new one is   * created.   */ +struct bdi_writeback *wb_get_lookup(struct backing_dev_info *bdi, +				    struct cgroup_subsys_state *memcg_css) +{ +	struct bdi_writeback *wb; + +	if (!memcg_css->parent) +		return &bdi->wb; + +	rcu_read_lock(); +	wb = radix_tree_lookup(&bdi->cgwb_tree, memcg_css->id); +	if (wb) { +		struct cgroup_subsys_state *blkcg_css; + +		/* see whether the blkcg association has changed */ +		blkcg_css = cgroup_get_e_css(memcg_css->cgroup, &io_cgrp_subsys); +		if (unlikely(wb->blkcg_css != blkcg_css || !wb_tryget(wb))) +			wb = NULL; +		css_put(blkcg_css); +	} +	rcu_read_unlock(); + +	return wb; +} + +/** + * wb_get_create - get wb for a given memcg, create if necessary + * @bdi: target bdi + * @memcg_css: cgroup_subsys_state of the target memcg (must have positive ref) + * @gfp: allocation mask to use + * + * Try to get the wb for @memcg_css on @bdi.  If it doesn't exist, try to + * create one.  See wb_get_lookup() for more details. + */  struct bdi_writeback *wb_get_create(struct backing_dev_info *bdi,  				    struct cgroup_subsys_state *memcg_css,  				    gfp_t gfp) @@ -650,20 +685,7 @@ struct bdi_writeback *wb_get_create(struct backing_dev_info *bdi,  		return &bdi->wb;  	do { -		rcu_read_lock(); -		wb = radix_tree_lookup(&bdi->cgwb_tree, memcg_css->id); -		if (wb) { -			struct cgroup_subsys_state *blkcg_css; - -			/* see whether the blkcg association has changed */ -			blkcg_css = cgroup_get_e_css(memcg_css->cgroup, -						     &io_cgrp_subsys); -			if (unlikely(wb->blkcg_css != blkcg_css || -				     !wb_tryget(wb))) -				wb = NULL; -			css_put(blkcg_css); -		} -		rcu_read_unlock(); +		wb = wb_get_lookup(bdi, memcg_css);  	} while (!wb && !cgwb_create(bdi, memcg_css, gfp));  	return wb; @@ -859,9 +881,58 @@ struct backing_dev_info *bdi_alloc_node(gfp_t gfp_mask, int node_id)  }  EXPORT_SYMBOL(bdi_alloc_node); +static struct rb_node **bdi_lookup_rb_node(u64 id, struct rb_node **parentp) +{ +	struct rb_node **p = &bdi_tree.rb_node; +	struct rb_node *parent = NULL; +	struct backing_dev_info *bdi; + +	lockdep_assert_held(&bdi_lock); + +	while (*p) { +		parent = *p; +		bdi = rb_entry(parent, struct backing_dev_info, rb_node); + +		if (bdi->id > id) +			p = &(*p)->rb_left; +		else if (bdi->id < id) +			p = &(*p)->rb_right; +		else +			break; +	} + +	if (parentp) +		*parentp = parent; +	return p; +} + +/** + * bdi_get_by_id - lookup and get bdi from its id + * @id: bdi id to lookup + * + * Find bdi matching @id and get it.  Returns NULL if the matching bdi + * doesn't exist or is already unregistered. + */ +struct backing_dev_info *bdi_get_by_id(u64 id) +{ +	struct backing_dev_info *bdi = NULL; +	struct rb_node **p; + +	spin_lock_bh(&bdi_lock); +	p = bdi_lookup_rb_node(id, NULL); +	if (*p) { +		bdi = rb_entry(*p, struct backing_dev_info, rb_node); +		bdi_get(bdi); +	} +	spin_unlock_bh(&bdi_lock); + +	return bdi; +} +  int bdi_register_va(struct backing_dev_info *bdi, const char *fmt, va_list args)  {  	struct device *dev; +	struct rb_node *parent, **p;  	if (bdi->dev)	/* The driver needs to use separate queues per device */  		return 0; @@ -877,7 +948,15 @@ int bdi_register_va(struct backing_dev_info *bdi, const char *fmt, va_list args)  	set_bit(WB_registered, &bdi->wb.state);  	spin_lock_bh(&bdi_lock); + +	bdi->id = ++bdi_id_cursor; + +	p = bdi_lookup_rb_node(bdi->id, &parent); +	rb_link_node(&bdi->rb_node, parent, p); +	rb_insert_color(&bdi->rb_node, &bdi_tree); +  	list_add_tail_rcu(&bdi->bdi_list, &bdi_list); +  	spin_unlock_bh(&bdi_lock);  	trace_writeback_bdi_register(bdi); @@ -918,6 +997,7 @@ EXPORT_SYMBOL(bdi_register_owner);  static void bdi_remove_from_list(struct backing_dev_info *bdi)  {  	spin_lock_bh(&bdi_lock); +	rb_erase(&bdi->rb_node, &bdi_tree);  	list_del_rcu(&bdi->bdi_list);  	spin_unlock_bh(&bdi_lock);  |