/*
* 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 .
*/
#include
#include
#include
#include
#include
#include
#include
#include "edp_internal.h"
DEFINE_MUTEX(edp_lock);
static LIST_HEAD(edp_managers);
LIST_HEAD(edp_governors);
static struct edp_manager *find_manager(const char *name)
{
struct edp_manager *mgr;
if (!name)
return NULL;
list_for_each_entry(mgr, &edp_managers, link)
if (!strcmp(mgr->name, name))
return mgr;
return NULL;
}
static void promote(struct work_struct *work)
{
unsigned int prev_denied;
struct edp_manager *m = container_of(work, struct edp_manager, work);
mutex_lock(&edp_lock);
if (m->num_denied && m->remaining && m->gov) {
prev_denied = m->num_denied;
m->gov->promote(m);
if (prev_denied != m->num_denied)
sysfs_notify(m->kobj, NULL, "denied");
}
mutex_unlock(&edp_lock);
}
void schedule_promotion(struct edp_manager *mgr)
{
if (mgr->remaining && mgr->num_denied && mgr->gov->promote)
schedule_work(&mgr->work);
}
int edp_register_manager(struct edp_manager *mgr)
{
int r = -EEXIST;
if (!mgr)
return -EINVAL;
if (!mgr->max)
return -EINVAL;
mutex_lock(&edp_lock);
if (!find_manager(mgr->name)) {
list_add_tail(&mgr->link, &edp_managers);
mgr->registered = true;
mgr->remaining = mgr->max;
mgr->gov = NULL;
mgr->gov_data = NULL;
INIT_LIST_HEAD(&mgr->clients);
INIT_WORK(&mgr->work, promote);
mgr->kobj = NULL;
edp_manager_add_kobject(mgr);
manager_add_dentry(mgr);
r = 0;
}
mutex_unlock(&edp_lock);
return r;
}
EXPORT_SYMBOL(edp_register_manager);
int edp_set_governor_unlocked(struct edp_manager *mgr,
struct edp_governor *gov)
{
int r = 0;
if (mgr ? !mgr->registered : 1)
return -EINVAL;
if (mgr->gov) {
if (mgr->gov->stop)
mgr->gov->stop(mgr);
mgr->gov->refcnt--;
module_put(mgr->gov->owner);
mgr->gov = NULL;
}
if (gov) {
if (!gov->refcnt)
return -EINVAL;
if (!try_module_get(gov->owner))
return -EINVAL;
if (gov->start)
r = gov->start(mgr);
if (r) {
module_put(gov->owner);
WARN_ON(1);
return r;
}
gov->refcnt++;
mgr->gov = gov;
}
return 0;
}
int edp_unregister_manager(struct edp_manager *mgr)
{
if (!mgr)
return -EINVAL;
mutex_lock(&edp_lock);
if (!mgr->registered) {
mutex_unlock(&edp_lock);
return -ENODEV;
}
if (!list_empty(&mgr->clients)) {
mutex_unlock(&edp_lock);
return -EBUSY;
}
manager_remove_dentry(mgr);
edp_manager_remove_kobject(mgr);
edp_set_governor_unlocked(mgr, NULL);
list_del(&mgr->link);
mgr->registered = false;
mutex_unlock(&edp_lock);
cancel_work_sync(&mgr->work);
return 0;
}
EXPORT_SYMBOL(edp_unregister_manager);
struct edp_manager *edp_get_manager(const char *name)
{
struct edp_manager *mgr;
mutex_lock(&edp_lock);
mgr = find_manager(name);
mutex_unlock(&edp_lock);
return mgr;
}
EXPORT_SYMBOL(edp_get_manager);
static struct edp_client *find_client(struct edp_manager *mgr,
const char *name)
{
struct edp_client *p;
if (!name)
return NULL;
list_for_each_entry(p, &mgr->clients, link)
if (!strcmp(p->name, name))
return p;
return NULL;
}
unsigned int e0_current_sum(struct edp_manager *mgr)
{
struct edp_client *p;
unsigned int sum = 0;
list_for_each_entry(p, &mgr->clients, link)
sum += p->states[p->e0_index];
return sum;
}
static bool states_ok(struct edp_client *client)
{
int i;
if (!client->states || !client->num_states ||
client->e0_index >= client->num_states)
return false;
/* state array should be sorted in descending order */
for (i = 1; i < client->num_states; i++)
if (client->states[i] > client->states[i - 1])
return false;
return client->states[0] ? true : false;
}
/* Keep the list sorted on priority */
static void add_client(struct edp_client *new, struct list_head *head)
{
struct edp_client *p;
list_for_each_entry(p, head, link) {
if (p->priority > new->priority) {
list_add_tail(&new->link, &p->link);
return;
}
}
list_add_tail(&new->link, &p->link);
}
int register_client(struct edp_manager *mgr, struct edp_client *client)
{
if (!mgr || !client)
return -EINVAL;
if (!mgr->registered)
return -ENODEV;
if (client->manager || find_client(mgr, client->name))
return -EEXIST;
if (!states_ok(client) || client->priority > EDP_MIN_PRIO ||
client->priority < EDP_MAX_PRIO ||
(client->e0_index && !client->throttle))
return -EINVAL;
/* make sure that we can satisfy E0 for all registered clients */
if (e0_current_sum(mgr) + client->states[client->e0_index] > mgr->max)
return -E2BIG;
add_client(client, &mgr->clients);
client->manager = mgr;
client->req = NULL;
client->cur = NULL;
INIT_LIST_HEAD(&client->borrowers);
client->num_borrowers = 0;
client->num_loans = 0;
client->ithreshold = client->states[0];
client->kobj = NULL;
edp_client_add_kobject(client);
client_add_dentry(client);
return 0;
}
int edp_register_client(struct edp_manager *mgr, struct edp_client *client)
{
int r;
mutex_lock(&edp_lock);
r = register_client(mgr, client);
mutex_unlock(&edp_lock);
return r;
}
EXPORT_SYMBOL(edp_register_client);
static void update_loans(struct edp_client *client)
{
struct edp_governor *gov;
gov = client->manager ? client->manager->gov : NULL;
if (gov && client->cur && !list_empty(&client->borrowers)) {
if (gov->update_loans && *client->cur > client->ithreshold)
gov->update_loans(client);
}
}
/* generic default implementation */
void edp_default_update_request(struct edp_client *client,
const unsigned int *req,
void (*throttle)(struct edp_client *))
{
struct edp_manager *m = client->manager;
unsigned int old = cur_level(client);
unsigned int new = req ? *req : 0;
bool was_denied = client->cur != client->req;
client->req = req;
if (new < old) {
client->cur = req;
m->remaining += old - new;
} else if (new - old <= m->remaining) {
client->cur = req;
m->remaining -= new - old;
} else {
throttle(client);
}
if (was_denied && client->cur == client->req)
m->num_denied--;
else if (!was_denied && client->cur != client->req)
m->num_denied++;
}
/* generic default implementation */
void edp_default_update_loans(struct edp_client *lender)
{
unsigned int size = *lender->cur - lender->ithreshold;
struct loan_client *p;
list_for_each_entry(p, &lender->borrowers, link) {
if (size != p->size) {
p->size = p->client->notify_loan_update(
size, lender, p->client->private_data);
WARN_ON(p->size > size);
}
size -= min(p->size, size);
if (!size)
return;
}
}
unsigned int edp_throttling_point(struct edp_client *c, unsigned int deficit)
{
unsigned int lim;
unsigned int i;
if (cur_level(c) - e0_level(c) <= deficit)
return c->e0_index;
lim = cur_level(c) - deficit;
i = cur_index(c);
while (i < c->e0_index && c->states[i] > lim)
i++;
return i;
}
unsigned int edp_promotion_point(struct edp_client *c, unsigned int step)
{
unsigned int limit = cur_level(c) + step;
unsigned int ci = cur_index(c);
unsigned int i = req_index(c);
while (i < ci && c->states[i] > limit)
i++;
WARN_ON(i >= c->num_states);
return i;
}
static int mod_request(struct edp_client *client, const unsigned int *req)
{
struct edp_manager *m = client->manager;
unsigned int prev_remain = m->remaining;
unsigned int prev_denied = m->num_denied;
if (!m->gov)
return -ENODEV;
m->gov->update_request(client, req);
update_loans(client);
/* Do not block calling clients for promotions */
if (m->remaining > prev_remain)
schedule_promotion(m);
if (m->num_denied != prev_denied)
sysfs_notify(m->kobj, NULL, "denied");
return 0;
}
static void del_borrower(struct edp_client *lender, struct loan_client *pcl)
{
pcl->client->notify_loan_close(lender, pcl->client->private_data);
lender->num_borrowers--;
pcl->client->num_loans--;
list_del(&pcl->link);
kfree(pcl);
}
static void close_all_loans(struct edp_client *client)
{
struct loan_client *p;
while (!list_empty(&client->borrowers)) {
p = list_first_entry(&client->borrowers, struct loan_client,
link);
del_borrower(client, p);
}
}
static inline bool registered_client(struct edp_client *client)
{
return client ? client->manager : false;
}
int unregister_client(struct edp_client *client)
{
if (!registered_client(client))
return -EINVAL;
if (client->num_loans)
return -EBUSY;
client_remove_dentry(client);
edp_client_remove_kobject(client);
close_all_loans(client);
mod_request(client, NULL);
list_del(&client->link);
client->manager = NULL;
return 0;
}
int edp_unregister_client(struct edp_client *client)
{
int r;
mutex_lock(&edp_lock);
r = unregister_client(client);
mutex_unlock(&edp_lock);
return r;
}
EXPORT_SYMBOL(edp_unregister_client);
int edp_update_client_request_unlocked(struct edp_client *client,
unsigned int req, int *approved)
{
int r;
if (!registered_client(client))
return -EINVAL;
if (req >= client->num_states)
return -EINVAL;
r = mod_request(client, client->states + req);
if (!r && approved)
*approved = client->cur - client->states;
return r;
}
int edp_update_client_request(struct edp_client *client, unsigned int req,
unsigned int *approved)
{
int r;
mutex_lock(&edp_lock);
r = edp_update_client_request_unlocked(client, req, approved);
mutex_unlock(&edp_lock);
return r;
}
EXPORT_SYMBOL(edp_update_client_request);
static struct edp_client *get_client(const char *name)
{
struct edp_client *client;
struct edp_manager *mgr;
if (!name)
return NULL;
list_for_each_entry(mgr, &edp_managers, link) {
client = find_client(mgr, name);
if (client)
return client;
}
return NULL;
}
struct edp_client *edp_get_client(const char *name)
{
struct edp_client *client;
mutex_lock(&edp_lock);
client = get_client(name);
mutex_unlock(&edp_lock);
return client;
}
EXPORT_SYMBOL(edp_get_client);
static struct loan_client *find_borrower(struct edp_client *lender,
struct edp_client *borrower)
{
struct loan_client *p;
list_for_each_entry(p, &lender->borrowers, link)
if (p->client == borrower)
return p;
return NULL;
}
/* Keep the list sorted on priority */
static void add_borrower(struct loan_client *new, struct list_head *head)
{
struct loan_client *p;
list_for_each_entry(p, head, link) {
if (p->client->priority > new->client->priority) {
list_add_tail(&new->link, &p->link);
return;
}
}
list_add_tail(&new->link, &p->link);
}
static int register_loan(struct edp_client *lender, struct edp_client *borrower)
{
struct loan_client *p;
if (!registered_client(lender) || !registered_client(borrower))
return -EINVAL;
if (lender->manager != borrower->manager ||
!borrower->notify_loan_update ||
!borrower->notify_loan_close)
return -EINVAL;
if (find_borrower(lender, borrower))
return -EEXIST;
if (lender->num_borrowers >= lender->max_borrowers)
return -EBUSY;
p = kzalloc(sizeof(*p), GFP_KERNEL);
if (!p)
return -ENOMEM;
p->client = borrower;
lender->num_borrowers++;
borrower->num_loans++;
add_borrower(p, &lender->borrowers);
update_loans(lender);
return 0;
}
int edp_register_loan(struct edp_client *lender, struct edp_client *borrower)
{
int r;
mutex_lock(&edp_lock);
r = register_loan(lender, borrower);
mutex_unlock(&edp_lock);
return r;
}
EXPORT_SYMBOL(edp_register_loan);
static int unregister_loan(struct edp_client *lender,
struct edp_client *borrower)
{
struct loan_client *p;
if (!registered_client(lender) || !registered_client(borrower))
return -EINVAL;
p = find_borrower(lender, borrower);
if (!p)
return -EINVAL;
del_borrower(lender, p);
update_loans(lender);
return 0;
}
int edp_unregister_loan(struct edp_client *lender, struct edp_client *borrower)
{
int r;
mutex_lock(&edp_lock);
r = unregister_loan(lender, borrower);
mutex_unlock(&edp_lock);
return r;
}
EXPORT_SYMBOL(edp_unregister_loan);
int edp_update_loan_threshold_unlocked(struct edp_client *client,
unsigned int threshold)
{
if (!registered_client(client))
return -EINVAL;
client->ithreshold = threshold;
update_loans(client);
return 0;
}
int edp_update_loan_threshold(struct edp_client *client, unsigned int threshold)
{
int r;
mutex_lock(&edp_lock);
r = edp_update_loan_threshold_unlocked(client, threshold);
mutex_unlock(&edp_lock);
return r;
}
EXPORT_SYMBOL(edp_update_loan_threshold);
struct edp_governor *edp_find_governor_unlocked(const char *s)
{
struct edp_governor *g;
list_for_each_entry(g, &edp_governors, link)
if (!strnicmp(s, g->name, EDP_NAME_LEN))
return g;
return NULL;
}
int edp_register_governor(struct edp_governor *gov)
{
int r = 0;
if (!gov)
return -EINVAL;
if (!gov->update_request)
return -EINVAL;
mutex_lock(&edp_lock);
if (edp_find_governor_unlocked(gov->name)) {
r = -EEXIST;
} else {
gov->refcnt = 1;
list_add(&gov->link, &edp_governors);
}
mutex_unlock(&edp_lock);
return r;
}
EXPORT_SYMBOL(edp_register_governor);
int edp_unregister_governor(struct edp_governor *gov)
{
int r = 0;
mutex_lock(&edp_lock);
if (!gov) {
r = -EINVAL;
} else if (gov->refcnt != 1) {
r = gov->refcnt > 1 ? -EBUSY : -ENODEV;
} else {
list_del(&gov->link);
gov->refcnt = 0;
}
mutex_unlock(&edp_lock);
return r;
}
EXPORT_SYMBOL(edp_unregister_governor);
struct edp_governor *edp_get_governor(const char *name)
{
struct edp_governor *g;
mutex_lock(&edp_lock);
g = edp_find_governor_unlocked(name);
mutex_unlock(&edp_lock);
return g;
}
EXPORT_SYMBOL(edp_get_governor);
int edp_set_governor(struct edp_manager *mgr, struct edp_governor *gov)
{
int r;
mutex_lock(&edp_lock);
r = edp_set_governor_unlocked(mgr, gov);
mutex_unlock(&edp_lock);
return r;
}
EXPORT_SYMBOL(edp_set_governor);