diff options
Diffstat (limited to 'drivers/edp/sysedp_sysfs.c')
-rw-r--r-- | drivers/edp/sysedp_sysfs.c | 349 |
1 files changed, 349 insertions, 0 deletions
diff --git a/drivers/edp/sysedp_sysfs.c b/drivers/edp/sysedp_sysfs.c new file mode 100644 index 000000000000..98669507820a --- /dev/null +++ b/drivers/edp/sysedp_sysfs.c @@ -0,0 +1,349 @@ +/* + * Copyright (c) 2013, NVIDIA CORPORATION. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include <linux/kernel.h> +#include <linux/sysfs.h> +#include <linux/mutex.h> +#include <linux/slab.h> +#include <linux/sysedp.h> +#include <linux/err.h> +#include <trace/events/sysedp.h> +#include "sysedp_internal.h" + +static struct kobject sysedp_kobj; + +struct sysedp_consumer_attribute { + struct attribute attr; + ssize_t (*show)(struct sysedp_consumer *c, char *buf); + ssize_t (*store)(struct sysedp_consumer *c, + const char *buf, size_t count); +}; + + +static ssize_t states_show(struct sysedp_consumer *c, char *s) +{ + unsigned int i; + int cnt = 0; + const int sz = sizeof(*c->states) * 3 + 2; + + for (i = 0; i < c->num_states && (cnt + sz) < PAGE_SIZE; i++) + cnt += sprintf(s + cnt, "%s%u", i ? " " : "", c->states[i]); + + cnt += sprintf(s + cnt, "\n"); + return cnt; +} + +static ssize_t current_show(struct sysedp_consumer *c, char *s) +{ + return sprintf(s, "%u\n", c->states[c->state]); +} + +static ssize_t state_show(struct sysedp_consumer *c, char *s) +{ + return sprintf(s, "%u\n", c->state); +} + +static ssize_t state_store(struct sysedp_consumer *c, const char *s, + size_t count) +{ + unsigned int new_state; + + if (sscanf(s, "%u", &new_state) != 1) + return -EINVAL; + + sysedp_set_state(c, new_state); + + return count; +} + +static struct sysedp_consumer_attribute attr_current = { + .attr = { .name = "current", .mode = 0444 }, + .show = current_show +}; +static struct sysedp_consumer_attribute attr_state = __ATTR(state, 0660, + state_show, + state_store); +static struct sysedp_consumer_attribute attr_states = __ATTR_RO(states); + +static struct attribute *consumer_attrs[] = { + &attr_current.attr, + &attr_state.attr, + &attr_states.attr, + NULL +}; + +static struct sysedp_consumer *to_consumer(struct kobject *kobj) +{ + return container_of(kobj, struct sysedp_consumer, kobj); +} + +static ssize_t consumer_attr_show(struct kobject *kobj, + struct attribute *attr, char *buf) +{ + ssize_t r = -EINVAL; + struct sysedp_consumer *c; + struct sysedp_consumer_attribute *cattr; + + c = to_consumer(kobj); + cattr = container_of(attr, struct sysedp_consumer_attribute, attr); + if (c && cattr) { + if (cattr->show) + r = cattr->show(c, buf); + } + + return r; +} + +static ssize_t consumer_attr_store(struct kobject *kobj, + struct attribute *attr, const char *buf, size_t count) +{ + ssize_t r = -EINVAL; + struct sysedp_consumer *c; + struct sysedp_consumer_attribute *cattr; + + c = to_consumer(kobj); + cattr = container_of(attr, struct sysedp_consumer_attribute, attr); + if (c && cattr) { + if (cattr->store) + r = cattr->store(c, buf, count); + } + + return r; +} + +static const struct sysfs_ops consumer_sysfs_ops = { + .show = consumer_attr_show, + .store = consumer_attr_store +}; + +static struct kobj_type ktype_consumer = { + .sysfs_ops = &consumer_sysfs_ops, + .default_attrs = consumer_attrs +}; + +int sysedp_consumer_add_kobject(struct sysedp_consumer *consumer) +{ + struct kobject *parent = &sysedp_kobj; + + if (kobject_init_and_add(&consumer->kobj, &ktype_consumer, parent, + consumer->name)) { + pr_err("%s: failed to init & add sysfs consumer entry\n", + consumer->name); + return -EINVAL; + } + + kobject_uevent(&consumer->kobj, KOBJ_ADD); + return 0; +} + +void sysedp_consumer_remove_kobject(struct sysedp_consumer *consumer) +{ + kobject_put(&consumer->kobj); +} + +struct sysedp_attribute { + struct attribute attr; + ssize_t (*show)(char *buf); + ssize_t (*store)(const char *buf, size_t count); +}; + +static unsigned int *get_tokenized_data(const char *buf, + unsigned int *num_tokens) +{ + const char *cp; + int i; + unsigned int ntokens = 1; + unsigned int *tokenized_data; + int err = -EINVAL; + + cp = buf; + while ((cp = strpbrk(cp + 1, ","))) + ntokens++; + + tokenized_data = kmalloc(ntokens * sizeof(unsigned int), + GFP_KERNEL); + if (!tokenized_data) { + err = -ENOMEM; + goto err; + } + + cp = buf; + i = 0; + while (i < ntokens) { + if (sscanf(cp, "%u", &tokenized_data[i++]) != 1) + goto err_kfree; + + cp = strpbrk(cp, ","); + if (!cp) + break; + cp++; + } + + if (i != ntokens) + goto err_kfree; + + *num_tokens = ntokens; + return tokenized_data; + +err_kfree: + kfree(tokenized_data); +err: + return ERR_PTR(err); +} + + +static ssize_t consumer_register_store(const char *s, size_t count) +{ + size_t name_len; + unsigned int *states; + unsigned int num_states; + struct sysedp_consumer *consumer; + + name_len = strcspn(s, " \n"); + if (name_len > SYSEDP_NAME_LEN-1) + return -EINVAL; + + states = get_tokenized_data(s + name_len, &num_states); + if (IS_ERR_OR_NULL(states)) + return -EINVAL; + + consumer = kzalloc(sizeof(*consumer), GFP_KERNEL); + if (!consumer) { + kfree(states); + return -ENOMEM; + } + + memcpy(consumer->name, s, name_len); + consumer->name[name_len] = 0; + consumer->states = states; + consumer->num_states = num_states; + consumer->removable = 1; + + if (sysedp_register_consumer(consumer)) { + kfree(states); + kfree(consumer); + return -EINVAL; + } + + return count; +} + +static ssize_t consumer_unregister_store(const char *s, size_t count) +{ + char name[SYSEDP_NAME_LEN]; + size_t n; + struct sysedp_consumer *consumer; + + n = count > SYSEDP_NAME_LEN ? SYSEDP_NAME_LEN : count; + strncpy(name, s, n); + name[n-1] = 0; + consumer = sysedp_get_consumer(strim(name)); + + if (!consumer) + return -EINVAL; + + if (!consumer->removable) + return -EINVAL; + + kfree(consumer->states); + sysedp_free_consumer(consumer); + + return count; +} + +static ssize_t margin_show(char *s) +{ + return sprintf(s, "%d\n", margin); +} + +static ssize_t margin_store(const char *s, size_t count) +{ + int val; + if (sscanf(s, "%d", &val) != 1) + return -EINVAL; + + mutex_lock(&sysedp_lock); + margin = val; + _sysedp_refresh(); + mutex_unlock(&sysedp_lock); + + return count; +} + +static ssize_t avail_budget_show(char *s) +{ + return sprintf(s, "%u\n", avail_budget); +} + +static struct sysedp_attribute attr_consumer_register = + __ATTR(consumer_register, 0220, NULL, consumer_register_store); +static struct sysedp_attribute attr_consumer_unregister = + __ATTR(consumer_unregister, 0220, NULL, consumer_unregister_store); +static struct sysedp_attribute attr_margin = + __ATTR(margin, 0660, margin_show, margin_store); +static struct sysedp_attribute attr_avail_budget = __ATTR_RO(avail_budget); + +static struct attribute *sysedp_attrs[] = { + &attr_consumer_register.attr, + &attr_consumer_unregister.attr, + &attr_margin.attr, + &attr_avail_budget.attr, + NULL +}; + +static ssize_t sysedp_attr_show(struct kobject *kobj, + struct attribute *_attr, char *buf) +{ + ssize_t r = -EINVAL; + struct sysedp_attribute *attr; + attr = container_of(_attr, struct sysedp_attribute, attr); + if (attr && attr->show) + r = attr->show(buf); + return r; +} + +static ssize_t sysedp_attr_store(struct kobject *kobj, struct attribute *_attr, + const char *buf, size_t count) +{ + ssize_t r = -EINVAL; + struct sysedp_attribute *attr; + attr = container_of(_attr, struct sysedp_attribute, attr); + if (attr && attr->store) + r = attr->store(buf, count); + return r; +} + +static const struct sysfs_ops sysedp_sysfs_ops = { + .show = sysedp_attr_show, + .store = sysedp_attr_store +}; + +static struct kobj_type ktype_sysedp = { + .sysfs_ops = &sysedp_sysfs_ops, + .default_attrs = sysedp_attrs +}; + +int sysedp_init_sysfs(void) +{ + struct kobject *parent = NULL; + +#ifdef CONFIG_PM + parent = power_kobj; +#endif + + return kobject_init_and_add(&sysedp_kobj, &ktype_sysedp, + parent, "sysedp"); +} |