blob: 2464bcce74d714cf9a8d94d79031f67da688acd2 [file] [log] [blame]
/*
* 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/module.h>
#include <linux/edp.h>
#include <linux/debugfs.h>
#include "edp_internal.h"
struct dentry *edp_debugfs_dir;
/*
* Reducing the cap is tricky - we might require throttling of other
* clients (therefore, involving the governor). So we will fool the
* framework by using a dummy client that has a single E-state (E0)
* equalling the reduction.
*/
static int reduce_cap(struct edp_manager *m, unsigned int new_max)
{
int r = 0;
unsigned int delta = m->max - new_max;
unsigned int remain;
struct edp_client c = {
.name = ".debug",
.states = &delta,
.num_states = 1,
.e0_index = 0,
.max_borrowers = 0,
.priority = EDP_MIN_PRIO
};
r = register_client(m, &c);
if (r)
return r;
r = edp_update_client_request_unlocked(&c, 0, NULL);
if (r)
return r;
remain = m->remaining;
r = unregister_client(&c);
if (r)
return r;
m->remaining = remain;
m->max = new_max;
return 0;
}
static int __manager_cap_set(struct edp_manager *m, unsigned int new_max)
{
if (new_max >= m->max) {
m->remaining += new_max - m->max;
m->max = new_max;
schedule_promotion(m);
return 0;
}
return reduce_cap(m, new_max);
}
static int manager_status_show(struct seq_file *file, void *data)
{
struct edp_manager *m;
struct edp_client *c;
if (!file->private)
return -ENODEV;
m = file->private;
mutex_lock(&edp_lock);
seq_printf(file, "cap : %u\n", m->max);
seq_printf(file, "sum(E0) : %u\n", e0_current_sum(m));
seq_printf(file, "remaining: %u\n", m->remaining);
seq_printf(file, "------------------------------------------\n");
seq_printf(file, "%-16s %3s %5s %7s %7s\n",
"client", "pri", "E0", "request", "current");
seq_printf(file, "------------------------------------------\n");
list_for_each_entry(c, &m->clients, link)
seq_printf(file, "%-16s %3d %5u %7u %7u\n", c->name,
c->priority, e0_level(c), req_level(c),
cur_level(c));
mutex_unlock(&edp_lock);
return 0;
}
static int manager_status_open(struct inode *inode, struct file *file)
{
return single_open(file, manager_status_show, inode->i_private);
}
static const struct file_operations manager_status_fops = {
.open = manager_status_open,
.read = seq_read,
};
static int manager_cap_set(void *data, u64 val)
{
struct edp_manager *m = data;
int r;
mutex_lock(&edp_lock);
r = __manager_cap_set(m, val);
mutex_unlock(&edp_lock);
return r;
}
static int manager_cap_get(void *data, u64 *val)
{
struct edp_manager *m = data;
mutex_lock(&edp_lock);
*val = m->max;
mutex_unlock(&edp_lock);
return 0;
}
DEFINE_SIMPLE_ATTRIBUTE(manager_cap_fops, manager_cap_get,
manager_cap_set, "%lld\n");
void manager_add_dentry(struct edp_manager *m)
{
struct dentry *d;
if (!edp_debugfs_dir)
return;
d = debugfs_create_dir(m->name, edp_debugfs_dir);
if (IS_ERR_OR_NULL(d))
return;
m->dentry = d;
d = debugfs_create_file("status", S_IRUGO, m->dentry, m,
&manager_status_fops);
WARN_ON(IS_ERR_OR_NULL(d));
d = debugfs_create_file("cap", S_IRUGO | S_IWUSR, m->dentry, m,
&manager_cap_fops);
WARN_ON(IS_ERR_OR_NULL(d));
}
void manager_remove_dentry(struct edp_manager *m)
{
debugfs_remove_recursive(m->dentry);
m->dentry = NULL;
}
static int __client_current_set(struct edp_client *c, unsigned int new)
{
struct edp_manager *m;
unsigned int nl;
unsigned int cl;
if (new >= c->num_states)
return -EINVAL;
nl = c->states[new];
cl = cur_level(c);
m = c->manager;
if (nl > cl && nl - cl > m->remaining)
return -EBUSY;
c->cur = c->states + new;
c->req = c->states + new;
if (nl < cl) {
m->remaining += cl - nl;
if (c->throttle)
c->throttle(new, c->private_data);
schedule_promotion(m);
} else if (nl > cl) {
m->remaining -= nl - cl;
if (c->notify_promotion)
c->notify_promotion(new, c->private_data);
}
return 0;
}
static int client_current_set(void *data, u64 val)
{
struct edp_client *c = data;
int r;
mutex_lock(&edp_lock);
r = __client_current_set(c, val);
mutex_unlock(&edp_lock);
return r;
}
static int client_current_get(void *data, u64 *val)
{
struct edp_client *c = data;
mutex_lock(&edp_lock);
*val = cur_level(c);
mutex_unlock(&edp_lock);
return 0;
}
DEFINE_SIMPLE_ATTRIBUTE(client_current_fops, client_current_get,
client_current_set, "%lld\n");
void client_add_dentry(struct edp_client *c)
{
struct dentry *d;
if (!c->manager->dentry)
return;
d = debugfs_create_dir(c->name, c->manager->dentry);
if (IS_ERR_OR_NULL(d)) {
WARN_ON(1);
return;
}
c->dentry = d;
d = debugfs_create_file("current", S_IRUGO | S_IWUSR, c->dentry,
c, &client_current_fops);
WARN_ON(IS_ERR_OR_NULL(d));
}
void client_remove_dentry(struct edp_client *c)
{
debugfs_remove_recursive(c->dentry);
c->dentry = NULL;
}
static void dbg_update_request(struct edp_client *c, const unsigned int *r) {}
static void dbg_update_loans(struct edp_client *c) {}
static void dbg_promote(struct edp_manager *mgr) {}
static struct edp_governor dbg_governor = {
.name = "debug",
.owner = THIS_MODULE,
.update_request = dbg_update_request,
.update_loans = dbg_update_loans,
.promote = dbg_promote
};
static int __init debug_init(void)
{
struct dentry *d;
d = debugfs_create_dir("edp", NULL);
if (IS_ERR_OR_NULL(d)) {
WARN_ON(1);
return -EFAULT;
}
edp_debugfs_dir = d;
return edp_register_governor(&dbg_governor);
}
postcore_initcall(debug_init);