Files
linux/kernel/cgroup/rdma.c
Jiri Pirko c4ef67e5aa RDMA/cgroup: Disambiguate devices across net namespaces
RDMA device names are unique only within a network namespace, but an
RDMA cgroup can account resources for devices from multiple namespaces.
Duplicate names therefore make cgroup output ambiguous and can cause
limit writes to select the wrong device.

Use the system-wide RDMA device index to distinguish duplicate names
while preserving the existing UAPI for unique names. Reject ambiguous
name-only writes with -ENOTUNIQ and expose a complete device view to
administrators.

Signed-off-by: Jiri Pirko <jiri@nvidia.com>
Link: https://patch.msgid.link/20260716132316.1495242-9-jiri@resnulli.us
Signed-off-by: Leon Romanovsky <leon@kernel.org>
2026-07-26 03:11:00 -04:00

837 lines
21 KiB
C

// 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 void
uncharge_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 bool
rdmacg_device_name_unique_locked(const struct rdmacg_device *device)
{
struct rdmacg_device *other;
lockdep_assert_held(&rdmacg_mutex);
list_for_each_entry(other, &rdmacg_devices, dev_node)
if (other != device && !strcmp(other->name, device->name))
return false;
return true;
}
static void rdmacg_print_device_key(struct seq_file *sf,
const struct rdmacg_device *device)
{
seq_puts(sf, device->name);
if (!rdmacg_device_name_unique_locked(device))
seq_printf(sf, " index=%u", device->index);
seq_putc(sf, ' ');
}
static ssize_t rdmacg_resource_set_max(struct kernfs_open_file *of,
char *buf, size_t nbytes, loff_t off)
{
struct rdma_cgroup *cg = css_rdmacg(of_css(of));
const char *dev_name;
struct rdmacg_resource_pool *rpool;
struct rdmacg_device *device;
char *options = strstrip(buf);
char *p;
int *new_limits;
unsigned long enables = 0;
u32 dev_index = 0;
bool has_index = false;
int i = 0, ret = 0;
/* extract the device name first */
dev_name = strsep(&options, " ");
if (!dev_name) {
ret = -EINVAL;
goto err;
}
new_limits = kzalloc_objs(int, RDMACG_RESOURCE_MAX);
if (!new_limits) {
ret = -ENOMEM;
goto err;
}
/* parse the optional device index and resource limit tokens */
while ((p = strsep(&options, " \t\n"))) {
substring_t args[MAX_OPT_ARGS];
int tok, intval;
if (!*p)
continue;
tok = match_token(p, rdmacg_limit_tokens, args);
switch (tok) {
case RDMACG_DEVICE_INDEX:
if (has_index || match_uint(&args[0], &dev_index)) {
ret = -EINVAL;
goto parse_err;
}
has_index = true;
break;
case RDMACG_HCA_HANDLE_VAL:
if (match_int(&args[0], &intval) || intval < 0) {
ret = -EINVAL;
goto parse_err;
}
new_limits[RDMACG_RESOURCE_HCA_HANDLE] = intval;
enables |= BIT(RDMACG_RESOURCE_HCA_HANDLE);
break;
case RDMACG_HCA_HANDLE_MAX:
new_limits[RDMACG_RESOURCE_HCA_HANDLE] = S32_MAX;
enables |= BIT(RDMACG_RESOURCE_HCA_HANDLE);
break;
case RDMACG_HCA_OBJECT_VAL:
if (match_int(&args[0], &intval) || intval < 0) {
ret = -EINVAL;
goto parse_err;
}
new_limits[RDMACG_RESOURCE_HCA_OBJECT] = intval;
enables |= BIT(RDMACG_RESOURCE_HCA_OBJECT);
break;
case RDMACG_HCA_OBJECT_MAX:
new_limits[RDMACG_RESOURCE_HCA_OBJECT] = S32_MAX;
enables |= BIT(RDMACG_RESOURCE_HCA_OBJECT);
break;
default:
ret = -EINVAL;
goto parse_err;
}
}
/* acquire lock to synchronize with hot plug devices */
mutex_lock(&rdmacg_mutex);
device = rdmacg_get_device_locked(dev_name, has_index, dev_index);
if (IS_ERR(device)) {
ret = PTR_ERR(device);
goto dev_err;
}
rpool = get_cg_rpool_locked(cg, device);
if (IS_ERR(rpool)) {
ret = PTR_ERR(rpool);
goto dev_err;
}
/* now set the new limits of the rpool */
for_each_set_bit(i, &enables, RDMACG_RESOURCE_MAX)
set_resource_limit(rpool, i, new_limits[i]);
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);
}
}
dev_err:
mutex_unlock(&rdmacg_mutex);
parse_err:
kfree(new_limits);
err:
return ret ?: nbytes;
}
static void print_rpool_values(struct seq_file *sf,
struct rdmacg_resource_pool *rpool)
{
enum rdmacg_file_type sf_type;
int i;
u32 value;
sf_type = seq_cft(sf)->private;
for (i = 0; i < RDMACG_RESOURCE_MAX; i++) {
seq_puts(sf, rdmacg_resource_names[i]);
seq_putc(sf, '=');
if (sf_type == RDMACG_RESOURCE_TYPE_MAX) {
if (rpool)
value = rpool->resources[i].max;
else
value = S32_MAX;
} else if (sf_type == RDMACG_RESOURCE_TYPE_PEAK) {
value = rpool ? rpool->resources[i].peak : 0;
} else {
if (rpool)
value = rpool->resources[i].usage;
else
value = 0;
}
if (value == S32_MAX)
seq_puts(sf, RDMACG_MAX_STR);
else
seq_printf(sf, "%d", value);
seq_putc(sf, ' ');
}
}
static int rdmacg_resource_read(struct seq_file *sf, void *v)
{
struct rdmacg_device *device;
struct rdmacg_resource_pool *rpool;
struct rdma_cgroup *cg = css_rdmacg(seq_css(sf));
mutex_lock(&rdmacg_mutex);
list_for_each_entry(device, &rdmacg_devices, dev_node) {
rdmacg_print_device_key(sf, device);
rpool = find_cg_rpool_locked(cg, device);
print_rpool_values(sf, rpool);
seq_putc(sf, '\n');
}
mutex_unlock(&rdmacg_mutex);
return 0;
}
static int rdmacg_events_show(struct seq_file *sf, void *v)
{
struct rdma_cgroup *cg = css_rdmacg(seq_css(sf));
struct rdmacg_resource_pool *rpool;
struct rdmacg_device *device;
int i;
mutex_lock(&rdmacg_mutex);
list_for_each_entry(device, &rdmacg_devices, dev_node) {
rpool = find_cg_rpool_locked(cg, device);
rdmacg_print_device_key(sf, device);
for (i = 0; i < RDMACG_RESOURCE_MAX; i++) {
seq_printf(sf, "%s.max=%llu %s.alloc_fail=%llu",
rdmacg_resource_names[i],
rpool ? rpool->events_max[i] : 0ULL,
rdmacg_resource_names[i],
rpool ? rpool->events_alloc_fail[i] : 0ULL);
if (i < RDMACG_RESOURCE_MAX - 1)
seq_putc(sf, ' ');
}
seq_putc(sf, '\n');
}
mutex_unlock(&rdmacg_mutex);
return 0;
}
static int rdmacg_events_local_show(struct seq_file *sf, void *v)
{
struct rdma_cgroup *cg = css_rdmacg(seq_css(sf));
struct rdmacg_resource_pool *rpool;
struct rdmacg_device *device;
int i;
mutex_lock(&rdmacg_mutex);
list_for_each_entry(device, &rdmacg_devices, dev_node) {
rpool = find_cg_rpool_locked(cg, device);
rdmacg_print_device_key(sf, device);
for (i = 0; i < RDMACG_RESOURCE_MAX; i++) {
seq_printf(sf, "%s.max=%llu %s.alloc_fail=%llu",
rdmacg_resource_names[i],
rpool ? rpool->events_local_max[i] : 0ULL,
rdmacg_resource_names[i],
rpool ? rpool->events_local_alloc_fail[i] : 0ULL);
if (i < RDMACG_RESOURCE_MAX - 1)
seq_putc(sf, ' ');
}
seq_putc(sf, '\n');
}
mutex_unlock(&rdmacg_mutex);
return 0;
}
static struct cftype rdmacg_files[] = {
{
.name = "max",
.write = rdmacg_resource_set_max,
.seq_show = rdmacg_resource_read,
.private = RDMACG_RESOURCE_TYPE_MAX,
.flags = CFTYPE_NOT_ON_ROOT,
},
{
.name = "current",
.seq_show = rdmacg_resource_read,
.private = RDMACG_RESOURCE_TYPE_STAT,
.flags = CFTYPE_NOT_ON_ROOT,
},
{
.name = "peak",
.seq_show = rdmacg_resource_read,
.private = RDMACG_RESOURCE_TYPE_PEAK,
.flags = CFTYPE_NOT_ON_ROOT,
},
{
.name = "events",
.seq_show = rdmacg_events_show,
.file_offset = offsetof(struct rdma_cgroup, events_file),
.flags = CFTYPE_NOT_ON_ROOT,
},
{
.name = "events.local",
.seq_show = rdmacg_events_local_show,
.file_offset = offsetof(struct rdma_cgroup, events_local_file),
.flags = CFTYPE_NOT_ON_ROOT,
},
{ } /* terminate */
};
static struct cgroup_subsys_state *
rdmacg_css_alloc(struct cgroup_subsys_state *parent)
{
struct rdma_cgroup *cg;
cg = kzalloc_obj(*cg);
if (!cg)
return ERR_PTR(-ENOMEM);
INIT_LIST_HEAD(&cg->rpools);
return &cg->css;
}
static void rdmacg_css_free(struct cgroup_subsys_state *css)
{
struct rdma_cgroup *cg = css_rdmacg(css);
struct rdmacg_resource_pool *rpool, *tmp;
/* Clean up rpools kept alive by non-zero peak values */
mutex_lock(&rdmacg_mutex);
list_for_each_entry_safe(rpool, tmp, &cg->rpools, cg_node)
free_cg_rpool_locked(rpool);
mutex_unlock(&rdmacg_mutex);
kfree(cg);
}
/**
* rdmacg_css_offline - cgroup css_offline callback
* @css: css of interest
*
* This function is called when @css is about to go away and responsible
* for shooting down all rdmacg associated with @css. As part of that it
* marks all the resource pool entries to max value, so that when resources are
* uncharged, associated resource pool can be freed as well.
*/
static void rdmacg_css_offline(struct cgroup_subsys_state *css)
{
struct rdma_cgroup *cg = css_rdmacg(css);
struct rdmacg_resource_pool *rpool;
mutex_lock(&rdmacg_mutex);
list_for_each_entry(rpool, &cg->rpools, cg_node)
set_all_resource_max_limit(rpool);
mutex_unlock(&rdmacg_mutex);
}
struct cgroup_subsys rdma_cgrp_subsys = {
.css_alloc = rdmacg_css_alloc,
.css_free = rdmacg_css_free,
.css_offline = rdmacg_css_offline,
.legacy_cftypes = rdmacg_files,
.dfl_cftypes = rdmacg_files,
};