| /* |
| * Copyright (c) 2012-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/module.h> |
| #include <linux/edp.h> |
| #include <linux/slab.h> |
| #include "edp_internal.h" |
| |
| static struct kobject edp_kobj; |
| |
| struct manager_entry { |
| struct edp_manager *manager; |
| struct kobject kobj; |
| }; |
| |
| struct manager_attr { |
| struct attribute attr; |
| ssize_t (*show)(struct edp_manager *, char *); |
| ssize_t (*store)(struct edp_manager *, const char *, size_t); |
| }; |
| |
| static ssize_t cap_show(struct edp_manager *m, char *s) |
| { |
| return scnprintf(s, PAGE_SIZE, "%u\n", m->max); |
| } |
| |
| static ssize_t remaining_show(struct edp_manager *m, char *s) |
| { |
| return scnprintf(s, PAGE_SIZE, "%u\n", m->remaining); |
| } |
| |
| static ssize_t manager_governor_show(struct edp_manager *m, char *s) |
| { |
| return scnprintf(s, PAGE_SIZE, "%s\n", m->gov ? m->gov->name : ""); |
| } |
| |
| static ssize_t manager_governor_store(struct edp_manager *m, const char *s, |
| size_t count) |
| { |
| char name[EDP_NAME_LEN]; |
| struct edp_governor *gov; |
| |
| if (!count || count >= sizeof(name)) |
| return -EINVAL; |
| |
| memcpy(name, s, count); |
| name[count] = 0; |
| strim(name); |
| gov = edp_find_governor_unlocked(name); |
| if (!gov) |
| return -EINVAL; |
| |
| return edp_set_governor_unlocked(m, gov) ?: count; |
| } |
| |
| struct manager_attr attr_cap = __ATTR_RO(cap); |
| struct manager_attr attr_remaining = __ATTR_RO(remaining); |
| struct manager_attr attr_mgr_gov = __ATTR(governor, 0644, |
| manager_governor_show, manager_governor_store); |
| |
| static struct attribute *manager_attrs[] = { |
| &attr_cap.attr, |
| &attr_remaining.attr, |
| &attr_mgr_gov.attr, |
| NULL |
| }; |
| |
| static struct edp_manager *to_manager(struct kobject *kobj) |
| { |
| struct manager_entry *me = container_of(kobj, struct manager_entry, |
| kobj); |
| return me ? me->manager : NULL; |
| } |
| |
| static ssize_t manager_state_show(struct kobject *kobj, |
| struct attribute *attr, char *buf) |
| { |
| ssize_t r; |
| struct edp_manager *m; |
| struct manager_attr *mattr; |
| |
| mutex_lock(&edp_lock); |
| m = to_manager(kobj); |
| mattr = container_of(attr, struct manager_attr, attr); |
| r = m && mattr ? mattr->show(m, buf) : -EINVAL; |
| mutex_unlock(&edp_lock); |
| |
| return r; |
| } |
| |
| static ssize_t manager_state_store(struct kobject *kobj, |
| struct attribute *attr, const char *buf, size_t count) |
| { |
| ssize_t r; |
| struct edp_manager *m; |
| struct manager_attr *mattr; |
| |
| mutex_lock(&edp_lock); |
| m = to_manager(kobj); |
| mattr = container_of(attr, struct manager_attr, attr); |
| r = m && mattr ? mattr->store(m, buf, count) : -EINVAL; |
| mutex_unlock(&edp_lock); |
| |
| return r; |
| } |
| |
| static const struct sysfs_ops manager_sysfs_ops = { |
| .show = manager_state_show, |
| .store = manager_state_store |
| }; |
| |
| static struct kobj_type ktype_manager = { |
| .sysfs_ops = &manager_sysfs_ops, |
| .default_attrs = manager_attrs |
| }; |
| |
| void edp_manager_add_kobject(struct edp_manager *mgr) |
| { |
| struct manager_entry *me; |
| |
| me = kzalloc(sizeof(*me), GFP_KERNEL); |
| if (!me) { |
| pr_err("%s: failed to alloc sysfs manager entry\n", |
| mgr->name); |
| return; |
| } |
| |
| if (kobject_init_and_add(&me->kobj, &ktype_manager, &edp_kobj, |
| mgr->name)) { |
| pr_err("%s: failed to init & add sysfs manager entry\n", |
| mgr->name); |
| kfree(me); |
| return; |
| } |
| |
| me->manager = mgr; |
| mgr->kobj = &me->kobj; |
| kobject_uevent(&me->kobj, KOBJ_ADD); |
| return; |
| } |
| |
| void edp_manager_remove_kobject(struct edp_manager *mgr) |
| { |
| struct manager_entry *me; |
| |
| if (!mgr->kobj) |
| return; |
| |
| me = container_of(mgr->kobj, struct manager_entry, kobj); |
| mgr->kobj = NULL; |
| kobject_put(&me->kobj); |
| kfree(me); |
| } |
| |
| struct client_entry { |
| struct edp_client *client; |
| struct kobject kobj; |
| }; |
| |
| struct client_attr { |
| struct attribute attr; |
| ssize_t (*show)(struct edp_client *, char *); |
| ssize_t (*store)(struct edp_client *, const char *); |
| }; |
| |
| static ssize_t states_show(struct edp_client *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 num_states_show(struct edp_client *c, char *s) |
| { |
| return scnprintf(s, PAGE_SIZE, "%u\n", c->num_states); |
| } |
| |
| static ssize_t e0_show(struct edp_client *c, char *s) |
| { |
| return scnprintf(s, PAGE_SIZE, "%u\n", c->states[c->e0_index]); |
| } |
| |
| static ssize_t max_borrowers_show(struct edp_client *c, char *s) |
| { |
| return scnprintf(s, PAGE_SIZE, "%u\n", c->max_borrowers); |
| } |
| |
| static ssize_t priority_show(struct edp_client *c, char *s) |
| { |
| return scnprintf(s, PAGE_SIZE, "%d\n", c->priority); |
| } |
| |
| static ssize_t request_show(struct edp_client *c, char *s) |
| { |
| return scnprintf(s, PAGE_SIZE, "%u\n", req_level(c)); |
| } |
| |
| /* Allow only updates that are guaranteed to succeed */ |
| static ssize_t request_store(struct edp_client *c, const char *s) |
| { |
| unsigned int id; |
| |
| if (sscanf(s, "%u", &id) != 1) |
| return -EINVAL; |
| |
| if (id >= c->num_states) |
| return -EINVAL; |
| |
| if (id < c->e0_index && id < req_index(c)) |
| return -EPERM; |
| |
| return edp_update_client_request_unlocked(c, id, NULL); |
| } |
| |
| static ssize_t current_show(struct edp_client *c, char *s) |
| { |
| return scnprintf(s, PAGE_SIZE, "%u\n", cur_level(c)); |
| } |
| |
| static ssize_t threshold_show(struct edp_client *c, char *s) |
| { |
| return scnprintf(s, PAGE_SIZE, "%u\n", c->ithreshold); |
| } |
| |
| static ssize_t threshold_store(struct edp_client *c, const char *s) |
| { |
| unsigned int tv; |
| |
| if (sscanf(s, "%u", &tv) != 1) |
| return -EINVAL; |
| |
| return edp_update_loan_threshold_unlocked(c, tv); |
| } |
| |
| static ssize_t borrowers_show(struct edp_client *c, char *s) |
| { |
| return scnprintf(s, PAGE_SIZE, "%u\n", c->num_borrowers); |
| } |
| |
| static ssize_t loans_show(struct edp_client *c, char *s) |
| { |
| return scnprintf(s, PAGE_SIZE, "%u\n", c->num_loans); |
| } |
| |
| struct client_attr attr_states = __ATTR_RO(states); |
| struct client_attr attr_num_states = __ATTR_RO(num_states); |
| struct client_attr attr_e0 = __ATTR_RO(e0); |
| struct client_attr attr_max_borrowers = __ATTR_RO(max_borrowers); |
| struct client_attr attr_priority = __ATTR_RO(priority); |
| struct client_attr attr_request = __ATTR(request, 0644, request_show, |
| request_store); |
| struct client_attr attr_threshold = __ATTR(threshold, 0644, threshold_show, |
| threshold_store); |
| struct client_attr attr_borrowers = __ATTR_RO(borrowers); |
| struct client_attr attr_loans = __ATTR_RO(loans); |
| struct client_attr attr_current = { |
| .attr = { .name = "current", .mode = 0444 }, |
| .show = current_show |
| }; |
| |
| static struct attribute *client_attrs[] = { |
| &attr_states.attr, |
| &attr_num_states.attr, |
| &attr_e0.attr, |
| &attr_max_borrowers.attr, |
| &attr_priority.attr, |
| &attr_request.attr, |
| &attr_current.attr, |
| &attr_threshold.attr, |
| &attr_borrowers.attr, |
| &attr_loans.attr, |
| NULL |
| }; |
| |
| static struct edp_client *to_client(struct kobject *kobj) |
| { |
| struct client_entry *ce = container_of(kobj, struct client_entry, |
| kobj); |
| return ce ? ce->client : NULL; |
| } |
| |
| static ssize_t client_state_show(struct kobject *kobj, |
| struct attribute *attr, char *buf) |
| { |
| ssize_t r; |
| struct edp_client *c; |
| struct client_attr *cattr; |
| |
| mutex_lock(&edp_lock); |
| c = to_client(kobj); |
| cattr = container_of(attr, struct client_attr, attr); |
| r = c && cattr ? cattr->show(c, buf) : -EINVAL; |
| mutex_unlock(&edp_lock); |
| |
| return r; |
| } |
| |
| static ssize_t client_state_store(struct kobject *kobj, |
| struct attribute *attr, const char *buf, size_t count) |
| { |
| ssize_t r = -EINVAL; |
| struct edp_client *c; |
| struct client_attr *cattr; |
| |
| mutex_lock(&edp_lock); |
| |
| c = to_client(kobj); |
| cattr = container_of(attr, struct client_attr, attr); |
| if (c && cattr) { |
| if (cattr->store) |
| r = cattr->store(c, buf); |
| } |
| |
| mutex_unlock(&edp_lock); |
| |
| return r ?: count; |
| } |
| |
| static const struct sysfs_ops client_sysfs_ops = { |
| .show = client_state_show, |
| .store = client_state_store |
| }; |
| |
| static struct kobj_type ktype_client = { |
| .sysfs_ops = &client_sysfs_ops, |
| .default_attrs = client_attrs |
| }; |
| |
| void edp_client_add_kobject(struct edp_client *client) |
| { |
| struct client_entry *ce; |
| struct kobject *parent = client->manager->kobj; |
| |
| if (!parent) |
| return; |
| |
| ce = kzalloc(sizeof(*ce), GFP_KERNEL); |
| if (!ce) { |
| pr_err("%s: failed to alloc sysfs client entry\n", |
| client->name); |
| return; |
| } |
| |
| if (kobject_init_and_add(&ce->kobj, &ktype_client, parent, |
| client->name)) { |
| pr_err("%s: failed to init & add sysfs client entry\n", |
| client->name); |
| kfree(ce); |
| return; |
| } |
| |
| ce->client = client; |
| client->kobj = &ce->kobj; |
| kobject_uevent(&ce->kobj, KOBJ_ADD); |
| return; |
| } |
| |
| void edp_client_remove_kobject(struct edp_client *client) |
| { |
| struct client_entry *ce; |
| |
| if (!client->kobj) |
| return; |
| |
| ce = container_of(client->kobj, struct client_entry, kobj); |
| client->kobj = NULL; |
| kobject_put(&ce->kobj); |
| kfree(ce); |
| } |
| |
| static ssize_t governors_show(struct kobject *kobj, struct attribute *attr, |
| char *s) |
| { |
| struct edp_governor *g; |
| int cnt = 0; |
| |
| mutex_lock(&edp_lock); |
| |
| list_for_each_entry(g, &edp_governors, link) { |
| if (cnt + EDP_NAME_LEN + 2 >= PAGE_SIZE) |
| break; |
| cnt += sprintf(s + cnt, "%s%s", cnt ? " " : "", g->name); |
| } |
| |
| cnt += sprintf(s + cnt, "\n"); |
| |
| mutex_unlock(&edp_lock); |
| |
| return cnt; |
| } |
| |
| static const struct sysfs_ops edp_sysfs_ops = { |
| .show = governors_show |
| }; |
| |
| static struct attribute attr_governors = { |
| .name = "governors", |
| .mode = 0444 |
| }; |
| |
| static struct attribute *edp_attrs[] = { |
| &attr_governors, |
| NULL |
| }; |
| |
| static struct kobj_type ktype_edp = { |
| .sysfs_ops = &edp_sysfs_ops, |
| .default_attrs = edp_attrs |
| }; |
| |
| static int __init edp_sysfs_init(void) |
| { |
| struct kobject *parent = NULL; |
| |
| #ifdef CONFIG_PM |
| parent = power_kobj; |
| #endif |
| |
| return kobject_init_and_add(&edp_kobj, &ktype_edp, parent, "edp"); |
| } |
| postcore_initcall(edp_sysfs_init); |