blob: bd1fc05709b55f653819358878115336cb017e73 [file] [log] [blame]
/*
* This module exposes the interface to kernel space for specifying
* QoS dependencies. It provides infrastructure for registration of:
*
* Dependents on a QoS value : register requests
* Watchers of QoS value : get notified when target QoS value changes
*
* This QoS design is best effort based. Dependents register their QoS needs.
* Watchers register to keep track of the current QoS needs of the system.
*
* There are 3 basic classes of QoS parameter: latency, timeout, throughput
* each have defined units:
* latency: usec
* timeout: usec <-- currently not used.
* throughput: kbs (kilo byte / sec)
*
* There are lists of pm_qos_objects each one wrapping requests, notifiers
*
* User mode requests on a QOS parameter register themselves to the
* subsystem by opening the device node /dev/... and writing there request to
* the node. As long as the process holds a file handle open to the node the
* client continues to be accounted for. Upon file release the usermode
* request is removed and a new qos target is computed. This way when the
* request that the application has is cleaned up when closes the file
* pointer or exits the pm_qos_object will get an opportunity to clean up.
*
* Mark Gross <mgross@linux.intel.com>
*
* Support added for bounded constraints by
* Sai Gurrappadi <sgurrappadi@nvidia.com>
* Copyright (c) 2013-2014, NVIDIA CORPORATION. All rights reserved.
*/
/*#define DEBUG*/
#include <linux/pm_qos.h>
#include <linux/sched.h>
#include <linux/spinlock.h>
#include <linux/slab.h>
#include <linux/time.h>
#include <linux/fs.h>
#include <linux/device.h>
#include <linux/miscdevice.h>
#include <linux/string.h>
#include <linux/platform_device.h>
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/moduleparam.h>
#include <linux/uaccess.h>
#include <linux/export.h>
/*
* locking rule: all changes to constraints or notifiers lists
* or pm_qos_object list or pm_qos_bounded objects/lists and
* pm_qos_objects need to happen with pm_qos_lock
* One lock to rule them all
*/
struct pm_qos_object {
struct pm_qos_constraints *constraints;
struct miscdevice pm_qos_power_miscdev;
char *name;
};
struct pm_qos_bounded_object {
struct pm_qos_bounded_constraint *bounds;
struct miscdevice miscdev;
char *name;
};
static DEFINE_MUTEX(pm_qos_lock);
static struct pm_qos_object null_pm_qos;
static struct pm_qos_bounded_object null_pm_qos_bounded;
static BLOCKING_NOTIFIER_HEAD(cpu_dma_lat_notifier);
static struct pm_qos_constraints cpu_dma_constraints = {
.list = PLIST_HEAD_INIT(cpu_dma_constraints.list),
.target_value = PM_QOS_CPU_DMA_LAT_DEFAULT_VALUE,
.default_value = PM_QOS_CPU_DMA_LAT_DEFAULT_VALUE,
.type = PM_QOS_MIN,
.notifiers = &cpu_dma_lat_notifier,
};
static struct pm_qos_object cpu_dma_pm_qos = {
.constraints = &cpu_dma_constraints,
.name = "cpu_dma_latency",
};
static BLOCKING_NOTIFIER_HEAD(network_lat_notifier);
static struct pm_qos_constraints network_lat_constraints = {
.list = PLIST_HEAD_INIT(network_lat_constraints.list),
.target_value = PM_QOS_NETWORK_LAT_DEFAULT_VALUE,
.default_value = PM_QOS_NETWORK_LAT_DEFAULT_VALUE,
.type = PM_QOS_MIN,
.notifiers = &network_lat_notifier,
};
static struct pm_qos_object network_lat_pm_qos = {
.constraints = &network_lat_constraints,
.name = "network_latency",
};
static BLOCKING_NOTIFIER_HEAD(network_throughput_notifier);
static struct pm_qos_constraints network_tput_constraints = {
.list = PLIST_HEAD_INIT(network_tput_constraints.list),
.target_value = PM_QOS_NETWORK_THROUGHPUT_DEFAULT_VALUE,
.default_value = PM_QOS_NETWORK_THROUGHPUT_DEFAULT_VALUE,
.type = PM_QOS_MAX,
.notifiers = &network_throughput_notifier,
};
static struct pm_qos_object network_throughput_pm_qos = {
.constraints = &network_tput_constraints,
.name = "network_throughput",
};
static struct pm_qos_bounded_constraint online_cpus_constraint = {
.prio_list = PLIST_HEAD_INIT(online_cpus_constraint.prio_list),
.max_class = PM_QOS_MAX_ONLINE_CPUS,
.min_class = PM_QOS_MIN_ONLINE_CPUS,
.min_wins = true,
};
static struct pm_qos_bounded_object online_cpus_pm_qos = {
.bounds = &online_cpus_constraint,
.name = "constraint_online_cpus",
};
static BLOCKING_NOTIFIER_HEAD(min_online_cpus_notifier);
static struct pm_qos_constraints min_online_cpus_constraints = {
.list = PLIST_HEAD_INIT(min_online_cpus_constraints.list),
.target_value = PM_QOS_MIN_ONLINE_CPUS_DEFAULT_VALUE,
.default_value = PM_QOS_MIN_ONLINE_CPUS_DEFAULT_VALUE,
.type = PM_QOS_MAX,
.notifiers = &min_online_cpus_notifier,
.parent_class = PM_QOS_ONLINE_CPUS_BOUNDS,
};
static struct pm_qos_object min_online_cpus_pm_qos = {
.constraints = &min_online_cpus_constraints,
.name = "min_online_cpus",
};
static BLOCKING_NOTIFIER_HEAD(max_online_cpus_notifier);
static struct pm_qos_constraints max_online_cpus_constraints = {
.list = PLIST_HEAD_INIT(max_online_cpus_constraints.list),
.target_value = PM_QOS_MAX_ONLINE_CPUS_DEFAULT_VALUE,
.default_value = PM_QOS_MAX_ONLINE_CPUS_DEFAULT_VALUE,
.type = PM_QOS_MIN,
.notifiers = &max_online_cpus_notifier,
.parent_class = PM_QOS_ONLINE_CPUS_BOUNDS,
};
static struct pm_qos_object max_online_cpus_pm_qos = {
.constraints = &max_online_cpus_constraints,
.name = "max_online_cpus",
};
static struct pm_qos_bounded_constraint cpu_freq_constraint = {
.prio_list = PLIST_HEAD_INIT(cpu_freq_constraint.prio_list),
.max_class = PM_QOS_CPU_FREQ_MAX,
.min_class = PM_QOS_CPU_FREQ_MIN,
.min_wins = false,
};
static struct pm_qos_bounded_object cpu_freq_pm_qos = {
.bounds = &cpu_freq_constraint,
.name = "constraint_cpu_freq",
};
static BLOCKING_NOTIFIER_HEAD(cpu_freq_min_notifier);
static struct pm_qos_constraints cpu_freq_min_constraints = {
.list = PLIST_HEAD_INIT(cpu_freq_min_constraints.list),
.target_value = PM_QOS_CPU_FREQ_MIN_DEFAULT_VALUE,
.default_value = PM_QOS_CPU_FREQ_MIN_DEFAULT_VALUE,
.type = PM_QOS_MAX,
.notifiers = &cpu_freq_min_notifier,
.parent_class = PM_QOS_CPU_FREQ_BOUNDS,
};
static struct pm_qos_object cpu_freq_min_pm_qos = {
.constraints = &cpu_freq_min_constraints,
.name = "cpu_freq_min",
};
static BLOCKING_NOTIFIER_HEAD(cpu_freq_max_notifier);
static struct pm_qos_constraints cpu_freq_max_constraints = {
.list = PLIST_HEAD_INIT(cpu_freq_max_constraints.list),
.target_value = PM_QOS_CPU_FREQ_MAX_DEFAULT_VALUE,
.default_value = PM_QOS_CPU_FREQ_MAX_DEFAULT_VALUE,
.type = PM_QOS_MIN,
.notifiers = &cpu_freq_max_notifier,
.parent_class = PM_QOS_CPU_FREQ_BOUNDS,
};
static struct pm_qos_object cpu_freq_max_pm_qos = {
.constraints = &cpu_freq_max_constraints,
.name = "cpu_freq_max",
};
static struct pm_qos_bounded_constraint gpu_freq_constraint = {
.prio_list = PLIST_HEAD_INIT(gpu_freq_constraint.prio_list),
.max_class = PM_QOS_GPU_FREQ_MAX,
.min_class = PM_QOS_GPU_FREQ_MIN,
.min_wins = false,
};
static struct pm_qos_bounded_object gpu_freq_pm_qos = {
.bounds = &gpu_freq_constraint,
.name = "constraint_gpu_freq",
};
static BLOCKING_NOTIFIER_HEAD(gpu_freq_min_notifier);
static struct pm_qos_constraints gpu_freq_min_constraints = {
.list = PLIST_HEAD_INIT(gpu_freq_min_constraints.list),
.target_value = PM_QOS_GPU_FREQ_MIN_DEFAULT_VALUE,
.default_value = PM_QOS_GPU_FREQ_MIN_DEFAULT_VALUE,
.type = PM_QOS_MAX,
.notifiers = &gpu_freq_min_notifier,
.parent_class = PM_QOS_GPU_FREQ_BOUNDS,
};
static struct pm_qos_object gpu_freq_min_pm_qos = {
.constraints = &gpu_freq_min_constraints,
.name = "gpu_freq_min",
};
static BLOCKING_NOTIFIER_HEAD(gpu_freq_max_notifier);
static struct pm_qos_constraints gpu_freq_max_constraints = {
.list = PLIST_HEAD_INIT(gpu_freq_max_constraints.list),
.target_value = PM_QOS_GPU_FREQ_MAX_DEFAULT_VALUE,
.default_value = PM_QOS_GPU_FREQ_MAX_DEFAULT_VALUE,
.type = PM_QOS_MIN,
.notifiers = &gpu_freq_max_notifier,
.parent_class = PM_QOS_GPU_FREQ_BOUNDS,
};
static struct pm_qos_object gpu_freq_max_pm_qos = {
.constraints = &gpu_freq_max_constraints,
.name = "gpu_freq_max",
};
static BLOCKING_NOTIFIER_HEAD(emc_freq_min_notifier);
static struct pm_qos_constraints emc_freq_min_constraints = {
.list = PLIST_HEAD_INIT(emc_freq_min_constraints.list),
.target_value = PM_QOS_EMC_FREQ_MIN_DEFAULT_VALUE,
.default_value = PM_QOS_EMC_FREQ_MIN_DEFAULT_VALUE,
.type = PM_QOS_MAX,
.notifiers = &emc_freq_min_notifier,
};
static struct pm_qos_object emc_freq_min_pm_qos = {
.constraints = &emc_freq_min_constraints,
.name = "emc_freq_min",
};
static struct pm_qos_object *pm_qos_array[] = {
&null_pm_qos,
&cpu_dma_pm_qos,
&network_lat_pm_qos,
&network_throughput_pm_qos,
&min_online_cpus_pm_qos,
&max_online_cpus_pm_qos,
&cpu_freq_min_pm_qos,
&cpu_freq_max_pm_qos,
&gpu_freq_min_pm_qos,
&gpu_freq_max_pm_qos,
&emc_freq_min_pm_qos
};
static struct pm_qos_bounded_object * const pm_qos_bounded_obj_array[] = {
&null_pm_qos_bounded,
&cpu_freq_pm_qos,
&gpu_freq_pm_qos,
&online_cpus_pm_qos
};
static ssize_t pm_qos_power_write(struct file *filp, const char __user *buf,
size_t count, loff_t *f_pos);
static ssize_t pm_qos_power_read(struct file *filp, char __user *buf,
size_t count, loff_t *f_pos);
static int pm_qos_power_open(struct inode *inode, struct file *filp);
static int pm_qos_power_release(struct inode *inode, struct file *filp);
static const struct file_operations pm_qos_power_fops = {
.write = pm_qos_power_write,
.read = pm_qos_power_read,
.open = pm_qos_power_open,
.release = pm_qos_power_release,
.llseek = noop_llseek,
};
static ssize_t pm_qos_bounded_write(struct file *filp, const char __user *buf,
size_t count, loff_t *f_pos);
static ssize_t pm_qos_bounded_read(struct file *filp, char __user *buf,
size_t count, loff_t *f_pos);
static int pm_qos_bounded_open(struct inode *inode, struct file *filp);
static int pm_qos_bounded_release(struct inode *inode, struct file *filp);
static const struct file_operations pm_qos_bounded_constraint_fops = {
.write = pm_qos_bounded_write,
.read = pm_qos_bounded_read,
.open = pm_qos_bounded_open,
.release = pm_qos_bounded_release,
.llseek = generic_file_llseek,
};
static bool pm_qos_enabled __read_mostly = true;
/* unlocked internal variant */
static inline int pm_qos_get_value(struct pm_qos_constraints *c)
{
if (plist_head_empty(&c->list))
return c->default_value;
switch (c->type) {
case PM_QOS_MIN:
return plist_first(&c->list)->prio;
case PM_QOS_MAX:
return plist_last(&c->list)->prio;
default:
/* runtime check for not using enum */
BUG();
return PM_QOS_DEFAULT_VALUE;
}
}
s32 pm_qos_read_value(struct pm_qos_constraints *c)
{
return c->target_value;
}
static inline void pm_qos_set_value(struct pm_qos_constraints *c, s32 value)
{
c->target_value = value;
}
/*
* Finds the current max and min targets for the given bounded constraint.
* If buf is non NULL, the max and min targets at each priority level are
* written to the provided buffer as long as buf_size isn't exceeded.
* Returns the bytes read into the buffer and updates variables target_max and
* target_min with new targets.
*/
static size_t pm_qos_find_bounded_targets(struct pm_qos_bounded_constraint *c,
s32 *target_max, s32 *target_min,
char *buf, size_t buf_size)
{
char str[30];
size_t size, bytes_read;
struct pm_qos_prio *p;
struct pm_qos_constraints *max_constraint, *min_constraint;
s32 cur_max, cur_min, tmp_max, tmp_min;
bool stop = false;
char header[] = "Priority Min Max\n";
max_constraint = pm_qos_array[c->max_class]->constraints;
min_constraint = pm_qos_array[c->min_class]->constraints;
cur_max = max_constraint->default_value;
cur_min = min_constraint->default_value;
bytes_read = 0;
if (buf) {
size = strlen(header);
if (size > buf_size)
return 0;
memcpy(buf, header, size);
bytes_read = size;
buf_size -= size;
buf += size;
}
/*
* Output the intersection of all (min, max) higher priority
* ranges at each priority level
*/
plist_for_each_entry(p, &c->prio_list, node) {
tmp_min = min_constraint->default_value;
tmp_max = max_constraint->default_value;
if (!plist_head_empty(&p->max_list))
tmp_max = plist_first(&p->max_list)->prio;
if (!plist_head_empty(&p->min_list))
tmp_min = plist_last(&p->min_list)->prio;
if (tmp_min > tmp_max) {
if (c->min_wins)
tmp_max = tmp_min;
else
tmp_min = tmp_max;
}
if (tmp_min > cur_min) {
if (tmp_min < cur_max) {
cur_min = tmp_min;
} else {
cur_min = cur_max;
stop = true;
}
}
if (tmp_max < cur_max) {
if (tmp_max > cur_min) {
cur_max = tmp_max;
} else {
cur_max = cur_min;
stop = true;
}
}
if (buf) {
size = scnprintf(str, sizeof(str), "%i %i %i\n",
p->node.prio, cur_min, cur_max);
if (size > buf_size)
return 0;
memcpy(buf, str, size);
buf += size;
buf_size -= size;
bytes_read += size;
}
if (stop)
break;
}
if (target_max)
*target_max = cur_max;
if (target_min)
*target_min = cur_min;
return bytes_read;
}
/* Updates the target bounds for the given bounded constraint */
static void pm_qos_set_bounded_targets(struct pm_qos_bounded_constraint *c)
{
struct pm_qos_constraints *max_constraint, *min_constraint;
s32 cur_max, cur_min;
max_constraint = pm_qos_array[c->max_class]->constraints;
min_constraint = pm_qos_array[c->min_class]->constraints;
cur_max = max_constraint->default_value;
cur_min = min_constraint->default_value;
if (pm_qos_enabled)
pm_qos_find_bounded_targets(c, &cur_max, &cur_min, NULL, 0);
pm_qos_set_value(max_constraint, cur_max);
pm_qos_set_value(min_constraint, cur_min);
}
/*
* Remove node of the given priority and type. Removes priority node
* from the list of priorities if it is no longer used.
*/
static void pm_qos_remove_node(struct plist_node *node,
struct pm_qos_prio *priority,
struct pm_qos_bounded_constraint *c,
enum pm_qos_type type)
{
if (!priority)
return;
/* pm_qos_max => minimum bound of constraint and vice versa */
if (type == PM_QOS_MAX)
plist_del(node, &priority->min_list);
else if (type == PM_QOS_MIN)
plist_del(node, &priority->max_list);
else
return;
/* Remove priority level if no longer used */
if (plist_head_empty(&priority->max_list) &&
plist_head_empty(&priority->min_list)) {
plist_del(&priority->node, &c->prio_list);
kfree(priority);
}
}
/* Add new node at the specified priority for the given type */
static void pm_qos_add_node(struct plist_node *node,
struct pm_qos_prio *priority,
enum pm_qos_type type)
{
if (!priority)
return;
/* pm_qos_max => minimum bound of constraint and vice versa */
if (type == PM_QOS_MAX)
plist_add(node, &priority->min_list);
else if (type == PM_QOS_MIN)
plist_add(node, &priority->max_list);
return;
}
/* Creates, initializes and adds the priority level. Returns NULL on failure */
static struct pm_qos_prio *pm_qos_add_priority(int priority,
struct plist_head *list)
{
struct pm_qos_prio *prio = kzalloc(sizeof(*prio), GFP_KERNEL);
if (!prio)
return NULL;
plist_node_init(&prio->node, priority);
plist_head_init(&prio->max_list);
plist_head_init(&prio->min_list);
plist_add(&prio->node, list);
return prio;
}
/* Returns priority level from the priority list. NULL if it doesn't exist */
static struct pm_qos_prio *pm_qos_get_prio_level(int priority,
struct plist_head *list)
{
struct pm_qos_prio *p;
if (plist_head_empty(list))
return NULL;
plist_for_each_entry(p, list, node)
if (p->node.prio == priority)
return p;
return NULL;
}
/**
* pm_qos_update_bounded_target - Update a bounded constraints target bounds
* @c: bound that is being updated (either min or max)
* @value: new value to add or update for the given bound
* @req: the request that is getting updated/added or removed
* @priority: new priority of the bound request
* @action: remove/add/update req
*
* Returns a negative value on error, 0 if the target bounds were not updated
* and 1 if the target bounds were updated.
*/
static int pm_qos_update_bounded_target(struct pm_qos_constraints *c, s32 value,
struct pm_qos_request *req,
int priority,
enum pm_qos_req_action action)
{
struct pm_qos_constraints *max_constraint, *min_constraint;
struct pm_qos_bounded_constraint *parent;
struct pm_qos_prio *prio;
s32 prev_max, prev_min, curr_max, curr_min, new_value;
int ret = -EINVAL;
if (!c->parent_class)
return 0;
mutex_lock(&pm_qos_lock);
parent = pm_qos_bounded_obj_array[c->parent_class]->bounds;
max_constraint = pm_qos_array[parent->max_class]->constraints;
min_constraint = pm_qos_array[parent->min_class]->constraints;
prev_max = pm_qos_read_value(max_constraint);
prev_min = pm_qos_read_value(min_constraint);
new_value = value;
if (value == PM_QOS_DEFAULT_VALUE)
new_value = c->default_value;
switch (action) {
case PM_QOS_REMOVE_REQ:
prio = pm_qos_get_prio_level(priority, &parent->prio_list);
pm_qos_remove_node(&req->node, prio, parent, c->type);
break;
case PM_QOS_UPDATE_REQ:
/*
* Remove old node and update by reinitializing node and
* adding it
*/
prio = pm_qos_get_prio_level(req->priority, &parent->prio_list);
if (!prio) {
WARN(1, KERN_ERR "pm_qos_update_bounded_target: priority does not exist\n");
mutex_unlock(&pm_qos_lock);
return -EINVAL;
}
pm_qos_remove_node(&req->node, prio, parent, c->type);
/* Fall through and add */
case PM_QOS_ADD_REQ:
prio = pm_qos_get_prio_level(priority, &parent->prio_list);
if (!prio) {
prio = pm_qos_add_priority(priority,
&parent->prio_list);
if (!prio) {
mutex_unlock(&pm_qos_lock);
return -ENOMEM;
}
}
plist_node_init(&req->node, new_value);
pm_qos_add_node(&req->node, prio, c->type);
break;
default:
break;
}
pm_qos_set_bounded_targets(parent);
curr_max = pm_qos_read_value(max_constraint);
curr_min = pm_qos_read_value(min_constraint);
ret = 0;
/* Call notifiers if necessary */
if (prev_max != curr_max) {
if (curr_max < prev_min) {
blocking_notifier_call_chain(min_constraint->notifiers,
(unsigned long)curr_min,
NULL);
prev_min = curr_min;
}
blocking_notifier_call_chain(max_constraint->notifiers,
(unsigned long)curr_max,
NULL);
ret = 1;
}
if (prev_min != curr_min) {
blocking_notifier_call_chain(min_constraint->notifiers,
(unsigned long)curr_min,
NULL);
ret = 1;
}
mutex_unlock(&pm_qos_lock);
return ret;
}
/**
* pm_qos_update_target - manages the constraints list and calls the notifiers
* if needed
* @c: constraints data struct
* @node: request to add to the list, to update or to remove
* @action: action to take on the constraints list
* @value: value of the request to add or update
*
* This function returns 1 if the aggregated constraint value has changed, 0
* otherwise.
*/
int pm_qos_update_target(struct pm_qos_constraints *c, struct plist_node *node,
enum pm_qos_req_action action, int value)
{
int prev_value, curr_value, new_value, ret;
mutex_lock(&pm_qos_lock);
prev_value = pm_qos_get_value(c);
if (value == PM_QOS_DEFAULT_VALUE)
new_value = c->default_value;
else
new_value = value;
switch (action) {
case PM_QOS_REMOVE_REQ:
plist_del(node, &c->list);
break;
case PM_QOS_UPDATE_REQ:
/*
* to change the list, we atomically remove, reinit
* with new value and add, then see if the extremal
* changed
*/
plist_del(node, &c->list);
case PM_QOS_ADD_REQ:
plist_node_init(node, new_value);
plist_add(node, &c->list);
break;
default:
/* no action */
;
}
if (pm_qos_enabled) {
curr_value = pm_qos_get_value(c);
pm_qos_set_value(c, curr_value);
} else {
curr_value = c->default_value;
}
if (prev_value != curr_value) {
blocking_notifier_call_chain(c->notifiers,
(unsigned long)curr_value,
NULL);
ret = 1;
} else {
ret = 0;
}
mutex_unlock(&pm_qos_lock);
return ret;
}
/**
* pm_qos_flags_remove_req - Remove device PM QoS flags request.
* @pqf: Device PM QoS flags set to remove the request from.
* @req: Request to remove from the set.
*/
static void pm_qos_flags_remove_req(struct pm_qos_flags *pqf,
struct pm_qos_flags_request *req)
{
s32 val = 0;
list_del(&req->node);
list_for_each_entry(req, &pqf->list, node)
val |= req->flags;
pqf->effective_flags = val;
}
/**
* pm_qos_update_flags - Update a set of PM QoS flags.
* @pqf: Set of flags to update.
* @req: Request to add to the set, to modify, or to remove from the set.
* @action: Action to take on the set.
* @val: Value of the request to add or modify.
*
* Update the given set of PM QoS flags and call notifiers if the aggregate
* value has changed. Returns 1 if the aggregate constraint value has changed,
* 0 otherwise.
*/
bool pm_qos_update_flags(struct pm_qos_flags *pqf,
struct pm_qos_flags_request *req,
enum pm_qos_req_action action, s32 val)
{
s32 prev_value, curr_value;
mutex_lock(&pm_qos_lock);
prev_value = list_empty(&pqf->list) ? 0 : pqf->effective_flags;
switch (action) {
case PM_QOS_REMOVE_REQ:
pm_qos_flags_remove_req(pqf, req);
break;
case PM_QOS_UPDATE_REQ:
pm_qos_flags_remove_req(pqf, req);
case PM_QOS_ADD_REQ:
req->flags = val;
INIT_LIST_HEAD(&req->node);
list_add_tail(&req->node, &pqf->list);
pqf->effective_flags |= val;
break;
default:
/* no action */
;
}
curr_value = list_empty(&pqf->list) ? 0 : pqf->effective_flags;
mutex_unlock(&pm_qos_lock);
if (curr_value != prev_value && pqf->notifiers)
blocking_notifier_call_chain(pqf->notifiers,
(unsigned long)curr_value,
NULL);
return prev_value != curr_value;
}
/**
* pm_qos_request - returns current system wide qos expectation
* @pm_qos_class: identification of which qos value is requested
*
* This function returns the current target value.
*/
int pm_qos_request(int pm_qos_class)
{
return pm_qos_read_value(pm_qos_array[pm_qos_class]->constraints);
}
EXPORT_SYMBOL_GPL(pm_qos_request);
int pm_qos_request_active(struct pm_qos_request *req)
{
return req->pm_qos_class != 0;
}
EXPORT_SYMBOL_GPL(pm_qos_request_active);
static void __pm_qos_update_request(struct pm_qos_request *req,
s32 new_value)
{
struct pm_qos_constraints *c;
if (new_value == req->node.prio)
return;
c = pm_qos_array[req->pm_qos_class]->constraints;
if (c->parent_class)
pm_qos_update_bounded_target(c, new_value, req, req->priority,
PM_QOS_UPDATE_REQ);
else
pm_qos_update_target(c, &req->node, PM_QOS_UPDATE_REQ,
new_value);
}
/**
* pm_qos_work_fn - the timeout handler of pm_qos_update_request_timeout
* @work: work struct for the delayed work (timeout)
*
* This cancels the timeout request by falling back to the default at timeout.
*/
static void pm_qos_work_fn(struct work_struct *work)
{
struct pm_qos_request *req = container_of(to_delayed_work(work),
struct pm_qos_request,
work);
__pm_qos_update_request(req, PM_QOS_DEFAULT_VALUE);
}
/**
* pm_qos_add_request - inserts new qos request into the list
* @req: pointer to a preallocated handle
* @pm_qos_class: identifies which list of qos request to use
* @value: defines the qos request
*
* This function inserts a new entry in the pm_qos_class list of requested qos
* performance characteristics. It recomputes the aggregate QoS expectations
* for the pm_qos_class of parameters and initializes the pm_qos_request
* handle. Caller needs to save this handle for later use in updates and
* removal.
*/
void pm_qos_add_request(struct pm_qos_request *req,
int pm_qos_class, s32 value)
{
struct pm_qos_constraints *c;
if (!req) /*guard against callers passing in null */
return;
if (pm_qos_request_active(req)) {
WARN(1, KERN_ERR "pm_qos_add_request() called for already added request\n");
return;
}
req->pm_qos_class = pm_qos_class;
INIT_DELAYED_WORK(&req->work, pm_qos_work_fn);
c = pm_qos_array[pm_qos_class]->constraints;
if (c->parent_class) {
req->priority = PM_QOS_PRIO_TRUSTED;
pm_qos_update_bounded_target(c, value, req, req->priority,
PM_QOS_ADD_REQ);
} else {
pm_qos_update_target(c, &req->node, PM_QOS_ADD_REQ, value);
}
}
EXPORT_SYMBOL_GPL(pm_qos_add_request);
/**
* pm_qos_update_request - modifies an existing qos request
* @req : handle to list element holding a pm_qos request to use
* @value: defines the qos request
*
* Updates an existing qos request for the pm_qos_class of parameters along
* with updating the target pm_qos_class value.
*
* Attempts are made to make this code callable on hot code paths.
*/
void pm_qos_update_request(struct pm_qos_request *req,
s32 new_value)
{
if (!req) /*guard against callers passing in null */
return;
if (!pm_qos_request_active(req)) {
WARN(1, KERN_ERR "pm_qos_update_request() called for unknown object\n");
return;
}
cancel_delayed_work_sync(&req->work);
__pm_qos_update_request(req, new_value);
}
EXPORT_SYMBOL_GPL(pm_qos_update_request);
/**
* pm_qos_update_request_timeout - modifies an existing qos request temporarily.
* @req : handle to list element holding a pm_qos request to use
* @new_value: defines the temporal qos request
* @timeout_us: the effective duration of this qos request in usecs.
*
* After timeout_us, this qos request is cancelled automatically.
*/
void pm_qos_update_request_timeout(struct pm_qos_request *req, s32 new_value,
unsigned long timeout_us)
{
struct pm_qos_constraints *c;
if (!req)
return;
if (WARN(!pm_qos_request_active(req),
"%s called for unknown object.", __func__))
return;
cancel_delayed_work_sync(&req->work);
c = pm_qos_array[req->pm_qos_class]->constraints;
if (new_value == req->node.prio) {
schedule_delayed_work(&req->work, usecs_to_jiffies(timeout_us));
return;
}
if (c->parent_class)
pm_qos_update_bounded_target(c, new_value, req, req->priority,
PM_QOS_UPDATE_REQ);
else
pm_qos_update_target(c, &req->node,
PM_QOS_UPDATE_REQ, new_value);
schedule_delayed_work(&req->work, usecs_to_jiffies(timeout_us));
}
EXPORT_SYMBOL_GPL(pm_qos_update_request_timeout);
/**
* pm_qos_remove_request - modifies an existing qos request
* @req: handle to request list element
*
* Will remove pm qos request from the list of constraints and
* recompute the current target value for the pm_qos_class. Call this
* on slow code paths.
*/
void pm_qos_remove_request(struct pm_qos_request *req)
{
struct pm_qos_constraints *c;
if (!req) /*guard against callers passing in null */
return;
/* silent return to keep pcm code cleaner */
if (!pm_qos_request_active(req)) {
WARN(1, KERN_ERR "pm_qos_remove_request() called for unknown object\n");
return;
}
cancel_delayed_work_sync(&req->work);
c = pm_qos_array[req->pm_qos_class]->constraints;
if (c->parent_class)
pm_qos_update_bounded_target(c, PM_QOS_DEFAULT_VALUE, req,
req->priority, PM_QOS_REMOVE_REQ);
else
pm_qos_update_target(c, &req->node, PM_QOS_REMOVE_REQ,
PM_QOS_DEFAULT_VALUE);
memset(req, 0, sizeof(*req));
}
EXPORT_SYMBOL_GPL(pm_qos_remove_request);
/**
* pm_qos_add_min_bound_req - adds a new minimum bound for a constraint
* @req: handle to request being added
* @priority: priority of the request being added. enum pm_qos_bound_priority
* @pm_qos_bounded_class: the bounded constraint id this min bound applies to
* @val: value of the min bound request
*/
void pm_qos_add_min_bound_req(struct pm_qos_request *req, int priority,
int pm_qos_bounded_class, s32 val)
{
struct pm_qos_bounded_constraint *c;
if (!req)
return;
if (pm_qos_request_active(req)) {
WARN(1, KERN_ERR "pm_qos_add_min_bound_req() called for already added request\n");
return;
}
c = pm_qos_bounded_obj_array[pm_qos_bounded_class]->bounds;
req->pm_qos_class = c->min_class;
req->priority = priority;
INIT_DELAYED_WORK(&req->work, pm_qos_work_fn);
pm_qos_update_bounded_target(pm_qos_array[c->min_class]->constraints,
val, req, req->priority, PM_QOS_ADD_REQ);
}
EXPORT_SYMBOL_GPL(pm_qos_add_min_bound_req);
/**
* pm_qos_add_max_bound_req - adds a new maximum bound for a constraint
* @req: handle to request being added
* @priority: priority of the request being added. enum pm_qos_bound_priority
* @pm_qos_bounded_class: the bounded constraint id this max bound applies to
* @val: value of the max bound request
*/
void pm_qos_add_max_bound_req(struct pm_qos_request *req, int priority,
int pm_qos_bounded_class, s32 val)
{
struct pm_qos_bounded_constraint *c;
if (!req)
return;
if (pm_qos_request_active(req)) {
WARN(1, KERN_ERR "pm_qos_add_max_bound_req() called for already added request\n");
return;
}
c = pm_qos_bounded_obj_array[pm_qos_bounded_class]->bounds;
req->pm_qos_class = c->max_class;
req->priority = priority;
INIT_DELAYED_WORK(&req->work, pm_qos_work_fn);
pm_qos_update_bounded_target(pm_qos_array[c->max_class]->constraints,
val, req, req->priority, PM_QOS_ADD_REQ);
}
EXPORT_SYMBOL_GPL(pm_qos_add_max_bound_req);
/**
* pm_qos_update_bounded_req - updates the requested bound for the constraint
* @req: handle to the request being updated
* @priority: priority to use for the request
* @val: updated value to use for the request
*
* Updating a request also resets any previous timeouts requests for the
* given request handle
*/
void pm_qos_update_bounded_req(struct pm_qos_request *req, int priority,
s32 val)
{
struct pm_qos_constraints *c;
if (!req)
return;
if (!pm_qos_request_active(req)) {
WARN(1, KERN_ERR "pm_qos_update_bounded_req() called for unknown object\n");
return;
}
cancel_delayed_work_sync(&req->work);
c = pm_qos_array[req->pm_qos_class]->constraints;
if (val == req->node.prio && priority == req->priority)
return;
pm_qos_update_bounded_target(c, val, req, priority,
PM_QOS_UPDATE_REQ);
req->priority = priority;
}
EXPORT_SYMBOL_GPL(pm_qos_update_bounded_req);
/**
* pm_qos_update_bounded_req_timeout - updates the timeout for the request
* @req: handle to the request for which timeout is updated
* @timeout_us: new timeout value to use in usecs
*/
void pm_qos_update_bounded_req_timeout(struct pm_qos_request *req,
unsigned long timeout_us)
{
pm_qos_update_request_timeout(req, req->node.prio, timeout_us);
}
EXPORT_SYMBOL_GPL(pm_qos_update_bounded_req_timeout);
/**
* pm_qos_remove_bounded_req - removes the requested bound
* @req: handle to the bound to remove
*
* Removes the requested type of bound for the bounded constraint
* and ensures that the target bounds are updated properly
*/
void pm_qos_remove_bounded_req(struct pm_qos_request *req)
{
pm_qos_remove_request(req);
}
EXPORT_SYMBOL_GPL(pm_qos_remove_bounded_req);
/**
* pm_qos_add_min_notifier - adds a notifier for the minimum bound
* @pm_qos_bounded_class: class id of the bounded constraint
* @notifier: notifier to add to the notifier list for min bound
*
* Notifier is called when there is a change to the minimum bound of
* the bounded constraint
*/
void pm_qos_add_min_notifier(int pm_qos_bounded_class,
struct notifier_block *notifier)
{
struct pm_qos_bounded_constraint *c;
c = pm_qos_bounded_obj_array[pm_qos_bounded_class]->bounds;
pm_qos_remove_notifier(c->min_class, notifier);
}
EXPORT_SYMBOL_GPL(pm_qos_add_min_notifier);
/**
* pm_qos_add_max_notifier - adds a notifier for the maximum bound
* @pm_qos_bounded_class: class id of the bounded constraint
* @notifier: notifier to add to the notifier list for min bound
*
* Notifier is called when there is a change to the maximum bound of
* the bounded constraint
*/
void pm_qos_add_max_notifier(int pm_qos_bounded_class,
struct notifier_block *notifier)
{
struct pm_qos_bounded_constraint *c;
c = pm_qos_bounded_obj_array[pm_qos_bounded_class]->bounds;
pm_qos_add_notifier(c->max_class, notifier);
}
EXPORT_SYMBOL_GPL(pm_qos_add_max_notifier);
/**
* pm_qos_remove_min_notifier - removes notifier for the minimum bound
* @pm_qos_bounded_class: class id of the bounded constraint
* @notifier: notifier to remove from the notifier list for min bound
*/
void pm_qos_remove_min_notifier(int pm_qos_bounded_class,
struct notifier_block *notifier)
{
struct pm_qos_bounded_constraint *c;
c = pm_qos_bounded_obj_array[pm_qos_bounded_class]->bounds;
pm_qos_remove_notifier(c->min_class, notifier);
}
EXPORT_SYMBOL_GPL(pm_qos_remove_min_notifier);
/**
* pm_qos_remove_max_notifier - removes notifier for the maximum bound
* @pm_qos_bounded_class: class id of the bounded constraint
* @notifier: notifier to remove from the notifier list for max bound
*/
void pm_qos_remove_max_notifier(int pm_qos_bounded_class,
struct notifier_block *notifier)
{
struct pm_qos_bounded_constraint *c;
c = pm_qos_bounded_obj_array[pm_qos_bounded_class]->bounds;
pm_qos_remove_notifier(c->max_class, notifier);
}
EXPORT_SYMBOL_GPL(pm_qos_remove_max_notifier);
/**
* pm_qos_read_min_bound - gets the current min bound set by all requests
* @pm_qos_bounded_class: class id of the bounded constraint
*/
s32 pm_qos_read_min_bound(int pm_qos_bounded_class)
{
struct pm_qos_bounded_constraint *c;
struct pm_qos_constraints *bound;
c = pm_qos_bounded_obj_array[pm_qos_bounded_class]->bounds;
bound = pm_qos_array[c->min_class]->constraints;
return pm_qos_read_value(bound);
}
EXPORT_SYMBOL_GPL(pm_qos_read_min_bound);
/**
* pm_qos_read_max_bound - gets the current max bound set by all requests
* @pm_qos_bounded_class: class id of the bounded constraint
*/
s32 pm_qos_read_max_bound(int pm_qos_bounded_class)
{
struct pm_qos_bounded_constraint *c;
struct pm_qos_constraints *bound;
c = pm_qos_bounded_obj_array[pm_qos_bounded_class]->bounds;
bound = pm_qos_array[c->max_class]->constraints;
return pm_qos_read_value(bound);
}
EXPORT_SYMBOL_GPL(pm_qos_read_max_bound);
static int pm_qos_enabled_set(const char *arg, const struct kernel_param *kp)
{
bool old;
int ret, i;
struct pm_qos_constraints *c;
struct pm_qos_bounded_constraint *parent;
old = pm_qos_enabled;
ret = param_set_bool(arg, kp);
if (ret != 0) {
pr_warn("%s: cannot set PM QoS enable to %s\n",
__FUNCTION__, arg);
return ret;
}
mutex_lock(&pm_qos_lock);
for (i = 1; i < PM_QOS_NUM_CLASSES; i++) {
c = pm_qos_array[i]->constraints;
if (c->parent_class) {
int class = c->parent_class;
parent = pm_qos_bounded_obj_array[class]->bounds;
pm_qos_set_bounded_targets(parent);
} else if (old && !pm_qos_enabled) {
/* got disabled */
pm_qos_set_value(c, c->default_value);
} else if (!old && pm_qos_enabled) {
/* got enabled */
pm_qos_set_value(c, pm_qos_get_value(c));
}
if (old != pm_qos_enabled)
blocking_notifier_call_chain(c->notifiers,
(unsigned long)pm_qos_read_value(c),
NULL);
}
mutex_unlock(&pm_qos_lock);
return ret;
}
static int pm_qos_enabled_get(char *buffer, const struct kernel_param *kp)
{
return param_get_bool(buffer, kp);
}
static struct kernel_param_ops pm_qos_enabled_ops = {
.set = pm_qos_enabled_set,
.get = pm_qos_enabled_get,
};
module_param_cb(enable, &pm_qos_enabled_ops, &pm_qos_enabled, 0644);
/**
* pm_qos_add_notifier - sets notification entry for changes to target value
* @pm_qos_class: identifies which qos target changes should be notified.
* @notifier: notifier block managed by caller.
*
* will register the notifier into a notification chain that gets called
* upon changes to the pm_qos_class target value.
*/
int pm_qos_add_notifier(int pm_qos_class, struct notifier_block *notifier)
{
int retval;
retval = blocking_notifier_chain_register(
pm_qos_array[pm_qos_class]->constraints->notifiers,
notifier);
return retval;
}
EXPORT_SYMBOL_GPL(pm_qos_add_notifier);
/**
* pm_qos_remove_notifier - deletes notification entry from chain.
* @pm_qos_class: identifies which qos target changes are notified.
* @notifier: notifier block to be removed.
*
* will remove the notifier from the notification chain that gets called
* upon changes to the pm_qos_class target value.
*/
int pm_qos_remove_notifier(int pm_qos_class, struct notifier_block *notifier)
{
int retval;
retval = blocking_notifier_chain_unregister(
pm_qos_array[pm_qos_class]->constraints->notifiers,
notifier);
return retval;
}
EXPORT_SYMBOL_GPL(pm_qos_remove_notifier);
/* User space interface to PM QoS classes via misc devices */
static int register_pm_qos_misc(struct pm_qos_object *qos)
{
qos->pm_qos_power_miscdev.minor = MISC_DYNAMIC_MINOR;
qos->pm_qos_power_miscdev.name = qos->name;
qos->pm_qos_power_miscdev.fops = &pm_qos_power_fops;
return misc_register(&qos->pm_qos_power_miscdev);
}
static int find_pm_qos_object_by_minor(int minor)
{
int pm_qos_class;
for (pm_qos_class = 0;
pm_qos_class < PM_QOS_NUM_CLASSES; pm_qos_class++) {
if (minor ==
pm_qos_array[pm_qos_class]->pm_qos_power_miscdev.minor)
return pm_qos_class;
}
return -1;
}
static int pm_qos_power_open(struct inode *inode, struct file *filp)
{
long pm_qos_class;
struct pm_qos_constraints *c;
struct pm_qos_request *req;
pm_qos_class = find_pm_qos_object_by_minor(iminor(inode));
if (pm_qos_class < 0)
return -EPERM;
req = kzalloc(sizeof(*req), GFP_KERNEL);
if (!req)
return -ENOMEM;
c = pm_qos_array[pm_qos_class]->constraints;
if (c->parent_class && c->type == PM_QOS_MAX)
pm_qos_add_min_bound_req(req, PM_QOS_PRIO_DEFAULT_UNTRUSTED,
c->parent_class, PM_QOS_DEFAULT_VALUE);
else if (c->parent_class && c->type == PM_QOS_MIN)
pm_qos_add_max_bound_req(req, PM_QOS_PRIO_DEFAULT_UNTRUSTED,
c->parent_class, PM_QOS_DEFAULT_VALUE);
else
pm_qos_add_request(req, pm_qos_class, PM_QOS_DEFAULT_VALUE);
filp->private_data = req;
return 0;
}
static int pm_qos_power_release(struct inode *inode, struct file *filp)
{
struct pm_qos_request *req;
req = filp->private_data;
pm_qos_remove_request(req);
kfree(req);
return 0;
}
static ssize_t pm_qos_power_read(struct file *filp, char __user *buf,
size_t count, loff_t *f_pos)
{
s32 value;
struct pm_qos_request *req = filp->private_data;
struct pm_qos_constraints *c;
if (!req)
return -EINVAL;
if (!pm_qos_request_active(req))
return -EINVAL;
mutex_lock(&pm_qos_lock);
c = pm_qos_array[req->pm_qos_class]->constraints;
if (c->parent_class)
value = pm_qos_read_value(c);
else
value = pm_qos_get_value(c);
mutex_unlock(&pm_qos_lock);
return simple_read_from_buffer(buf, count, f_pos, &value, sizeof(s32));
}
static ssize_t pm_qos_power_write(struct file *filp, const char __user *buf,
size_t count, loff_t *f_pos)
{
s32 value;
struct pm_qos_request *req;
if (count == sizeof(s32)) {
if (copy_from_user(&value, buf, sizeof(s32)))
return -EFAULT;
} else if (count <= 11) { /* ASCII perhaps? */
char ascii_value[11];
unsigned long int ulval;
int ret;
if (copy_from_user(ascii_value, buf, count))
return -EFAULT;
if (count > 10) {
if (ascii_value[10] == '\n')
ascii_value[10] = '\0';
else
return -EINVAL;
} else {
ascii_value[count] = '\0';
}
ret = kstrtoul(ascii_value, 16, &ulval);
if (ret) {
pr_debug("%s, 0x%lx, 0x%x\n", ascii_value, ulval, ret);
return -EINVAL;
}
value = (s32)lower_32_bits(ulval);
} else {
return -EINVAL;
}
req = filp->private_data;
pm_qos_update_request(req, value);
return count;
}
/* Userspace interface for bounded constraints */
static int register_pm_qos_bounded_obj(struct pm_qos_bounded_object *qos)
{
qos->miscdev.minor = MISC_DYNAMIC_MINOR;
qos->miscdev.name = qos->name;
qos->miscdev.fops = &pm_qos_bounded_constraint_fops;
return misc_register(&qos->miscdev);
}
static int find_pm_qos_bounded_obj_by_minor(int minor)
{
int class;
for (class = 0; class < PM_QOS_NUM_BOUNDED_CLASSES; class++)
if (minor == pm_qos_bounded_obj_array[class]->miscdev.minor)
return class;
return -1;
}
struct pm_qos_bounded_user_req {
struct pm_qos_request min_req;
struct pm_qos_request max_req;
};
/* Represents the userspace input */
struct pm_qos_bounded_input {
s32 max;
s32 min;
s32 priority;
s32 timeout_ms;
};
#define MAX_READ_BYTES 3000
#define MAX_WRITE_BYTES 50
/*
* Assumes input is a maximum of 4 s32 ASCII base10 numbers space delimited
* Order of input is max, min, priority and timeout
*/
static ssize_t pm_qos_bounded_write(struct file *filp, const char __user *buf,
size_t count, loff_t *f_pos)
{
int i, ret;
char *input, *val, *tmp;
struct pm_qos_bounded_input value;
struct pm_qos_constraints *max_constraint, *min_constraint;
struct pm_qos_bounded_user_req *req = filp->private_data;
s32 * const value_array[] = {
&value.max, &value.min, &value.priority, &value.timeout_ms
};
if (!count || count >= MAX_WRITE_BYTES)
return -EINVAL;
input = kzalloc(count + 1, GFP_KERNEL);
tmp = input;
if (!input)
return -ENOMEM;
if (copy_from_user(input, buf, count)) {
kfree(tmp);
return -EFAULT;
}
input[count] = '\0';
memset(&value, 0, sizeof(value));
max_constraint = pm_qos_array[req->max_req.pm_qos_class]->constraints;
min_constraint = pm_qos_array[req->min_req.pm_qos_class]->constraints;
value.max = max_constraint->default_value;
value.min = min_constraint->default_value;
i = 0;
while ((val = strsep(&input, " ")) != NULL) {
if (!val || *val == '\0')
break;
ret = kstrtoint(val, 10, value_array[i]);
if (ret) {
pr_debug("%s, %d, %x\n", val, *value_array[i], ret);
kfree(tmp);
return -EINVAL;
}
i++;
if (i >= ARRAY_SIZE(value_array))
break;
}
if (value.priority <= PM_QOS_PRIO_TRUSTED ||
value.priority >= PM_QOS_NUM_PRIO)
value.priority = PM_QOS_PRIO_DEFAULT_UNTRUSTED;
pm_qos_update_bounded_req(&req->max_req, value.priority, value.max);
pm_qos_update_bounded_req(&req->min_req, value.priority, value.min);
if (value.timeout_ms > 0) {
pm_qos_update_bounded_req_timeout(&req->max_req,
value.timeout_ms * 1000);
pm_qos_update_bounded_req_timeout(&req->min_req,
value.timeout_ms * 1000);
}
kfree(tmp);
return count;
}
/*
* Read out the intersection of all ranges with priority greater than or
* equal to at every priority level. If the intersection set is NULL the
* higher priority bound is taken. Returns the total number of bytes read.
* If count (max bytes to read) is not large enough to read the ranges
* for all priorities -EFAULT is returned
*/
static ssize_t pm_qos_bounded_read(struct file *filp, char __user *buf,
size_t count, loff_t *f_pos)
{
char *output;
struct pm_qos_constraints *max_constraint;
struct pm_qos_bounded_constraint *parent;
size_t bytes_read;
ssize_t ret;
struct pm_qos_bounded_user_req *req = filp->private_data;
max_constraint = pm_qos_array[req->max_req.pm_qos_class]->constraints;
parent = pm_qos_bounded_obj_array[max_constraint->parent_class]->bounds;
/* Finished reading */
if (*f_pos)
return 0;
output = kzalloc(MAX_READ_BYTES, GFP_KERNEL);
if (!output)
return -ENOMEM;
mutex_lock(&pm_qos_lock);
bytes_read = pm_qos_find_bounded_targets(parent, NULL, NULL,
output, MAX_READ_BYTES);
mutex_unlock(&pm_qos_lock);
if (bytes_read > count)
goto err;
ret = copy_to_user(buf, output, bytes_read);
if (bytes_read == ret)
goto err;
*f_pos += bytes_read - ret;
kfree(output);
return bytes_read - ret;
err:
kfree(output);
return -EFAULT;
}
static int pm_qos_bounded_open(struct inode *inode, struct file *filp)
{
long class;
struct pm_qos_bounded_user_req *req;
class = find_pm_qos_bounded_obj_by_minor(iminor(inode));
if (class < 0)
return -EPERM;
req = kzalloc(sizeof(*req), GFP_KERNEL);
if (!req)
return -ENOMEM;
pm_qos_add_max_bound_req(&req->max_req, PM_QOS_PRIO_DEFAULT_UNTRUSTED,
class, PM_QOS_DEFAULT_VALUE);
pm_qos_add_min_bound_req(&req->min_req, PM_QOS_PRIO_DEFAULT_UNTRUSTED,
class, PM_QOS_DEFAULT_VALUE);
filp->private_data = req;
return 0;
}
static int pm_qos_bounded_release(struct inode *inode, struct file *filp)
{
struct pm_qos_bounded_user_req *req;
req = filp->private_data;
pm_qos_remove_bounded_req(&req->max_req);
pm_qos_remove_bounded_req(&req->min_req);
kfree(req);
return 0;
}
static int __init pm_qos_power_init(void)
{
int ret = 0;
int i;
BUILD_BUG_ON(ARRAY_SIZE(pm_qos_array) != PM_QOS_NUM_CLASSES);
BUILD_BUG_ON(ARRAY_SIZE(pm_qos_bounded_obj_array) !=
PM_QOS_NUM_BOUNDED_CLASSES);
for (i = 1; i < PM_QOS_NUM_CLASSES; i++) {
ret = register_pm_qos_misc(pm_qos_array[i]);
if (ret < 0) {
printk(KERN_ERR "pm_qos_param: %s setup failed\n",
pm_qos_array[i]->name);
return ret;
}
}
for (i = 1; i < PM_QOS_NUM_BOUNDED_CLASSES; i++) {
ret = register_pm_qos_bounded_obj(pm_qos_bounded_obj_array[i]);
if (ret < 0) {
pr_err("pm_qos_bounded_reg: %s setup failed\n",
pm_qos_bounded_obj_array[i]->name);
return ret;
}
}
return ret;
}
late_initcall(pm_qos_power_init);