forked from Qortal/Brooklyn
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
570 lines
13 KiB
570 lines
13 KiB
// SPDX-License-Identifier: GPL-2.0 |
|
/* |
|
* System Trace Module (STM) master/channel allocation policy management |
|
* Copyright (c) 2014, Intel Corporation. |
|
* |
|
* A master/channel allocation policy allows mapping string identifiers to |
|
* master and channel ranges, where allocation can be done. |
|
*/ |
|
|
|
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt |
|
|
|
#include <linux/types.h> |
|
#include <linux/module.h> |
|
#include <linux/device.h> |
|
#include <linux/configfs.h> |
|
#include <linux/slab.h> |
|
#include <linux/stm.h> |
|
#include "stm.h" |
|
|
|
/* |
|
* STP Master/Channel allocation policy configfs layout. |
|
*/ |
|
|
|
struct stp_policy { |
|
struct config_group group; |
|
struct stm_device *stm; |
|
}; |
|
|
|
struct stp_policy_node { |
|
struct config_group group; |
|
struct stp_policy *policy; |
|
unsigned int first_master; |
|
unsigned int last_master; |
|
unsigned int first_channel; |
|
unsigned int last_channel; |
|
/* this is the one that's exposed to the attributes */ |
|
unsigned char priv[]; |
|
}; |
|
|
|
void *stp_policy_node_priv(struct stp_policy_node *pn) |
|
{ |
|
if (!pn) |
|
return NULL; |
|
|
|
return pn->priv; |
|
} |
|
|
|
static struct configfs_subsystem stp_policy_subsys; |
|
|
|
void stp_policy_node_get_ranges(struct stp_policy_node *policy_node, |
|
unsigned int *mstart, unsigned int *mend, |
|
unsigned int *cstart, unsigned int *cend) |
|
{ |
|
*mstart = policy_node->first_master; |
|
*mend = policy_node->last_master; |
|
*cstart = policy_node->first_channel; |
|
*cend = policy_node->last_channel; |
|
} |
|
|
|
static inline struct stp_policy *to_stp_policy(struct config_item *item) |
|
{ |
|
return item ? |
|
container_of(to_config_group(item), struct stp_policy, group) : |
|
NULL; |
|
} |
|
|
|
static inline struct stp_policy_node * |
|
to_stp_policy_node(struct config_item *item) |
|
{ |
|
return item ? |
|
container_of(to_config_group(item), struct stp_policy_node, |
|
group) : |
|
NULL; |
|
} |
|
|
|
void *to_pdrv_policy_node(struct config_item *item) |
|
{ |
|
struct stp_policy_node *node = to_stp_policy_node(item); |
|
|
|
return stp_policy_node_priv(node); |
|
} |
|
EXPORT_SYMBOL_GPL(to_pdrv_policy_node); |
|
|
|
static ssize_t |
|
stp_policy_node_masters_show(struct config_item *item, char *page) |
|
{ |
|
struct stp_policy_node *policy_node = to_stp_policy_node(item); |
|
ssize_t count; |
|
|
|
count = sprintf(page, "%u %u\n", policy_node->first_master, |
|
policy_node->last_master); |
|
|
|
return count; |
|
} |
|
|
|
static ssize_t |
|
stp_policy_node_masters_store(struct config_item *item, const char *page, |
|
size_t count) |
|
{ |
|
struct stp_policy_node *policy_node = to_stp_policy_node(item); |
|
unsigned int first, last; |
|
struct stm_device *stm; |
|
char *p = (char *)page; |
|
ssize_t ret = -ENODEV; |
|
|
|
if (sscanf(p, "%u %u", &first, &last) != 2) |
|
return -EINVAL; |
|
|
|
mutex_lock(&stp_policy_subsys.su_mutex); |
|
stm = policy_node->policy->stm; |
|
if (!stm) |
|
goto unlock; |
|
|
|
/* must be within [sw_start..sw_end], which is an inclusive range */ |
|
if (first > last || first < stm->data->sw_start || |
|
last > stm->data->sw_end) { |
|
ret = -ERANGE; |
|
goto unlock; |
|
} |
|
|
|
ret = count; |
|
policy_node->first_master = first; |
|
policy_node->last_master = last; |
|
|
|
unlock: |
|
mutex_unlock(&stp_policy_subsys.su_mutex); |
|
|
|
return ret; |
|
} |
|
|
|
static ssize_t |
|
stp_policy_node_channels_show(struct config_item *item, char *page) |
|
{ |
|
struct stp_policy_node *policy_node = to_stp_policy_node(item); |
|
ssize_t count; |
|
|
|
count = sprintf(page, "%u %u\n", policy_node->first_channel, |
|
policy_node->last_channel); |
|
|
|
return count; |
|
} |
|
|
|
static ssize_t |
|
stp_policy_node_channels_store(struct config_item *item, const char *page, |
|
size_t count) |
|
{ |
|
struct stp_policy_node *policy_node = to_stp_policy_node(item); |
|
unsigned int first, last; |
|
struct stm_device *stm; |
|
char *p = (char *)page; |
|
ssize_t ret = -ENODEV; |
|
|
|
if (sscanf(p, "%u %u", &first, &last) != 2) |
|
return -EINVAL; |
|
|
|
mutex_lock(&stp_policy_subsys.su_mutex); |
|
stm = policy_node->policy->stm; |
|
if (!stm) |
|
goto unlock; |
|
|
|
if (first > INT_MAX || last > INT_MAX || first > last || |
|
last >= stm->data->sw_nchannels) { |
|
ret = -ERANGE; |
|
goto unlock; |
|
} |
|
|
|
ret = count; |
|
policy_node->first_channel = first; |
|
policy_node->last_channel = last; |
|
|
|
unlock: |
|
mutex_unlock(&stp_policy_subsys.su_mutex); |
|
|
|
return ret; |
|
} |
|
|
|
static void stp_policy_node_release(struct config_item *item) |
|
{ |
|
struct stp_policy_node *node = to_stp_policy_node(item); |
|
|
|
kfree(node); |
|
} |
|
|
|
static struct configfs_item_operations stp_policy_node_item_ops = { |
|
.release = stp_policy_node_release, |
|
}; |
|
|
|
CONFIGFS_ATTR(stp_policy_node_, masters); |
|
CONFIGFS_ATTR(stp_policy_node_, channels); |
|
|
|
static struct configfs_attribute *stp_policy_node_attrs[] = { |
|
&stp_policy_node_attr_masters, |
|
&stp_policy_node_attr_channels, |
|
NULL, |
|
}; |
|
|
|
static const struct config_item_type stp_policy_type; |
|
static const struct config_item_type stp_policy_node_type; |
|
|
|
const struct config_item_type * |
|
get_policy_node_type(struct configfs_attribute **attrs) |
|
{ |
|
struct config_item_type *type; |
|
struct configfs_attribute **merged; |
|
|
|
type = kmemdup(&stp_policy_node_type, sizeof(stp_policy_node_type), |
|
GFP_KERNEL); |
|
if (!type) |
|
return NULL; |
|
|
|
merged = memcat_p(stp_policy_node_attrs, attrs); |
|
if (!merged) { |
|
kfree(type); |
|
return NULL; |
|
} |
|
|
|
type->ct_attrs = merged; |
|
|
|
return type; |
|
} |
|
|
|
static struct config_group * |
|
stp_policy_node_make(struct config_group *group, const char *name) |
|
{ |
|
const struct config_item_type *type = &stp_policy_node_type; |
|
struct stp_policy_node *policy_node, *parent_node; |
|
const struct stm_protocol_driver *pdrv; |
|
struct stp_policy *policy; |
|
|
|
if (group->cg_item.ci_type == &stp_policy_type) { |
|
policy = container_of(group, struct stp_policy, group); |
|
} else { |
|
parent_node = container_of(group, struct stp_policy_node, |
|
group); |
|
policy = parent_node->policy; |
|
} |
|
|
|
if (!policy->stm) |
|
return ERR_PTR(-ENODEV); |
|
|
|
pdrv = policy->stm->pdrv; |
|
policy_node = |
|
kzalloc(offsetof(struct stp_policy_node, priv[pdrv->priv_sz]), |
|
GFP_KERNEL); |
|
if (!policy_node) |
|
return ERR_PTR(-ENOMEM); |
|
|
|
if (pdrv->policy_node_init) |
|
pdrv->policy_node_init((void *)policy_node->priv); |
|
|
|
if (policy->stm->pdrv_node_type) |
|
type = policy->stm->pdrv_node_type; |
|
|
|
config_group_init_type_name(&policy_node->group, name, type); |
|
|
|
policy_node->policy = policy; |
|
|
|
/* default values for the attributes */ |
|
policy_node->first_master = policy->stm->data->sw_start; |
|
policy_node->last_master = policy->stm->data->sw_end; |
|
policy_node->first_channel = 0; |
|
policy_node->last_channel = policy->stm->data->sw_nchannels - 1; |
|
|
|
return &policy_node->group; |
|
} |
|
|
|
static void |
|
stp_policy_node_drop(struct config_group *group, struct config_item *item) |
|
{ |
|
config_item_put(item); |
|
} |
|
|
|
static struct configfs_group_operations stp_policy_node_group_ops = { |
|
.make_group = stp_policy_node_make, |
|
.drop_item = stp_policy_node_drop, |
|
}; |
|
|
|
static const struct config_item_type stp_policy_node_type = { |
|
.ct_item_ops = &stp_policy_node_item_ops, |
|
.ct_group_ops = &stp_policy_node_group_ops, |
|
.ct_attrs = stp_policy_node_attrs, |
|
.ct_owner = THIS_MODULE, |
|
}; |
|
|
|
/* |
|
* Root group: policies. |
|
*/ |
|
static ssize_t stp_policy_device_show(struct config_item *item, |
|
char *page) |
|
{ |
|
struct stp_policy *policy = to_stp_policy(item); |
|
ssize_t count; |
|
|
|
count = sprintf(page, "%s\n", |
|
(policy && policy->stm) ? |
|
policy->stm->data->name : |
|
"<none>"); |
|
|
|
return count; |
|
} |
|
|
|
CONFIGFS_ATTR_RO(stp_policy_, device); |
|
|
|
static ssize_t stp_policy_protocol_show(struct config_item *item, |
|
char *page) |
|
{ |
|
struct stp_policy *policy = to_stp_policy(item); |
|
ssize_t count; |
|
|
|
count = sprintf(page, "%s\n", |
|
(policy && policy->stm) ? |
|
policy->stm->pdrv->name : |
|
"<none>"); |
|
|
|
return count; |
|
} |
|
|
|
CONFIGFS_ATTR_RO(stp_policy_, protocol); |
|
|
|
static struct configfs_attribute *stp_policy_attrs[] = { |
|
&stp_policy_attr_device, |
|
&stp_policy_attr_protocol, |
|
NULL, |
|
}; |
|
|
|
void stp_policy_unbind(struct stp_policy *policy) |
|
{ |
|
struct stm_device *stm = policy->stm; |
|
|
|
/* |
|
* stp_policy_release() will not call here if the policy is already |
|
* unbound; other users should not either, as no link exists between |
|
* this policy and anything else in that case |
|
*/ |
|
if (WARN_ON_ONCE(!policy->stm)) |
|
return; |
|
|
|
lockdep_assert_held(&stm->policy_mutex); |
|
|
|
stm->policy = NULL; |
|
policy->stm = NULL; |
|
|
|
/* |
|
* Drop the reference on the protocol driver and lose the link. |
|
*/ |
|
stm_put_protocol(stm->pdrv); |
|
stm->pdrv = NULL; |
|
stm_put_device(stm); |
|
} |
|
|
|
static void stp_policy_release(struct config_item *item) |
|
{ |
|
struct stp_policy *policy = to_stp_policy(item); |
|
struct stm_device *stm = policy->stm; |
|
|
|
/* a policy *can* be unbound and still exist in configfs tree */ |
|
if (!stm) |
|
return; |
|
|
|
mutex_lock(&stm->policy_mutex); |
|
stp_policy_unbind(policy); |
|
mutex_unlock(&stm->policy_mutex); |
|
|
|
kfree(policy); |
|
} |
|
|
|
static struct configfs_item_operations stp_policy_item_ops = { |
|
.release = stp_policy_release, |
|
}; |
|
|
|
static struct configfs_group_operations stp_policy_group_ops = { |
|
.make_group = stp_policy_node_make, |
|
}; |
|
|
|
static const struct config_item_type stp_policy_type = { |
|
.ct_item_ops = &stp_policy_item_ops, |
|
.ct_group_ops = &stp_policy_group_ops, |
|
.ct_attrs = stp_policy_attrs, |
|
.ct_owner = THIS_MODULE, |
|
}; |
|
|
|
static struct config_group * |
|
stp_policy_make(struct config_group *group, const char *name) |
|
{ |
|
const struct config_item_type *pdrv_node_type; |
|
const struct stm_protocol_driver *pdrv; |
|
char *devname, *proto, *p; |
|
struct config_group *ret; |
|
struct stm_device *stm; |
|
int err; |
|
|
|
devname = kasprintf(GFP_KERNEL, "%s", name); |
|
if (!devname) |
|
return ERR_PTR(-ENOMEM); |
|
|
|
/* |
|
* node must look like <device_name>.<policy_name>, where |
|
* <device_name> is the name of an existing stm device; may |
|
* contain dots; |
|
* <policy_name> is an arbitrary string; may not contain dots |
|
* <device_name>:<protocol_name>.<policy_name> |
|
*/ |
|
p = strrchr(devname, '.'); |
|
if (!p) { |
|
kfree(devname); |
|
return ERR_PTR(-EINVAL); |
|
} |
|
|
|
*p = '\0'; |
|
|
|
/* |
|
* look for ":<protocol_name>": |
|
* + no protocol suffix: fall back to whatever is available; |
|
* + unknown protocol: fail the whole thing |
|
*/ |
|
proto = strrchr(devname, ':'); |
|
if (proto) |
|
*proto++ = '\0'; |
|
|
|
stm = stm_find_device(devname); |
|
if (!stm) { |
|
kfree(devname); |
|
return ERR_PTR(-ENODEV); |
|
} |
|
|
|
err = stm_lookup_protocol(proto, &pdrv, &pdrv_node_type); |
|
kfree(devname); |
|
|
|
if (err) { |
|
stm_put_device(stm); |
|
return ERR_PTR(-ENODEV); |
|
} |
|
|
|
mutex_lock(&stm->policy_mutex); |
|
if (stm->policy) { |
|
ret = ERR_PTR(-EBUSY); |
|
goto unlock_policy; |
|
} |
|
|
|
stm->policy = kzalloc(sizeof(*stm->policy), GFP_KERNEL); |
|
if (!stm->policy) { |
|
ret = ERR_PTR(-ENOMEM); |
|
goto unlock_policy; |
|
} |
|
|
|
config_group_init_type_name(&stm->policy->group, name, |
|
&stp_policy_type); |
|
|
|
stm->pdrv = pdrv; |
|
stm->pdrv_node_type = pdrv_node_type; |
|
stm->policy->stm = stm; |
|
ret = &stm->policy->group; |
|
|
|
unlock_policy: |
|
mutex_unlock(&stm->policy_mutex); |
|
|
|
if (IS_ERR(ret)) { |
|
/* |
|
* pdrv and stm->pdrv at this point can be quite different, |
|
* and only one of them needs to be 'put' |
|
*/ |
|
stm_put_protocol(pdrv); |
|
stm_put_device(stm); |
|
} |
|
|
|
return ret; |
|
} |
|
|
|
static struct configfs_group_operations stp_policy_root_group_ops = { |
|
.make_group = stp_policy_make, |
|
}; |
|
|
|
static const struct config_item_type stp_policy_root_type = { |
|
.ct_group_ops = &stp_policy_root_group_ops, |
|
.ct_owner = THIS_MODULE, |
|
}; |
|
|
|
static struct configfs_subsystem stp_policy_subsys = { |
|
.su_group = { |
|
.cg_item = { |
|
.ci_namebuf = "stp-policy", |
|
.ci_type = &stp_policy_root_type, |
|
}, |
|
}, |
|
}; |
|
|
|
/* |
|
* Lock the policy mutex from the outside |
|
*/ |
|
static struct stp_policy_node * |
|
__stp_policy_node_lookup(struct stp_policy *policy, char *s) |
|
{ |
|
struct stp_policy_node *policy_node, *ret = NULL; |
|
struct list_head *head = &policy->group.cg_children; |
|
struct config_item *item; |
|
char *start, *end = s; |
|
|
|
if (list_empty(head)) |
|
return NULL; |
|
|
|
next: |
|
for (;;) { |
|
start = strsep(&end, "/"); |
|
if (!start) |
|
break; |
|
|
|
if (!*start) |
|
continue; |
|
|
|
list_for_each_entry(item, head, ci_entry) { |
|
policy_node = to_stp_policy_node(item); |
|
|
|
if (!strcmp(start, |
|
policy_node->group.cg_item.ci_name)) { |
|
ret = policy_node; |
|
|
|
if (!end) |
|
goto out; |
|
|
|
head = &policy_node->group.cg_children; |
|
goto next; |
|
} |
|
} |
|
break; |
|
} |
|
|
|
out: |
|
return ret; |
|
} |
|
|
|
|
|
struct stp_policy_node * |
|
stp_policy_node_lookup(struct stm_device *stm, char *s) |
|
{ |
|
struct stp_policy_node *policy_node = NULL; |
|
|
|
mutex_lock(&stp_policy_subsys.su_mutex); |
|
|
|
mutex_lock(&stm->policy_mutex); |
|
if (stm->policy) |
|
policy_node = __stp_policy_node_lookup(stm->policy, s); |
|
mutex_unlock(&stm->policy_mutex); |
|
|
|
if (policy_node) |
|
config_item_get(&policy_node->group.cg_item); |
|
else |
|
mutex_unlock(&stp_policy_subsys.su_mutex); |
|
|
|
return policy_node; |
|
} |
|
|
|
void stp_policy_node_put(struct stp_policy_node *policy_node) |
|
{ |
|
lockdep_assert_held(&stp_policy_subsys.su_mutex); |
|
|
|
mutex_unlock(&stp_policy_subsys.su_mutex); |
|
config_item_put(&policy_node->group.cg_item); |
|
} |
|
|
|
int __init stp_configfs_init(void) |
|
{ |
|
config_group_init(&stp_policy_subsys.su_group); |
|
mutex_init(&stp_policy_subsys.su_mutex); |
|
return configfs_register_subsystem(&stp_policy_subsys); |
|
} |
|
|
|
void __exit stp_configfs_exit(void) |
|
{ |
|
configfs_unregister_subsystem(&stp_policy_subsys); |
|
}
|
|
|