diff options
Diffstat (limited to 'drivers/hwtracing/stm/policy.c')
-rw-r--r-- | drivers/hwtracing/stm/policy.c | 570 |
1 files changed, 570 insertions, 0 deletions
diff --git a/drivers/hwtracing/stm/policy.c b/drivers/hwtracing/stm/policy.c new file mode 100644 index 000000000..42103c3a1 --- /dev/null +++ b/drivers/hwtracing/stm/policy.c @@ -0,0 +1,570 @@ +// 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); +} |