mirror of
https://github.com/torvalds/linux.git
synced 2024-11-15 00:21:59 +00:00
fc2fb3a075
The per-device PM QoS locking requires a spinlock to be used. The reasons are: - an alignement with the PM QoS core code, which is used by the per-device PM QoS code for the constraints lists management. The PM QoS core code uses spinlocks to protect the constraints lists, - some drivers need to use the per-device PM QoS functionality from interrupt context or spinlock protected context. An example of such a driver is the OMAP HSI (high-speed synchronous serial interface) driver which needs to control the IP block idle state depending on the FIFO empty state, from interrupt context. Reported-by: Djamil Elaidi <d-elaidi@ti.com> Signed-off-by: Jean Pihet <j-pihet@ti.com> Signed-off-by: Rafael J. Wysocki <rjw@sisk.pl>
529 lines
15 KiB
C
529 lines
15 KiB
C
/*
|
|
* Devices PM QoS constraints management
|
|
*
|
|
* Copyright (C) 2011 Texas Instruments, Inc.
|
|
*
|
|
* This program is free software; you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License version 2 as
|
|
* published by the Free Software Foundation.
|
|
*
|
|
*
|
|
* This module exposes the interface to kernel space for specifying
|
|
* per-device PM 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.
|
|
* Watchers can register different types of notification callbacks:
|
|
* . a per-device notification callback using the dev_pm_qos_*_notifier API.
|
|
* The notification chain data is stored in the per-device constraint
|
|
* data struct.
|
|
* . a system-wide notification callback using the dev_pm_qos_*_global_notifier
|
|
* API. The notification chain data is stored in a static variable.
|
|
*
|
|
* Notes about the per-device constraint data struct allocation:
|
|
* . The per-device constraints data struct ptr is stored into the device
|
|
* dev_pm_info.
|
|
* . To minimize the data usage by the per-device constraints, the data struct
|
|
* is only allocated at the first call to dev_pm_qos_add_request.
|
|
* . The data is later free'd when the device is removed from the system.
|
|
*
|
|
* Notes about locking:
|
|
* . The dev->power.lock lock protects the constraints list
|
|
* (dev->power.constraints) allocation and free, as triggered by the
|
|
* driver core code at device insertion and removal,
|
|
* . A global lock dev_pm_qos_lock protects the constraints list entries
|
|
* from any modification and the notifiers registration and unregistration.
|
|
* . For both locks a spinlock is needed since this code can be called from
|
|
* interrupt context or spinlock protected context.
|
|
*/
|
|
|
|
#include <linux/pm_qos.h>
|
|
#include <linux/spinlock.h>
|
|
#include <linux/slab.h>
|
|
#include <linux/device.h>
|
|
#include <linux/export.h>
|
|
|
|
#include "power.h"
|
|
|
|
static DEFINE_SPINLOCK(dev_pm_qos_lock);
|
|
|
|
static BLOCKING_NOTIFIER_HEAD(dev_pm_notifiers);
|
|
|
|
/**
|
|
* __dev_pm_qos_read_value - Get PM QoS constraint for a given device.
|
|
* @dev: Device to get the PM QoS constraint value for.
|
|
*
|
|
* This routine must be called with dev->power.lock held.
|
|
*/
|
|
s32 __dev_pm_qos_read_value(struct device *dev)
|
|
{
|
|
struct pm_qos_constraints *c = dev->power.constraints;
|
|
|
|
return c ? pm_qos_read_value(c) : 0;
|
|
}
|
|
|
|
/**
|
|
* dev_pm_qos_read_value - Get PM QoS constraint for a given device (locked).
|
|
* @dev: Device to get the PM QoS constraint value for.
|
|
*/
|
|
s32 dev_pm_qos_read_value(struct device *dev)
|
|
{
|
|
unsigned long flags;
|
|
s32 ret;
|
|
|
|
spin_lock_irqsave(&dev->power.lock, flags);
|
|
ret = __dev_pm_qos_read_value(dev);
|
|
spin_unlock_irqrestore(&dev->power.lock, flags);
|
|
|
|
return ret;
|
|
}
|
|
|
|
/*
|
|
* apply_constraint
|
|
* @req: constraint request to apply
|
|
* @action: action to perform add/update/remove, of type enum pm_qos_req_action
|
|
* @value: defines the qos request
|
|
*
|
|
* Internal function to update the constraints list using the PM QoS core
|
|
* code and if needed call the per-device and the global notification
|
|
* callbacks
|
|
*/
|
|
static int apply_constraint(struct dev_pm_qos_request *req,
|
|
enum pm_qos_req_action action, int value)
|
|
{
|
|
int ret, curr_value;
|
|
|
|
ret = pm_qos_update_target(req->dev->power.constraints,
|
|
&req->node, action, value);
|
|
|
|
if (ret) {
|
|
/* Call the global callbacks if needed */
|
|
curr_value = pm_qos_read_value(req->dev->power.constraints);
|
|
blocking_notifier_call_chain(&dev_pm_notifiers,
|
|
(unsigned long)curr_value,
|
|
req);
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
/*
|
|
* dev_pm_qos_constraints_allocate
|
|
* @dev: device to allocate data for
|
|
*
|
|
* Called at the first call to add_request, for constraint data allocation
|
|
* Must be called with the dev_pm_qos_lock lock held
|
|
*/
|
|
static int dev_pm_qos_constraints_allocate(struct device *dev)
|
|
{
|
|
struct pm_qos_constraints *c;
|
|
struct blocking_notifier_head *n;
|
|
unsigned long flags;
|
|
|
|
c = kzalloc(sizeof(*c), GFP_ATOMIC);
|
|
if (!c)
|
|
return -ENOMEM;
|
|
|
|
n = kzalloc(sizeof(*n), GFP_ATOMIC);
|
|
if (!n) {
|
|
kfree(c);
|
|
return -ENOMEM;
|
|
}
|
|
BLOCKING_INIT_NOTIFIER_HEAD(n);
|
|
|
|
plist_head_init(&c->list);
|
|
c->target_value = PM_QOS_DEV_LAT_DEFAULT_VALUE;
|
|
c->default_value = PM_QOS_DEV_LAT_DEFAULT_VALUE;
|
|
c->type = PM_QOS_MIN;
|
|
c->notifiers = n;
|
|
|
|
spin_lock_irqsave(&dev->power.lock, flags);
|
|
dev->power.constraints = c;
|
|
spin_unlock_irqrestore(&dev->power.lock, flags);
|
|
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* dev_pm_qos_constraints_init - Initalize device's PM QoS constraints pointer.
|
|
* @dev: target device
|
|
*
|
|
* Called from the device PM subsystem during device insertion under
|
|
* device_pm_lock().
|
|
*/
|
|
void dev_pm_qos_constraints_init(struct device *dev)
|
|
{
|
|
unsigned long flags;
|
|
|
|
spin_lock_irqsave(&dev_pm_qos_lock, flags);
|
|
dev->power.constraints = NULL;
|
|
dev->power.power_state = PMSG_ON;
|
|
spin_unlock_irqrestore(&dev_pm_qos_lock, flags);
|
|
}
|
|
|
|
/**
|
|
* dev_pm_qos_constraints_destroy
|
|
* @dev: target device
|
|
*
|
|
* Called from the device PM subsystem on device removal under device_pm_lock().
|
|
*/
|
|
void dev_pm_qos_constraints_destroy(struct device *dev)
|
|
{
|
|
struct dev_pm_qos_request *req, *tmp;
|
|
struct pm_qos_constraints *c;
|
|
unsigned long flags;
|
|
|
|
/*
|
|
* If the device's PM QoS resume latency limit has been exposed to user
|
|
* space, it has to be hidden at this point.
|
|
*/
|
|
dev_pm_qos_hide_latency_limit(dev);
|
|
|
|
spin_lock_irqsave(&dev_pm_qos_lock, flags);
|
|
|
|
dev->power.power_state = PMSG_INVALID;
|
|
c = dev->power.constraints;
|
|
if (!c)
|
|
goto out;
|
|
|
|
/* Flush the constraints list for the device */
|
|
plist_for_each_entry_safe(req, tmp, &c->list, node) {
|
|
/*
|
|
* Update constraints list and call the notification
|
|
* callbacks if needed
|
|
*/
|
|
apply_constraint(req, PM_QOS_REMOVE_REQ, PM_QOS_DEFAULT_VALUE);
|
|
memset(req, 0, sizeof(*req));
|
|
}
|
|
|
|
spin_lock_irq(&dev->power.lock);
|
|
dev->power.constraints = NULL;
|
|
spin_unlock_irq(&dev->power.lock);
|
|
|
|
kfree(c->notifiers);
|
|
kfree(c);
|
|
|
|
out:
|
|
spin_unlock_irqrestore(&dev_pm_qos_lock, flags);
|
|
}
|
|
|
|
/**
|
|
* dev_pm_qos_add_request - inserts new qos request into the list
|
|
* @dev: target device for the constraint
|
|
* @req: pointer to a preallocated handle
|
|
* @value: defines the qos request
|
|
*
|
|
* This function inserts a new entry in the device constraints list of
|
|
* requested qos performance characteristics. It recomputes the aggregate
|
|
* QoS expectations of parameters and initializes the dev_pm_qos_request
|
|
* handle. Caller needs to save this handle for later use in updates and
|
|
* removal.
|
|
*
|
|
* Returns 1 if the aggregated constraint value has changed,
|
|
* 0 if the aggregated constraint value has not changed,
|
|
* -EINVAL in case of wrong parameters, -ENOMEM if there's not enough memory
|
|
* to allocate for data structures, -ENODEV if the device has just been removed
|
|
* from the system.
|
|
*/
|
|
int dev_pm_qos_add_request(struct device *dev, struct dev_pm_qos_request *req,
|
|
s32 value)
|
|
{
|
|
int ret = 0;
|
|
unsigned long flags;
|
|
|
|
if (!dev || !req) /*guard against callers passing in null */
|
|
return -EINVAL;
|
|
|
|
if (WARN(dev_pm_qos_request_active(req),
|
|
"%s() called for already added request\n", __func__))
|
|
return -EINVAL;
|
|
|
|
req->dev = dev;
|
|
|
|
spin_lock_irqsave(&dev_pm_qos_lock, flags);
|
|
|
|
if (!dev->power.constraints) {
|
|
if (dev->power.power_state.event == PM_EVENT_INVALID) {
|
|
/* The device has been removed from the system. */
|
|
req->dev = NULL;
|
|
ret = -ENODEV;
|
|
goto out;
|
|
} else {
|
|
/*
|
|
* Allocate the constraints data on the first call to
|
|
* add_request, i.e. only if the data is not already
|
|
* allocated and if the device has not been removed.
|
|
*/
|
|
ret = dev_pm_qos_constraints_allocate(dev);
|
|
}
|
|
}
|
|
|
|
if (!ret)
|
|
ret = apply_constraint(req, PM_QOS_ADD_REQ, value);
|
|
|
|
out:
|
|
spin_unlock_irqrestore(&dev_pm_qos_lock, flags);
|
|
|
|
return ret;
|
|
}
|
|
EXPORT_SYMBOL_GPL(dev_pm_qos_add_request);
|
|
|
|
/**
|
|
* dev_pm_qos_update_request - modifies an existing qos request
|
|
* @req : handle to list element holding a dev_pm_qos request to use
|
|
* @new_value: defines the qos request
|
|
*
|
|
* Updates an existing dev PM qos request along with updating the
|
|
* target value.
|
|
*
|
|
* Attempts are made to make this code callable on hot code paths.
|
|
*
|
|
* Returns 1 if the aggregated constraint value has changed,
|
|
* 0 if the aggregated constraint value has not changed,
|
|
* -EINVAL in case of wrong parameters, -ENODEV if the device has been
|
|
* removed from the system
|
|
*/
|
|
int dev_pm_qos_update_request(struct dev_pm_qos_request *req,
|
|
s32 new_value)
|
|
{
|
|
int ret = 0;
|
|
unsigned long flags;
|
|
|
|
if (!req) /*guard against callers passing in null */
|
|
return -EINVAL;
|
|
|
|
if (WARN(!dev_pm_qos_request_active(req),
|
|
"%s() called for unknown object\n", __func__))
|
|
return -EINVAL;
|
|
|
|
spin_lock_irqsave(&dev_pm_qos_lock, flags);
|
|
|
|
if (req->dev->power.constraints) {
|
|
if (new_value != req->node.prio)
|
|
ret = apply_constraint(req, PM_QOS_UPDATE_REQ,
|
|
new_value);
|
|
} else {
|
|
/* Return if the device has been removed */
|
|
ret = -ENODEV;
|
|
}
|
|
|
|
spin_unlock_irqrestore(&dev_pm_qos_lock, flags);
|
|
return ret;
|
|
}
|
|
EXPORT_SYMBOL_GPL(dev_pm_qos_update_request);
|
|
|
|
/**
|
|
* dev_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. Call this on slow code paths.
|
|
*
|
|
* Returns 1 if the aggregated constraint value has changed,
|
|
* 0 if the aggregated constraint value has not changed,
|
|
* -EINVAL in case of wrong parameters, -ENODEV if the device has been
|
|
* removed from the system
|
|
*/
|
|
int dev_pm_qos_remove_request(struct dev_pm_qos_request *req)
|
|
{
|
|
int ret = 0;
|
|
unsigned long flags;
|
|
|
|
if (!req) /*guard against callers passing in null */
|
|
return -EINVAL;
|
|
|
|
if (WARN(!dev_pm_qos_request_active(req),
|
|
"%s() called for unknown object\n", __func__))
|
|
return -EINVAL;
|
|
|
|
spin_lock_irqsave(&dev_pm_qos_lock, flags);
|
|
|
|
if (req->dev->power.constraints) {
|
|
ret = apply_constraint(req, PM_QOS_REMOVE_REQ,
|
|
PM_QOS_DEFAULT_VALUE);
|
|
memset(req, 0, sizeof(*req));
|
|
} else {
|
|
/* Return if the device has been removed */
|
|
ret = -ENODEV;
|
|
}
|
|
|
|
spin_unlock_irqrestore(&dev_pm_qos_lock, flags);
|
|
return ret;
|
|
}
|
|
EXPORT_SYMBOL_GPL(dev_pm_qos_remove_request);
|
|
|
|
/**
|
|
* dev_pm_qos_add_notifier - sets notification entry for changes to target value
|
|
* of per-device PM QoS constraints
|
|
*
|
|
* @dev: target device for the constraint
|
|
* @notifier: notifier block managed by caller.
|
|
*
|
|
* Will register the notifier into a notification chain that gets called
|
|
* upon changes to the target value for the device.
|
|
*
|
|
* If the device's constraints object doesn't exist when this routine is called,
|
|
* it will be created (or error code will be returned if that fails).
|
|
*/
|
|
int dev_pm_qos_add_notifier(struct device *dev, struct notifier_block *notifier)
|
|
{
|
|
int ret = 0;
|
|
unsigned long flags;
|
|
|
|
spin_lock_irqsave(&dev_pm_qos_lock, flags);
|
|
|
|
if (!dev->power.constraints)
|
|
ret = dev->power.power_state.event != PM_EVENT_INVALID ?
|
|
dev_pm_qos_constraints_allocate(dev) : -ENODEV;
|
|
|
|
if (!ret)
|
|
ret = blocking_notifier_chain_register(
|
|
dev->power.constraints->notifiers, notifier);
|
|
|
|
spin_unlock_irqrestore(&dev_pm_qos_lock, flags);
|
|
return ret;
|
|
}
|
|
EXPORT_SYMBOL_GPL(dev_pm_qos_add_notifier);
|
|
|
|
/**
|
|
* dev_pm_qos_remove_notifier - deletes notification for changes to target value
|
|
* of per-device PM QoS constraints
|
|
*
|
|
* @dev: target device for the constraint
|
|
* @notifier: notifier block to be removed.
|
|
*
|
|
* Will remove the notifier from the notification chain that gets called
|
|
* upon changes to the target value.
|
|
*/
|
|
int dev_pm_qos_remove_notifier(struct device *dev,
|
|
struct notifier_block *notifier)
|
|
{
|
|
int retval = 0;
|
|
unsigned long flags;
|
|
|
|
spin_lock_irqsave(&dev_pm_qos_lock, flags);
|
|
|
|
/* Silently return if the constraints object is not present. */
|
|
if (dev->power.constraints)
|
|
retval = blocking_notifier_chain_unregister(
|
|
dev->power.constraints->notifiers,
|
|
notifier);
|
|
|
|
spin_unlock_irqrestore(&dev_pm_qos_lock, flags);
|
|
return retval;
|
|
}
|
|
EXPORT_SYMBOL_GPL(dev_pm_qos_remove_notifier);
|
|
|
|
/**
|
|
* dev_pm_qos_add_global_notifier - sets notification entry for changes to
|
|
* target value of the PM QoS constraints for any device
|
|
*
|
|
* @notifier: notifier block managed by caller.
|
|
*
|
|
* Will register the notifier into a notification chain that gets called
|
|
* upon changes to the target value for any device.
|
|
*/
|
|
int dev_pm_qos_add_global_notifier(struct notifier_block *notifier)
|
|
{
|
|
return blocking_notifier_chain_register(&dev_pm_notifiers, notifier);
|
|
}
|
|
EXPORT_SYMBOL_GPL(dev_pm_qos_add_global_notifier);
|
|
|
|
/**
|
|
* dev_pm_qos_remove_global_notifier - deletes notification for changes to
|
|
* target value of PM QoS constraints for any device
|
|
*
|
|
* @notifier: notifier block to be removed.
|
|
*
|
|
* Will remove the notifier from the notification chain that gets called
|
|
* upon changes to the target value for any device.
|
|
*/
|
|
int dev_pm_qos_remove_global_notifier(struct notifier_block *notifier)
|
|
{
|
|
return blocking_notifier_chain_unregister(&dev_pm_notifiers, notifier);
|
|
}
|
|
EXPORT_SYMBOL_GPL(dev_pm_qos_remove_global_notifier);
|
|
|
|
/**
|
|
* dev_pm_qos_add_ancestor_request - Add PM QoS request for device's ancestor.
|
|
* @dev: Device whose ancestor to add the request for.
|
|
* @req: Pointer to the preallocated handle.
|
|
* @value: Constraint latency value.
|
|
*/
|
|
int dev_pm_qos_add_ancestor_request(struct device *dev,
|
|
struct dev_pm_qos_request *req, s32 value)
|
|
{
|
|
struct device *ancestor = dev->parent;
|
|
int error = -ENODEV;
|
|
|
|
while (ancestor && !ancestor->power.ignore_children)
|
|
ancestor = ancestor->parent;
|
|
|
|
if (ancestor)
|
|
error = dev_pm_qos_add_request(ancestor, req, value);
|
|
|
|
if (error)
|
|
req->dev = NULL;
|
|
|
|
return error;
|
|
}
|
|
EXPORT_SYMBOL_GPL(dev_pm_qos_add_ancestor_request);
|
|
|
|
#ifdef CONFIG_PM_RUNTIME
|
|
static void __dev_pm_qos_drop_user_request(struct device *dev)
|
|
{
|
|
dev_pm_qos_remove_request(dev->power.pq_req);
|
|
dev->power.pq_req = NULL;
|
|
}
|
|
|
|
/**
|
|
* dev_pm_qos_expose_latency_limit - Expose PM QoS latency limit to user space.
|
|
* @dev: Device whose PM QoS latency limit is to be exposed to user space.
|
|
* @value: Initial value of the latency limit.
|
|
*/
|
|
int dev_pm_qos_expose_latency_limit(struct device *dev, s32 value)
|
|
{
|
|
struct dev_pm_qos_request *req;
|
|
int ret;
|
|
|
|
if (!device_is_registered(dev) || value < 0)
|
|
return -EINVAL;
|
|
|
|
if (dev->power.pq_req)
|
|
return -EEXIST;
|
|
|
|
req = kzalloc(sizeof(*req), GFP_KERNEL);
|
|
if (!req)
|
|
return -ENOMEM;
|
|
|
|
ret = dev_pm_qos_add_request(dev, req, value);
|
|
if (ret < 0)
|
|
return ret;
|
|
|
|
dev->power.pq_req = req;
|
|
ret = pm_qos_sysfs_add(dev);
|
|
if (ret)
|
|
__dev_pm_qos_drop_user_request(dev);
|
|
|
|
return ret;
|
|
}
|
|
EXPORT_SYMBOL_GPL(dev_pm_qos_expose_latency_limit);
|
|
|
|
/**
|
|
* dev_pm_qos_hide_latency_limit - Hide PM QoS latency limit from user space.
|
|
* @dev: Device whose PM QoS latency limit is to be hidden from user space.
|
|
*/
|
|
void dev_pm_qos_hide_latency_limit(struct device *dev)
|
|
{
|
|
if (dev->power.pq_req) {
|
|
pm_qos_sysfs_remove(dev);
|
|
__dev_pm_qos_drop_user_request(dev);
|
|
}
|
|
}
|
|
EXPORT_SYMBOL_GPL(dev_pm_qos_hide_latency_limit);
|
|
#endif /* CONFIG_PM_RUNTIME */
|