forked from Qortal/Brooklyn
Ring the door. Take your seat moosey! Ring the door. Take your seat moosey! Ring the door. Take your seat moosey! Ring the door. Take your seat moosey! Ring the door. Take your seat moosey! Ring the door. Take your seat moosey! Ring the door. Take your seat moosey! Ring the door. Take your seat moosey! Ring the door. Take your seat moosey! Ring the door. Take your seat moosey! Ring the door. Take your seat moosey! Ring the door. Take your seat moosey! Ring the door. Take your seat moosey! Ring the door. Take your seat moosey! Ring the door. Take your seat moosey! Ring the door. Take your seat moosey! Ring the door. Take your seat moosey! Ring the door. Take your seat moosey! Ring the door. Take your seat moosey! Ring the door. Take your seat moosey! Ring the door. Take your seat moosey! Ring the door. Take your seat moosey! Ring the door. Take your seat moosey! Ring the door. Take your seat moosey! Ring the door. Take your seat moosey! Ring the door. Take your seat moosey! Ring the door. Take your seat moosey! Ring the door. Take your seat moosey! Ring the door. Take your seat moosey! Ring the door. Take your seat moosey! Ring the door. Take your seat moosey! Ring the door. Take your seat moosey! Ring the door. Take your seat moosey! Ring the door. Take your seat moosey! Ring the door. Take your seat moosey! Ring the door. Take your seat moosey! Ring the door. Take your seat moosey! Ring the door. Take your seat moosey! Ring the door. Take your seat moosey! Ring the door. Take your seat moosey! Ring the door. Take your seat moosey! Ring the door. Take your seat moosey! Ring the door. Take your seat moosey! Ring the door. Take your seat moosey! Ring the door. Take your seat moosey! Ring the door. Take your seat moosey! Ring the door. Take your seat moosey! Ring the door. Take your seat moosey! Ring the door. Take your seat moosey! Ring the door. Take your seat moosey! Ring the door. Take your seat moosey! Ring the door. Take your seat moosey! Ring the door. Take your seat moosey! Ring the door. Take your seat moosey! Ring the door. Take your seat moosey! Ring the door. Take your seat moosey! Ring the door. Take your seat moosey! Ring the door. Take your seat moosey! Ring the door. Take your seat moosey! Ring the door. Take your seat moosey! Ring the door. Take your seat moosey! Ring the door. Take your seat moosey! Ring the door. Take your seat moosey! Ring the door. Take your seat moosey! Ring the door. Take your seat moosey! Ring the door. Take your seat moosey! Ring the door. Take your seat moosey! Ring the door. Take your seat moosey! Ring the door. Take your seat moosey! Ring the door. Take your seat moosey! Ring the door. Take your seat moosey! Ring the door. Take your seat moosey! Ring the door. Take your seat moosey! Ring the door. Take your seat moosey! Ring the door. Take your seat moosey! Ring the door. Take your seat moosey! Ring the door. Take your seat moosey! Ring the door. Take your seat moosey! Ring the door. Take your seat moosey! Ring the door. Take your seat moosey! Ring the door. Take your seat moosey! Ring the door. Take your seat moosey! Ring the door. Take your seat moosey! Ring the door. Take your seat moosey! Ring the door. Take your seat moosey! Ring the door. Take your seat moosey! Ring the door. Take your seat moosey! Ring the door. Take your seat moosey! Ring the door. Take your seat moosey! Ring the door. Take your seat moosey! Ring the door. Take your seat moosey! Ring the door. Take your seat moosey! Ring the door. Take your seat moosey! Ring the door. Take your seat moosey! Ring the door. Take your seat moosey! Ring the door. Take your seat moosey! Ring the door. Take your seat moosey! Ring the door. Take your seat moosey! Ring the door. Take your seat moosey! Ring the door. Take your seat moosey! Ring the door. Take your seat moosey! Ring the door. Take your seat moosey! Ring the door. Take your seat moosey! Ring the door. Take your seat moosey! Ring the door. Take your seat moosey! Ring the door. Take your seat moosey! Ring the door. Take your seat moosey! Ring the door. Take your seat moosey! Ring the door. Take your seat moosey! Ring the door. Take your seat moosey! Ring the door. Take your seat moosey! Ring the door. Take your seat moosey!
508 lines
12 KiB
C
508 lines
12 KiB
C
// SPDX-License-Identifier: GPL-2.0-or-later
|
|
/*
|
|
* RDMA Transport Layer
|
|
*
|
|
* Copyright (c) 2014 - 2018 ProfitBricks GmbH. All rights reserved.
|
|
* Copyright (c) 2018 - 2019 1&1 IONOS Cloud GmbH. All rights reserved.
|
|
* Copyright (c) 2019 - 2020 1&1 IONOS SE. All rights reserved.
|
|
*/
|
|
#undef pr_fmt
|
|
#define pr_fmt(fmt) KBUILD_MODNAME " L" __stringify(__LINE__) ": " fmt
|
|
|
|
#include "rtrs-pri.h"
|
|
#include "rtrs-clt.h"
|
|
#include "rtrs-log.h"
|
|
|
|
#define MIN_MAX_RECONN_ATT -1
|
|
#define MAX_MAX_RECONN_ATT 9999
|
|
|
|
static void rtrs_clt_sess_release(struct kobject *kobj)
|
|
{
|
|
struct rtrs_clt_sess *sess;
|
|
|
|
sess = container_of(kobj, struct rtrs_clt_sess, kobj);
|
|
|
|
free_sess(sess);
|
|
}
|
|
|
|
static struct kobj_type ktype_sess = {
|
|
.sysfs_ops = &kobj_sysfs_ops,
|
|
.release = rtrs_clt_sess_release
|
|
};
|
|
|
|
static void rtrs_clt_sess_stats_release(struct kobject *kobj)
|
|
{
|
|
struct rtrs_clt_stats *stats;
|
|
|
|
stats = container_of(kobj, struct rtrs_clt_stats, kobj_stats);
|
|
|
|
free_percpu(stats->pcpu_stats);
|
|
|
|
kfree(stats);
|
|
}
|
|
|
|
static struct kobj_type ktype_stats = {
|
|
.sysfs_ops = &kobj_sysfs_ops,
|
|
.release = rtrs_clt_sess_stats_release,
|
|
};
|
|
|
|
static ssize_t max_reconnect_attempts_show(struct device *dev,
|
|
struct device_attribute *attr,
|
|
char *page)
|
|
{
|
|
struct rtrs_clt *clt = container_of(dev, struct rtrs_clt, dev);
|
|
|
|
return sysfs_emit(page, "%d\n",
|
|
rtrs_clt_get_max_reconnect_attempts(clt));
|
|
}
|
|
|
|
static ssize_t max_reconnect_attempts_store(struct device *dev,
|
|
struct device_attribute *attr,
|
|
const char *buf,
|
|
size_t count)
|
|
{
|
|
int value;
|
|
int ret;
|
|
struct rtrs_clt *clt = container_of(dev, struct rtrs_clt, dev);
|
|
|
|
ret = kstrtoint(buf, 10, &value);
|
|
if (ret) {
|
|
rtrs_err(clt, "%s: failed to convert string '%s' to int\n",
|
|
attr->attr.name, buf);
|
|
return ret;
|
|
}
|
|
if (value > MAX_MAX_RECONN_ATT ||
|
|
value < MIN_MAX_RECONN_ATT) {
|
|
rtrs_err(clt,
|
|
"%s: invalid range (provided: '%s', accepted: min: %d, max: %d)\n",
|
|
attr->attr.name, buf, MIN_MAX_RECONN_ATT,
|
|
MAX_MAX_RECONN_ATT);
|
|
return -EINVAL;
|
|
}
|
|
rtrs_clt_set_max_reconnect_attempts(clt, value);
|
|
|
|
return count;
|
|
}
|
|
|
|
static DEVICE_ATTR_RW(max_reconnect_attempts);
|
|
|
|
static ssize_t mpath_policy_show(struct device *dev,
|
|
struct device_attribute *attr,
|
|
char *page)
|
|
{
|
|
struct rtrs_clt *clt;
|
|
|
|
clt = container_of(dev, struct rtrs_clt, dev);
|
|
|
|
switch (clt->mp_policy) {
|
|
case MP_POLICY_RR:
|
|
return sysfs_emit(page, "round-robin (RR: %d)\n",
|
|
clt->mp_policy);
|
|
case MP_POLICY_MIN_INFLIGHT:
|
|
return sysfs_emit(page, "min-inflight (MI: %d)\n",
|
|
clt->mp_policy);
|
|
case MP_POLICY_MIN_LATENCY:
|
|
return sysfs_emit(page, "min-latency (ML: %d)\n",
|
|
clt->mp_policy);
|
|
default:
|
|
return sysfs_emit(page, "Unknown (%d)\n", clt->mp_policy);
|
|
}
|
|
}
|
|
|
|
static ssize_t mpath_policy_store(struct device *dev,
|
|
struct device_attribute *attr,
|
|
const char *buf,
|
|
size_t count)
|
|
{
|
|
struct rtrs_clt *clt;
|
|
int value;
|
|
int ret;
|
|
size_t len = 0;
|
|
|
|
clt = container_of(dev, struct rtrs_clt, dev);
|
|
|
|
ret = kstrtoint(buf, 10, &value);
|
|
if (!ret && (value == MP_POLICY_RR ||
|
|
value == MP_POLICY_MIN_INFLIGHT ||
|
|
value == MP_POLICY_MIN_LATENCY)) {
|
|
clt->mp_policy = value;
|
|
return count;
|
|
}
|
|
|
|
/* distinguish "mi" and "min-latency" with length */
|
|
len = strnlen(buf, NAME_MAX);
|
|
if (buf[len - 1] == '\n')
|
|
len--;
|
|
|
|
if (!strncasecmp(buf, "round-robin", 11) ||
|
|
(len == 2 && !strncasecmp(buf, "rr", 2)))
|
|
clt->mp_policy = MP_POLICY_RR;
|
|
else if (!strncasecmp(buf, "min-inflight", 12) ||
|
|
(len == 2 && !strncasecmp(buf, "mi", 2)))
|
|
clt->mp_policy = MP_POLICY_MIN_INFLIGHT;
|
|
else if (!strncasecmp(buf, "min-latency", 11) ||
|
|
(len == 2 && !strncasecmp(buf, "ml", 2)))
|
|
clt->mp_policy = MP_POLICY_MIN_LATENCY;
|
|
else
|
|
return -EINVAL;
|
|
|
|
return count;
|
|
}
|
|
|
|
static DEVICE_ATTR_RW(mpath_policy);
|
|
|
|
static ssize_t add_path_show(struct device *dev,
|
|
struct device_attribute *attr, char *page)
|
|
{
|
|
return sysfs_emit(
|
|
page,
|
|
"Usage: echo [<source addr>@]<destination addr> > %s\n\n*addr ::= [ ip:<ipv4|ipv6> | gid:<gid> ]\n",
|
|
attr->attr.name);
|
|
}
|
|
|
|
static ssize_t add_path_store(struct device *dev,
|
|
struct device_attribute *attr,
|
|
const char *buf, size_t count)
|
|
{
|
|
struct sockaddr_storage srcaddr, dstaddr;
|
|
struct rtrs_addr addr = {
|
|
.src = &srcaddr,
|
|
.dst = &dstaddr
|
|
};
|
|
struct rtrs_clt *clt;
|
|
const char *nl;
|
|
size_t len;
|
|
int err;
|
|
|
|
clt = container_of(dev, struct rtrs_clt, dev);
|
|
|
|
nl = strchr(buf, '\n');
|
|
if (nl)
|
|
len = nl - buf;
|
|
else
|
|
len = count;
|
|
err = rtrs_addr_to_sockaddr(buf, len, clt->port, &addr);
|
|
if (err)
|
|
return -EINVAL;
|
|
|
|
err = rtrs_clt_create_path_from_sysfs(clt, &addr);
|
|
if (err)
|
|
return err;
|
|
|
|
return count;
|
|
}
|
|
|
|
static DEVICE_ATTR_RW(add_path);
|
|
|
|
static ssize_t rtrs_clt_state_show(struct kobject *kobj,
|
|
struct kobj_attribute *attr, char *page)
|
|
{
|
|
struct rtrs_clt_sess *sess;
|
|
|
|
sess = container_of(kobj, struct rtrs_clt_sess, kobj);
|
|
if (sess->state == RTRS_CLT_CONNECTED)
|
|
return sysfs_emit(page, "connected\n");
|
|
|
|
return sysfs_emit(page, "disconnected\n");
|
|
}
|
|
|
|
static struct kobj_attribute rtrs_clt_state_attr =
|
|
__ATTR(state, 0444, rtrs_clt_state_show, NULL);
|
|
|
|
static ssize_t rtrs_clt_reconnect_show(struct kobject *kobj,
|
|
struct kobj_attribute *attr, char *buf)
|
|
{
|
|
return sysfs_emit(buf, "Usage: echo 1 > %s\n", attr->attr.name);
|
|
}
|
|
|
|
static ssize_t rtrs_clt_reconnect_store(struct kobject *kobj,
|
|
struct kobj_attribute *attr,
|
|
const char *buf, size_t count)
|
|
{
|
|
struct rtrs_clt_sess *sess;
|
|
int ret;
|
|
|
|
sess = container_of(kobj, struct rtrs_clt_sess, kobj);
|
|
if (!sysfs_streq(buf, "1")) {
|
|
rtrs_err(sess->clt, "%s: unknown value: '%s'\n",
|
|
attr->attr.name, buf);
|
|
return -EINVAL;
|
|
}
|
|
ret = rtrs_clt_reconnect_from_sysfs(sess);
|
|
if (ret)
|
|
return ret;
|
|
|
|
return count;
|
|
}
|
|
|
|
static struct kobj_attribute rtrs_clt_reconnect_attr =
|
|
__ATTR(reconnect, 0644, rtrs_clt_reconnect_show,
|
|
rtrs_clt_reconnect_store);
|
|
|
|
static ssize_t rtrs_clt_disconnect_show(struct kobject *kobj,
|
|
struct kobj_attribute *attr, char *buf)
|
|
{
|
|
return sysfs_emit(buf, "Usage: echo 1 > %s\n", attr->attr.name);
|
|
}
|
|
|
|
static ssize_t rtrs_clt_disconnect_store(struct kobject *kobj,
|
|
struct kobj_attribute *attr,
|
|
const char *buf, size_t count)
|
|
{
|
|
struct rtrs_clt_sess *sess;
|
|
|
|
sess = container_of(kobj, struct rtrs_clt_sess, kobj);
|
|
if (!sysfs_streq(buf, "1")) {
|
|
rtrs_err(sess->clt, "%s: unknown value: '%s'\n",
|
|
attr->attr.name, buf);
|
|
return -EINVAL;
|
|
}
|
|
rtrs_clt_close_conns(sess, true);
|
|
|
|
return count;
|
|
}
|
|
|
|
static struct kobj_attribute rtrs_clt_disconnect_attr =
|
|
__ATTR(disconnect, 0644, rtrs_clt_disconnect_show,
|
|
rtrs_clt_disconnect_store);
|
|
|
|
static ssize_t rtrs_clt_remove_path_show(struct kobject *kobj,
|
|
struct kobj_attribute *attr, char *buf)
|
|
{
|
|
return sysfs_emit(buf, "Usage: echo 1 > %s\n", attr->attr.name);
|
|
}
|
|
|
|
static ssize_t rtrs_clt_remove_path_store(struct kobject *kobj,
|
|
struct kobj_attribute *attr,
|
|
const char *buf, size_t count)
|
|
{
|
|
struct rtrs_clt_sess *sess;
|
|
int ret;
|
|
|
|
sess = container_of(kobj, struct rtrs_clt_sess, kobj);
|
|
if (!sysfs_streq(buf, "1")) {
|
|
rtrs_err(sess->clt, "%s: unknown value: '%s'\n",
|
|
attr->attr.name, buf);
|
|
return -EINVAL;
|
|
}
|
|
ret = rtrs_clt_remove_path_from_sysfs(sess, &attr->attr);
|
|
if (ret)
|
|
return ret;
|
|
|
|
return count;
|
|
}
|
|
|
|
static struct kobj_attribute rtrs_clt_remove_path_attr =
|
|
__ATTR(remove_path, 0644, rtrs_clt_remove_path_show,
|
|
rtrs_clt_remove_path_store);
|
|
|
|
STAT_ATTR(struct rtrs_clt_stats, cpu_migration,
|
|
rtrs_clt_stats_migration_cnt_to_str,
|
|
rtrs_clt_reset_cpu_migr_stats);
|
|
|
|
STAT_ATTR(struct rtrs_clt_stats, reconnects,
|
|
rtrs_clt_stats_reconnects_to_str,
|
|
rtrs_clt_reset_reconnects_stat);
|
|
|
|
STAT_ATTR(struct rtrs_clt_stats, rdma,
|
|
rtrs_clt_stats_rdma_to_str,
|
|
rtrs_clt_reset_rdma_stats);
|
|
|
|
STAT_ATTR(struct rtrs_clt_stats, reset_all,
|
|
rtrs_clt_reset_all_help,
|
|
rtrs_clt_reset_all_stats);
|
|
|
|
static struct attribute *rtrs_clt_stats_attrs[] = {
|
|
&cpu_migration_attr.attr,
|
|
&reconnects_attr.attr,
|
|
&rdma_attr.attr,
|
|
&reset_all_attr.attr,
|
|
NULL
|
|
};
|
|
|
|
static const struct attribute_group rtrs_clt_stats_attr_group = {
|
|
.attrs = rtrs_clt_stats_attrs,
|
|
};
|
|
|
|
static ssize_t rtrs_clt_hca_port_show(struct kobject *kobj,
|
|
struct kobj_attribute *attr,
|
|
char *page)
|
|
{
|
|
struct rtrs_clt_sess *sess;
|
|
|
|
sess = container_of(kobj, typeof(*sess), kobj);
|
|
|
|
return sysfs_emit(page, "%u\n", sess->hca_port);
|
|
}
|
|
|
|
static struct kobj_attribute rtrs_clt_hca_port_attr =
|
|
__ATTR(hca_port, 0444, rtrs_clt_hca_port_show, NULL);
|
|
|
|
static ssize_t rtrs_clt_hca_name_show(struct kobject *kobj,
|
|
struct kobj_attribute *attr,
|
|
char *page)
|
|
{
|
|
struct rtrs_clt_sess *sess;
|
|
|
|
sess = container_of(kobj, struct rtrs_clt_sess, kobj);
|
|
|
|
return sysfs_emit(page, "%s\n", sess->hca_name);
|
|
}
|
|
|
|
static struct kobj_attribute rtrs_clt_hca_name_attr =
|
|
__ATTR(hca_name, 0444, rtrs_clt_hca_name_show, NULL);
|
|
|
|
static ssize_t rtrs_clt_cur_latency_show(struct kobject *kobj,
|
|
struct kobj_attribute *attr,
|
|
char *page)
|
|
{
|
|
struct rtrs_clt_sess *sess;
|
|
|
|
sess = container_of(kobj, struct rtrs_clt_sess, kobj);
|
|
|
|
return sysfs_emit(page, "%lld ns\n",
|
|
ktime_to_ns(sess->s.hb_cur_latency));
|
|
}
|
|
|
|
static struct kobj_attribute rtrs_clt_cur_latency_attr =
|
|
__ATTR(cur_latency, 0444, rtrs_clt_cur_latency_show, NULL);
|
|
|
|
static ssize_t rtrs_clt_src_addr_show(struct kobject *kobj,
|
|
struct kobj_attribute *attr,
|
|
char *page)
|
|
{
|
|
struct rtrs_clt_sess *sess;
|
|
int len;
|
|
|
|
sess = container_of(kobj, struct rtrs_clt_sess, kobj);
|
|
len = sockaddr_to_str((struct sockaddr *)&sess->s.src_addr, page,
|
|
PAGE_SIZE);
|
|
len += sysfs_emit_at(page, len, "\n");
|
|
return len;
|
|
}
|
|
|
|
static struct kobj_attribute rtrs_clt_src_addr_attr =
|
|
__ATTR(src_addr, 0444, rtrs_clt_src_addr_show, NULL);
|
|
|
|
static ssize_t rtrs_clt_dst_addr_show(struct kobject *kobj,
|
|
struct kobj_attribute *attr,
|
|
char *page)
|
|
{
|
|
struct rtrs_clt_sess *sess;
|
|
int len;
|
|
|
|
sess = container_of(kobj, struct rtrs_clt_sess, kobj);
|
|
len = sockaddr_to_str((struct sockaddr *)&sess->s.dst_addr, page,
|
|
PAGE_SIZE);
|
|
len += sysfs_emit_at(page, len, "\n");
|
|
return len;
|
|
}
|
|
|
|
static struct kobj_attribute rtrs_clt_dst_addr_attr =
|
|
__ATTR(dst_addr, 0444, rtrs_clt_dst_addr_show, NULL);
|
|
|
|
static struct attribute *rtrs_clt_sess_attrs[] = {
|
|
&rtrs_clt_hca_name_attr.attr,
|
|
&rtrs_clt_hca_port_attr.attr,
|
|
&rtrs_clt_src_addr_attr.attr,
|
|
&rtrs_clt_dst_addr_attr.attr,
|
|
&rtrs_clt_state_attr.attr,
|
|
&rtrs_clt_reconnect_attr.attr,
|
|
&rtrs_clt_disconnect_attr.attr,
|
|
&rtrs_clt_remove_path_attr.attr,
|
|
&rtrs_clt_cur_latency_attr.attr,
|
|
NULL,
|
|
};
|
|
|
|
static const struct attribute_group rtrs_clt_sess_attr_group = {
|
|
.attrs = rtrs_clt_sess_attrs,
|
|
};
|
|
|
|
int rtrs_clt_create_sess_files(struct rtrs_clt_sess *sess)
|
|
{
|
|
struct rtrs_clt *clt = sess->clt;
|
|
char str[NAME_MAX];
|
|
int err;
|
|
struct rtrs_addr path = {
|
|
.src = &sess->s.src_addr,
|
|
.dst = &sess->s.dst_addr,
|
|
};
|
|
|
|
rtrs_addr_to_str(&path, str, sizeof(str));
|
|
err = kobject_init_and_add(&sess->kobj, &ktype_sess, clt->kobj_paths,
|
|
"%s", str);
|
|
if (err) {
|
|
pr_err("kobject_init_and_add: %d\n", err);
|
|
kobject_put(&sess->kobj);
|
|
return err;
|
|
}
|
|
err = sysfs_create_group(&sess->kobj, &rtrs_clt_sess_attr_group);
|
|
if (err) {
|
|
pr_err("sysfs_create_group(): %d\n", err);
|
|
goto put_kobj;
|
|
}
|
|
err = kobject_init_and_add(&sess->stats->kobj_stats, &ktype_stats,
|
|
&sess->kobj, "stats");
|
|
if (err) {
|
|
pr_err("kobject_init_and_add: %d\n", err);
|
|
kobject_put(&sess->stats->kobj_stats);
|
|
goto remove_group;
|
|
}
|
|
|
|
err = sysfs_create_group(&sess->stats->kobj_stats,
|
|
&rtrs_clt_stats_attr_group);
|
|
if (err) {
|
|
pr_err("failed to create stats sysfs group, err: %d\n", err);
|
|
goto put_kobj_stats;
|
|
}
|
|
|
|
return 0;
|
|
|
|
put_kobj_stats:
|
|
kobject_del(&sess->stats->kobj_stats);
|
|
kobject_put(&sess->stats->kobj_stats);
|
|
remove_group:
|
|
sysfs_remove_group(&sess->kobj, &rtrs_clt_sess_attr_group);
|
|
put_kobj:
|
|
kobject_del(&sess->kobj);
|
|
kobject_put(&sess->kobj);
|
|
|
|
return err;
|
|
}
|
|
|
|
void rtrs_clt_destroy_sess_files(struct rtrs_clt_sess *sess,
|
|
const struct attribute *sysfs_self)
|
|
{
|
|
kobject_del(&sess->stats->kobj_stats);
|
|
kobject_put(&sess->stats->kobj_stats);
|
|
if (sysfs_self)
|
|
sysfs_remove_file_self(&sess->kobj, sysfs_self);
|
|
kobject_del(&sess->kobj);
|
|
}
|
|
|
|
static struct attribute *rtrs_clt_attrs[] = {
|
|
&dev_attr_max_reconnect_attempts.attr,
|
|
&dev_attr_mpath_policy.attr,
|
|
&dev_attr_add_path.attr,
|
|
NULL,
|
|
};
|
|
|
|
static const struct attribute_group rtrs_clt_attr_group = {
|
|
.attrs = rtrs_clt_attrs,
|
|
};
|
|
|
|
int rtrs_clt_create_sysfs_root_files(struct rtrs_clt *clt)
|
|
{
|
|
return sysfs_create_group(&clt->dev.kobj, &rtrs_clt_attr_group);
|
|
}
|
|
|
|
void rtrs_clt_destroy_sysfs_root(struct rtrs_clt *clt)
|
|
{
|
|
sysfs_remove_group(&clt->dev.kobj, &rtrs_clt_attr_group);
|
|
|
|
if (clt->kobj_paths) {
|
|
kobject_del(clt->kobj_paths);
|
|
kobject_put(clt->kobj_paths);
|
|
}
|
|
}
|