836 lines · 21.4 KB · C
RawDownload
// SPDX-License-Identifier: GPL-2.0-only/* * RDMA resource limiting controller for cgroups. * * Used to allow a cgroup hierarchy to stop processes from consuming * additional RDMA resources after a certain limit is reached. * * Copyright (C) 2016 Parav Pandit <pandit.parav@gmail.com> */ #include <linux/bitops.h>#include <linux/limits.h>#include <linux/slab.h>#include <linux/seq_file.h>#include <linux/cgroup.h>#include <linux/parser.h>#include <linux/cgroup_rdma.h> #define RDMACG_MAX_STR "max" enum rdmacg_limit_tokens {	RDMACG_DEVICE_INDEX,	RDMACG_HCA_HANDLE_VAL,	RDMACG_HCA_HANDLE_MAX,	RDMACG_HCA_OBJECT_VAL,	RDMACG_HCA_OBJECT_MAX,	NR_RDMACG_LIMIT_TOKENS,}; static const match_table_t rdmacg_limit_tokens = {	{ RDMACG_DEVICE_INDEX,		"index=%u"	},	{ RDMACG_HCA_HANDLE_VAL,	"hca_handle=%d"	},	{ RDMACG_HCA_HANDLE_MAX,	"hca_handle=max"	},	{ RDMACG_HCA_OBJECT_VAL,	"hca_object=%d"	},	{ RDMACG_HCA_OBJECT_MAX,	"hca_object=max"	},	{ NR_RDMACG_LIMIT_TOKENS,	NULL			},}; /* * Protects list of resource pools maintained on per cgroup basis * and rdma device list. */static DEFINE_MUTEX(rdmacg_mutex);static LIST_HEAD(rdmacg_devices); enum rdmacg_file_type {	RDMACG_RESOURCE_TYPE_MAX,	RDMACG_RESOURCE_TYPE_STAT,	RDMACG_RESOURCE_TYPE_PEAK,}; /* * resource table definition as to be seen by the user. * Need to add entries to it when more resources are * added/defined at IB verb/core layer. */static char const *rdmacg_resource_names[] = {	[RDMACG_RESOURCE_HCA_HANDLE]	= "hca_handle",	[RDMACG_RESOURCE_HCA_OBJECT]	= "hca_object",}; /* resource tracker for each resource of rdma cgroup */struct rdmacg_resource {	int max;	int usage;	int peak;}; /* * resource pool object which represents per cgroup, per device * resources. There are multiple instances of this object per cgroup, * therefore it cannot be embedded within rdma_cgroup structure. It * is maintained as list. */struct rdmacg_resource_pool {	struct rdmacg_device	*device;	struct rdmacg_resource	resources[RDMACG_RESOURCE_MAX]; 	struct list_head	cg_node;	struct list_head	dev_node; 	/* count active user tasks of this pool */	u64			usage_sum;	/* total number counts which are set to max */	int			num_max_cnt; 	/* per-resource event counters */	u64			events_max[RDMACG_RESOURCE_MAX];	u64			events_alloc_fail[RDMACG_RESOURCE_MAX];	u64			events_local_max[RDMACG_RESOURCE_MAX];	u64			events_local_alloc_fail[RDMACG_RESOURCE_MAX];}; static struct rdma_cgroup *css_rdmacg(struct cgroup_subsys_state *css){	return container_of(css, struct rdma_cgroup, css);} static struct rdma_cgroup *parent_rdmacg(struct rdma_cgroup *cg){	return css_rdmacg(cg->css.parent);} static inline struct rdma_cgroup *get_current_rdmacg(void){	return css_rdmacg(task_get_css(current, rdma_cgrp_id));} static void set_resource_limit(struct rdmacg_resource_pool *rpool,			       int index, int new_max){	if (new_max == S32_MAX) {		if (rpool->resources[index].max != S32_MAX)			rpool->num_max_cnt++;	} else {		if (rpool->resources[index].max == S32_MAX)			rpool->num_max_cnt--;	}	rpool->resources[index].max = new_max;} static void set_all_resource_max_limit(struct rdmacg_resource_pool *rpool){	int i; 	for (i = 0; i < RDMACG_RESOURCE_MAX; i++)		set_resource_limit(rpool, i, S32_MAX);} static void free_cg_rpool_locked(struct rdmacg_resource_pool *rpool){	lockdep_assert_held(&rdmacg_mutex); 	list_del(&rpool->cg_node);	list_del(&rpool->dev_node);	kfree(rpool);} static bool rpool_has_persistent_state(struct rdmacg_resource_pool *rpool){	int i; 	/*	 * Keep the rpool alive if any peak value is non-zero,	 * so that rdma.peak persists as a historical high-	 * watermark even after all resources are freed.	 */	for (i = 0; i < RDMACG_RESOURCE_MAX; i++) {		if (rpool->resources[i].peak ||		    rpool->events_max[i] ||		    rpool->events_local_max[i] ||		    rpool->events_alloc_fail[i] ||		    rpool->events_local_alloc_fail[i])			return true;	}	return false;} static struct rdmacg_resource_pool *find_cg_rpool_locked(struct rdma_cgroup *cg,		     struct rdmacg_device *device) {	struct rdmacg_resource_pool *pool; 	lockdep_assert_held(&rdmacg_mutex); 	list_for_each_entry(pool, &cg->rpools, cg_node)		if (pool->device == device)			return pool; 	return NULL;} static struct rdmacg_resource_pool *get_cg_rpool_locked(struct rdma_cgroup *cg, struct rdmacg_device *device){	struct rdmacg_resource_pool *rpool; 	rpool = find_cg_rpool_locked(cg, device);	if (rpool)		return rpool; 	rpool = kzalloc_obj(*rpool);	if (!rpool)		return ERR_PTR(-ENOMEM); 	rpool->device = device;	set_all_resource_max_limit(rpool); 	INIT_LIST_HEAD(&rpool->cg_node);	INIT_LIST_HEAD(&rpool->dev_node);	list_add_tail(&rpool->cg_node, &cg->rpools);	list_add_tail(&rpool->dev_node, &device->rpools);	return rpool;} /** * uncharge_cg_locked - uncharge resource for rdma cgroup * @cg: pointer to cg to uncharge and all parents in hierarchy * @device: pointer to rdmacg device * @index: index of the resource to uncharge in cg (resource pool) * * It also frees the resource pool which was created as part of * charging operation when there are no resources attached to * resource pool. */static voiduncharge_cg_locked(struct rdma_cgroup *cg,		   struct rdmacg_device *device,		   enum rdmacg_resource_type index){	struct rdmacg_resource_pool *rpool; 	rpool = find_cg_rpool_locked(cg, device); 	/*	 * rpool cannot be null at this stage. Let kernel operate in case	 * if there a bug in IB stack or rdma controller, instead of crashing	 * the system.	 */	if (unlikely(!rpool)) {		pr_warn("Invalid device %p or rdma cgroup %p\n", device, cg);		return;	} 	rpool->resources[index].usage--; 	/*	 * A negative count (or overflow) is invalid,	 * it indicates a bug in the rdma controller.	 */	WARN_ON_ONCE(rpool->resources[index].usage < 0);	rpool->usage_sum--;	if (rpool->usage_sum == 0 &&	    rpool->num_max_cnt == RDMACG_RESOURCE_MAX) {		if (!rpool_has_persistent_state(rpool)) {			/*			 * No user of the rpool and all entries are set to max, so			 * safe to delete this rpool.			 */			free_cg_rpool_locked(rpool);		}	}} /** * rdmacg_event_locked - fire event when resource allocation exceeds limit * @cg: requesting cgroup * @over_cg: cgroup whose limit was exceeded * @device: rdma device * @index: resource type index * * Must be called under rdmacg_mutex. Updates event counters in the * resource pools of @cg and @over_cg, propagates hierarchical max * events from @over_cg (including itself) upward, and notifies * userspace via cgroup_file_notify(). */static void rdmacg_event_locked(struct rdma_cgroup *cg,				struct rdma_cgroup *over_cg,				struct rdmacg_device *device,				enum rdmacg_resource_type index){	struct rdmacg_resource_pool *rpool;	struct rdma_cgroup *p; 	lockdep_assert_held(&rdmacg_mutex); 	/* Increment local alloc_fail in requesting cgroup */	rpool = find_cg_rpool_locked(cg, device);	if (rpool) {		rpool->events_local_alloc_fail[index]++;		cgroup_file_notify(&cg->events_local_file);	} 	/* Increment local max in the over-limit cgroup */	rpool = find_cg_rpool_locked(over_cg, device);	if (rpool) {		rpool->events_local_max[index]++;		cgroup_file_notify(&over_cg->events_local_file);	} 	/* Propagate hierarchical max events upward */	for (p = over_cg; parent_rdmacg(p); p = parent_rdmacg(p)) {		rpool = get_cg_rpool_locked(p, device);		if (!IS_ERR(rpool)) {			rpool->events_max[index]++;			cgroup_file_notify(&p->events_file);		}	}	/* Propagate hierarchical alloc_fail from requesting cgroup upward */	for (p = cg; parent_rdmacg(p); p = parent_rdmacg(p)) {		rpool = get_cg_rpool_locked(p, device);		if (!IS_ERR(rpool)) {			rpool->events_alloc_fail[index]++;			cgroup_file_notify(&p->events_file);		}	}} /** * rdmacg_uncharge_hierarchy - hierarchically uncharge rdma resource count * @cg: pointer to cg to uncharge and all parents in hierarchy * @device: pointer to rdmacg device * @stop_cg: while traversing hirerchy, when meet with stop_cg cgroup *           stop uncharging * @index: index of the resource to uncharge in cg in given resource pool */static void rdmacg_uncharge_hierarchy(struct rdma_cgroup *cg,				     struct rdmacg_device *device,				     struct rdma_cgroup *stop_cg,				     enum rdmacg_resource_type index){	struct rdma_cgroup *p; 	mutex_lock(&rdmacg_mutex); 	for (p = cg; p != stop_cg; p = parent_rdmacg(p))		uncharge_cg_locked(p, device, index); 	mutex_unlock(&rdmacg_mutex); 	css_put(&cg->css);} /** * rdmacg_uncharge - hierarchically uncharge rdma resource count * @cg: pointer to cg to uncharge and all parents in hierarchy * @device: pointer to rdmacg device * @index: index of the resource to uncharge in cgroup in given resource pool */void rdmacg_uncharge(struct rdma_cgroup *cg,		     struct rdmacg_device *device,		     enum rdmacg_resource_type index){	if (index >= RDMACG_RESOURCE_MAX)		return; 	rdmacg_uncharge_hierarchy(cg, device, NULL, index);}EXPORT_SYMBOL(rdmacg_uncharge); /** * rdmacg_try_charge - hierarchically try to charge the rdma resource * @rdmacg: pointer to rdma cgroup which will own this resource * @device: pointer to rdmacg device * @index: index of the resource to charge in cgroup (resource pool) * * This function follows charging resource in hierarchical way. * It will fail if the charge would cause the new value to exceed the * hierarchical limit. * Returns 0 if the charge succeeded, otherwise -EAGAIN, -ENOMEM or -EINVAL. * Returns pointer to rdmacg for this resource when charging is successful. * * Charger needs to account resources on two criteria. * (a) per cgroup & (b) per device resource usage. * Per cgroup resource usage ensures that tasks of cgroup doesn't cross * the configured limits. Per device provides granular configuration * in multi device usage. It allocates resource pool in the hierarchy * for each parent it come across for first resource. Later on resource * pool will be available. Therefore it will be much faster thereon * to charge/uncharge. */int rdmacg_try_charge(struct rdma_cgroup **rdmacg,		      struct rdmacg_device *device,		      enum rdmacg_resource_type index){	struct rdma_cgroup *cg, *p;	struct rdmacg_resource_pool *rpool;	s64 new;	int ret = 0; 	if (index >= RDMACG_RESOURCE_MAX)		return -EINVAL; 	/*	 * hold on to css, as cgroup can be removed but resource	 * accounting happens on css.	 */	cg = get_current_rdmacg(); 	mutex_lock(&rdmacg_mutex);	for (p = cg; p; p = parent_rdmacg(p)) {		rpool = get_cg_rpool_locked(p, device);		if (IS_ERR(rpool)) {			ret = PTR_ERR(rpool);			goto err;		} else {			new = (s64)rpool->resources[index].usage + 1;			if (new > rpool->resources[index].max) {				ret = -EAGAIN;				goto err;			} else {				rpool->resources[index].usage = new;				rpool->usage_sum++;			}		}	}	/* Update peak only after all charges succeed */	for (p = cg; p; p = parent_rdmacg(p)) {		rpool = find_cg_rpool_locked(p, device);		if (rpool && rpool->resources[index].usage > rpool->resources[index].peak)			rpool->resources[index].peak = rpool->resources[index].usage;	}	mutex_unlock(&rdmacg_mutex); 	*rdmacg = cg;	return 0; err:	if (ret == -EAGAIN)		rdmacg_event_locked(cg, p, device, index);	mutex_unlock(&rdmacg_mutex);	rdmacg_uncharge_hierarchy(cg, device, p, index);	return ret;}EXPORT_SYMBOL(rdmacg_try_charge); /** * rdmacg_register_device - register rdmacg device to rdma controller. * @device: pointer to rdmacg device whose resources need to be accounted. * * If IB stack wish a device to participate in rdma cgroup resource * tracking, it must invoke this API to register with rdma cgroup before * any user space application can start using the RDMA resources. */void rdmacg_register_device(struct rdmacg_device *device){	INIT_LIST_HEAD(&device->dev_node);	INIT_LIST_HEAD(&device->rpools); 	mutex_lock(&rdmacg_mutex);	list_add_tail(&device->dev_node, &rdmacg_devices);	mutex_unlock(&rdmacg_mutex);}EXPORT_SYMBOL(rdmacg_register_device); /** * rdmacg_unregister_device - unregister rdmacg device from rdma controller. * @device: pointer to rdmacg device which was previously registered with rdma *          controller using rdmacg_register_device(). * * IB stack must invoke this after all the resources of the IB device * are destroyed and after ensuring that no more resources will be created * when this API is invoked. */void rdmacg_unregister_device(struct rdmacg_device *device){	struct rdmacg_resource_pool *rpool, *tmp; 	/*	 * Synchronize with any active resource settings,	 * usage query happening via configfs.	 */	mutex_lock(&rdmacg_mutex);	list_del_init(&device->dev_node); 	/*	 * Now that this device is off the cgroup list, its safe to free	 * all the rpool resources.	 */	list_for_each_entry_safe(rpool, tmp, &device->rpools, dev_node)		free_cg_rpool_locked(rpool); 	mutex_unlock(&rdmacg_mutex);}EXPORT_SYMBOL(rdmacg_unregister_device); static struct rdmacg_device *rdmacg_get_device_locked(const char *name, bool has_index, u32 index){	struct rdmacg_device *match = NULL;	struct rdmacg_device *device; 	lockdep_assert_held(&rdmacg_mutex); 	list_for_each_entry(device, &rdmacg_devices, dev_node) {		if (strcmp(name, device->name))			continue; 		if (has_index) {			if (device->index == index)				return device;			continue;		} 		if (match)			return ERR_PTR(-ENOTUNIQ);		match = device;	} 	return match ?: ERR_PTR(-ENODEV);} static boolrdmacg_device_name_unique_locked(const struct rdmacg_device *device){	struct rdmacg_device *other; 	lockdep_assert_held(&rdmacg_mutex);

Lines 1–500 of 836.