diff options
Diffstat (limited to 'net/sched/cls_api.c')
| -rw-r--r-- | net/sched/cls_api.c | 60 | 
1 files changed, 37 insertions, 23 deletions
diff --git a/net/sched/cls_api.c b/net/sched/cls_api.c index 0b2219adf520..b2d310745487 100644 --- a/net/sched/cls_api.c +++ b/net/sched/cls_api.c @@ -77,6 +77,8 @@ out:  }  EXPORT_SYMBOL(register_tcf_proto_ops); +static struct workqueue_struct *tc_filter_wq; +  int unregister_tcf_proto_ops(struct tcf_proto_ops *ops)  {  	struct tcf_proto_ops *t; @@ -86,6 +88,7 @@ int unregister_tcf_proto_ops(struct tcf_proto_ops *ops)  	 * tcf_proto_ops's destroy() handler.  	 */  	rcu_barrier(); +	flush_workqueue(tc_filter_wq);  	write_lock(&cls_mod_lock);  	list_for_each_entry(t, &tcf_proto_base, head) { @@ -100,6 +103,12 @@ int unregister_tcf_proto_ops(struct tcf_proto_ops *ops)  }  EXPORT_SYMBOL(unregister_tcf_proto_ops); +bool tcf_queue_work(struct work_struct *work) +{ +	return queue_work(tc_filter_wq, work); +} +EXPORT_SYMBOL(tcf_queue_work); +  /* Select new prio value from the range, managed by kernel. */  static inline u32 tcf_auto_prio(struct tcf_proto *tp) @@ -266,6 +275,23 @@ err_chain_create:  }  EXPORT_SYMBOL(tcf_block_get); +static void tcf_block_put_final(struct work_struct *work) +{ +	struct tcf_block *block = container_of(work, struct tcf_block, work); +	struct tcf_chain *chain, *tmp; + +	rtnl_lock(); +	/* Only chain 0 should be still here. */ +	list_for_each_entry_safe(chain, tmp, &block->chain_list, list) +		tcf_chain_put(chain); +	rtnl_unlock(); +	kfree(block); +} + +/* XXX: Standalone actions are not allowed to jump to any chain, and bound + * actions should be all removed after flushing. However, filters are now + * destroyed in tc filter workqueue with RTNL lock, they can not race here. + */  void tcf_block_put(struct tcf_block *block)  {  	struct tcf_chain *chain, *tmp; @@ -273,32 +299,15 @@ void tcf_block_put(struct tcf_block *block)  	if (!block)  		return; -	/* XXX: Standalone actions are not allowed to jump to any chain, and -	 * bound actions should be all removed after flushing. However, -	 * filters are destroyed in RCU callbacks, we have to hold the chains -	 * first, otherwise we would always race with RCU callbacks on this list -	 * without proper locking. -	 */ - -	/* Wait for existing RCU callbacks to cool down. */ -	rcu_barrier(); - -	/* Hold a refcnt for all chains, except 0, in case they are gone. */ -	list_for_each_entry(chain, &block->chain_list, list) -		if (chain->index) -			tcf_chain_hold(chain); - -	/* No race on the list, because no chain could be destroyed. */ -	list_for_each_entry(chain, &block->chain_list, list) +	list_for_each_entry_safe(chain, tmp, &block->chain_list, list)  		tcf_chain_flush(chain); -	/* Wait for RCU callbacks to release the reference count. */ +	INIT_WORK(&block->work, tcf_block_put_final); +	/* Wait for RCU callbacks to release the reference count and make +	 * sure their works have been queued before this. +	 */  	rcu_barrier(); - -	/* At this point, all the chains should have refcnt == 1. */ -	list_for_each_entry_safe(chain, tmp, &block->chain_list, list) -		tcf_chain_put(chain); -	kfree(block); +	tcf_queue_work(&block->work);  }  EXPORT_SYMBOL(tcf_block_put); @@ -879,6 +888,7 @@ void tcf_exts_destroy(struct tcf_exts *exts)  #ifdef CONFIG_NET_CLS_ACT  	LIST_HEAD(actions); +	ASSERT_RTNL();  	tcf_exts_to_list(exts, &actions);  	tcf_action_destroy(&actions, TCA_ACT_UNBIND);  	kfree(exts->actions); @@ -1030,6 +1040,10 @@ EXPORT_SYMBOL(tcf_exts_get_dev);  static int __init tc_filter_init(void)  { +	tc_filter_wq = alloc_ordered_workqueue("tc_filter_workqueue", 0); +	if (!tc_filter_wq) +		return -ENOMEM; +  	rtnl_register(PF_UNSPEC, RTM_NEWTFILTER, tc_ctl_tfilter, NULL, 0);  	rtnl_register(PF_UNSPEC, RTM_DELTFILTER, tc_ctl_tfilter, NULL, 0);  	rtnl_register(PF_UNSPEC, RTM_GETTFILTER, tc_ctl_tfilter,  |