mirror of
https://github.com/torvalds/linux.git
synced 2024-11-21 19:46:16 +00:00
a19ce320c3
We may end up calling pwrseq_target_free() on a partially initialized
target object whose unit is either NULL or an ERR_PTR(). Avoid
dereferencing invalid memory by adding an appropriate check to
pwrseq_target_free().
Fixes: 249ebf3f65
("power: sequencing: implement the pwrseq core")
Reported-by: Dan Carpenter <dan.carpenter@linaro.org>
Closes: https://lore.kernel.org/linux-pm/62a3531e-9927-40f8-b587-254a2dfa47ef@stanley.mountain/
Reviewed-by: Dan Carpenter <dan.carpenter@linaro.org>
Link: https://lore.kernel.org/r/20240712194004.241939-1-brgl@bgdev.pl
Signed-off-by: Bartosz Golaszewski <bartosz.golaszewski@linaro.org>
1107 lines
26 KiB
C
1107 lines
26 KiB
C
// SPDX-License-Identifier: GPL-2.0-only
|
|
/*
|
|
* Copyright (C) 2024 Linaro Ltd.
|
|
*/
|
|
|
|
#include <linux/bug.h>
|
|
#include <linux/cleanup.h>
|
|
#include <linux/debugfs.h>
|
|
#include <linux/device.h>
|
|
#include <linux/err.h>
|
|
#include <linux/export.h>
|
|
#include <linux/idr.h>
|
|
#include <linux/kernel.h>
|
|
#include <linux/kref.h>
|
|
#include <linux/list.h>
|
|
#include <linux/lockdep.h>
|
|
#include <linux/module.h>
|
|
#include <linux/mutex.h>
|
|
#include <linux/property.h>
|
|
#include <linux/pwrseq/consumer.h>
|
|
#include <linux/pwrseq/provider.h>
|
|
#include <linux/radix-tree.h>
|
|
#include <linux/rwsem.h>
|
|
#include <linux/slab.h>
|
|
|
|
/*
|
|
* Power-sequencing framework for linux.
|
|
*
|
|
* This subsystem allows power sequence providers to register a set of targets
|
|
* that consumers may request and power-up/down.
|
|
*
|
|
* Glossary:
|
|
*
|
|
* Unit - a unit is a discreet chunk of a power sequence. For instance one unit
|
|
* may enable a set of regulators, another may enable a specific GPIO. Units
|
|
* can define dependencies in the form of other units that must be enabled
|
|
* before it itself can be.
|
|
*
|
|
* Target - a target is a set of units (composed of the "final" unit and its
|
|
* dependencies) that a consumer selects by its name when requesting a handle
|
|
* to the power sequencer. Via the dependency system, multiple targets may
|
|
* share the same parts of a power sequence but ignore parts that are
|
|
* irrelevant.
|
|
*
|
|
* Descriptor - a handle passed by the pwrseq core to every consumer that
|
|
* serves as the entry point to the provider layer. It ensures coherence
|
|
* between different users and keeps reference counting consistent.
|
|
*
|
|
* Each provider must define a .match() callback whose role is to determine
|
|
* whether a potential consumer is in fact associated with this sequencer.
|
|
* This allows creating abstraction layers on top of regular device-tree
|
|
* resources like regulators, clocks and other nodes connected to the consumer
|
|
* via phandle.
|
|
*/
|
|
|
|
static DEFINE_IDA(pwrseq_ida);
|
|
|
|
/*
|
|
* Protects the device list on the pwrseq bus from concurrent modifications
|
|
* but allows simultaneous read-only access.
|
|
*/
|
|
static DECLARE_RWSEM(pwrseq_sem);
|
|
|
|
/**
|
|
* struct pwrseq_unit - Private power-sequence unit data.
|
|
* @ref: Reference count for this object. When it goes to 0, the object is
|
|
* destroyed.
|
|
* @name: Name of this target.
|
|
* @list: Link to siblings on the list of all units of a single sequencer.
|
|
* @deps: List of units on which this unit depends.
|
|
* @enable: Callback running the part of the power-on sequence provided by
|
|
* this unit.
|
|
* @disable: Callback running the part of the power-off sequence provided
|
|
* by this unit.
|
|
* @enable_count: Current number of users that enabled this unit. May be the
|
|
* consumer of the power sequencer or other units that depend
|
|
* on this one.
|
|
*/
|
|
struct pwrseq_unit {
|
|
struct kref ref;
|
|
const char *name;
|
|
struct list_head list;
|
|
struct list_head deps;
|
|
pwrseq_power_state_func enable;
|
|
pwrseq_power_state_func disable;
|
|
unsigned int enable_count;
|
|
};
|
|
|
|
static struct pwrseq_unit *pwrseq_unit_new(const struct pwrseq_unit_data *data)
|
|
{
|
|
struct pwrseq_unit *unit;
|
|
|
|
unit = kzalloc(sizeof(*unit), GFP_KERNEL);
|
|
if (!unit)
|
|
return NULL;
|
|
|
|
unit->name = kstrdup_const(data->name, GFP_KERNEL);
|
|
if (!unit->name) {
|
|
kfree(unit);
|
|
return NULL;
|
|
}
|
|
|
|
kref_init(&unit->ref);
|
|
INIT_LIST_HEAD(&unit->deps);
|
|
unit->enable = data->enable;
|
|
unit->disable = data->disable;
|
|
|
|
return unit;
|
|
}
|
|
|
|
static struct pwrseq_unit *pwrseq_unit_get(struct pwrseq_unit *unit)
|
|
{
|
|
kref_get(&unit->ref);
|
|
|
|
return unit;
|
|
}
|
|
|
|
static void pwrseq_unit_release(struct kref *ref);
|
|
|
|
static void pwrseq_unit_put(struct pwrseq_unit *unit)
|
|
{
|
|
kref_put(&unit->ref, pwrseq_unit_release);
|
|
}
|
|
|
|
/**
|
|
* struct pwrseq_unit_dep - Wrapper around a reference to the unit structure
|
|
* allowing to keep it on multiple dependency lists
|
|
* in different units.
|
|
* @list: Siblings on the list.
|
|
* @unit: Address of the referenced unit.
|
|
*/
|
|
struct pwrseq_unit_dep {
|
|
struct list_head list;
|
|
struct pwrseq_unit *unit;
|
|
};
|
|
|
|
static struct pwrseq_unit_dep *pwrseq_unit_dep_new(struct pwrseq_unit *unit)
|
|
{
|
|
struct pwrseq_unit_dep *dep;
|
|
|
|
dep = kzalloc(sizeof(*dep), GFP_KERNEL);
|
|
if (!dep)
|
|
return NULL;
|
|
|
|
dep->unit = unit;
|
|
|
|
return dep;
|
|
}
|
|
|
|
static void pwrseq_unit_dep_free(struct pwrseq_unit_dep *ref)
|
|
{
|
|
pwrseq_unit_put(ref->unit);
|
|
kfree(ref);
|
|
}
|
|
|
|
static void pwrseq_unit_free_deps(struct list_head *list)
|
|
{
|
|
struct pwrseq_unit_dep *dep, *next;
|
|
|
|
list_for_each_entry_safe(dep, next, list, list) {
|
|
list_del(&dep->list);
|
|
pwrseq_unit_dep_free(dep);
|
|
}
|
|
}
|
|
|
|
static void pwrseq_unit_release(struct kref *ref)
|
|
{
|
|
struct pwrseq_unit *unit = container_of(ref, struct pwrseq_unit, ref);
|
|
|
|
pwrseq_unit_free_deps(&unit->deps);
|
|
list_del(&unit->list);
|
|
kfree_const(unit->name);
|
|
kfree(unit);
|
|
}
|
|
|
|
/**
|
|
* struct pwrseq_target - Private power-sequence target data.
|
|
* @list: Siblings on the list of all targets exposed by a power sequencer.
|
|
* @name: Name of the target.
|
|
* @unit: Final unit for this target.
|
|
* @post_enable: Callback run after the target unit has been enabled, *after*
|
|
* the state lock has been released. It's useful for implementing
|
|
* boot-up delays without blocking other users from powering up
|
|
* using the same power sequencer.
|
|
*/
|
|
struct pwrseq_target {
|
|
struct list_head list;
|
|
const char *name;
|
|
struct pwrseq_unit *unit;
|
|
pwrseq_power_state_func post_enable;
|
|
};
|
|
|
|
static struct pwrseq_target *
|
|
pwrseq_target_new(const struct pwrseq_target_data *data)
|
|
{
|
|
struct pwrseq_target *target;
|
|
|
|
target = kzalloc(sizeof(*target), GFP_KERNEL);
|
|
if (!target)
|
|
return NULL;
|
|
|
|
target->name = kstrdup_const(data->name, GFP_KERNEL);
|
|
if (!target->name) {
|
|
kfree(target);
|
|
return NULL;
|
|
}
|
|
|
|
target->post_enable = data->post_enable;
|
|
|
|
return target;
|
|
}
|
|
|
|
static void pwrseq_target_free(struct pwrseq_target *target)
|
|
{
|
|
if (!IS_ERR_OR_NULL(target->unit))
|
|
pwrseq_unit_put(target->unit);
|
|
kfree_const(target->name);
|
|
kfree(target);
|
|
}
|
|
|
|
/**
|
|
* struct pwrseq_device - Private power sequencing data.
|
|
* @dev: Device struct associated with this sequencer.
|
|
* @id: Device ID.
|
|
* @owner: Prevents removal of active power sequencing providers.
|
|
* @rw_lock: Protects the device from being unregistered while in use.
|
|
* @state_lock: Prevents multiple users running the power sequence at the same
|
|
* time.
|
|
* @match: Power sequencer matching callback.
|
|
* @targets: List of targets exposed by this sequencer.
|
|
* @units: List of all units supported by this sequencer.
|
|
*/
|
|
struct pwrseq_device {
|
|
struct device dev;
|
|
int id;
|
|
struct module *owner;
|
|
struct rw_semaphore rw_lock;
|
|
struct mutex state_lock;
|
|
pwrseq_match_func match;
|
|
struct list_head targets;
|
|
struct list_head units;
|
|
};
|
|
|
|
static struct pwrseq_device *to_pwrseq_device(struct device *dev)
|
|
{
|
|
return container_of(dev, struct pwrseq_device, dev);
|
|
}
|
|
|
|
static struct pwrseq_device *pwrseq_device_get(struct pwrseq_device *pwrseq)
|
|
{
|
|
get_device(&pwrseq->dev);
|
|
|
|
return pwrseq;
|
|
}
|
|
|
|
static void pwrseq_device_put(struct pwrseq_device *pwrseq)
|
|
{
|
|
put_device(&pwrseq->dev);
|
|
}
|
|
|
|
/**
|
|
* struct pwrseq_desc - Wraps access to the pwrseq_device and ensures that one
|
|
* user cannot break the reference counting for others.
|
|
* @pwrseq: Reference to the power sequencing device.
|
|
* @target: Reference to the target this descriptor allows to control.
|
|
* @powered_on: Power state set by the holder of the descriptor (not necessarily
|
|
* corresponding to the actual power state of the device).
|
|
*/
|
|
struct pwrseq_desc {
|
|
struct pwrseq_device *pwrseq;
|
|
struct pwrseq_target *target;
|
|
bool powered_on;
|
|
};
|
|
|
|
static const struct bus_type pwrseq_bus = {
|
|
.name = "pwrseq",
|
|
};
|
|
|
|
static void pwrseq_release(struct device *dev)
|
|
{
|
|
struct pwrseq_device *pwrseq = to_pwrseq_device(dev);
|
|
struct pwrseq_target *target, *pos;
|
|
|
|
list_for_each_entry_safe(target, pos, &pwrseq->targets, list) {
|
|
list_del(&target->list);
|
|
pwrseq_target_free(target);
|
|
}
|
|
|
|
mutex_destroy(&pwrseq->state_lock);
|
|
ida_free(&pwrseq_ida, pwrseq->id);
|
|
kfree(pwrseq);
|
|
}
|
|
|
|
static const struct device_type pwrseq_device_type = {
|
|
.name = "power_sequencer",
|
|
.release = pwrseq_release,
|
|
};
|
|
|
|
static int pwrseq_check_unit_deps(const struct pwrseq_unit_data *data,
|
|
struct radix_tree_root *visited_units)
|
|
{
|
|
const struct pwrseq_unit_data *tmp, **cur;
|
|
int ret;
|
|
|
|
ret = radix_tree_insert(visited_units, (unsigned long)data,
|
|
(void *)data);
|
|
if (ret)
|
|
return ret;
|
|
|
|
for (cur = data->deps; cur && *cur; cur++) {
|
|
tmp = radix_tree_lookup(visited_units, (unsigned long)*cur);
|
|
if (tmp) {
|
|
WARN(1, "Circular dependency in power sequencing flow detected!\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
ret = pwrseq_check_unit_deps(*cur, visited_units);
|
|
if (ret)
|
|
return ret;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int pwrseq_check_target_deps(const struct pwrseq_target_data *data)
|
|
{
|
|
struct radix_tree_root visited_units;
|
|
struct radix_tree_iter iter;
|
|
void __rcu **slot;
|
|
int ret;
|
|
|
|
if (!data->unit)
|
|
return -EINVAL;
|
|
|
|
INIT_RADIX_TREE(&visited_units, GFP_KERNEL);
|
|
ret = pwrseq_check_unit_deps(data->unit, &visited_units);
|
|
radix_tree_for_each_slot(slot, &visited_units, &iter, 0)
|
|
radix_tree_delete(&visited_units, iter.index);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int pwrseq_unit_setup_deps(const struct pwrseq_unit_data **data,
|
|
struct list_head *dep_list,
|
|
struct list_head *unit_list,
|
|
struct radix_tree_root *processed_units);
|
|
|
|
static struct pwrseq_unit *
|
|
pwrseq_unit_setup(const struct pwrseq_unit_data *data,
|
|
struct list_head *unit_list,
|
|
struct radix_tree_root *processed_units)
|
|
{
|
|
struct pwrseq_unit *unit;
|
|
int ret;
|
|
|
|
unit = radix_tree_lookup(processed_units, (unsigned long)data);
|
|
if (unit)
|
|
return pwrseq_unit_get(unit);
|
|
|
|
unit = pwrseq_unit_new(data);
|
|
if (!unit)
|
|
return ERR_PTR(-ENOMEM);
|
|
|
|
if (data->deps) {
|
|
ret = pwrseq_unit_setup_deps(data->deps, &unit->deps,
|
|
unit_list, processed_units);
|
|
if (ret) {
|
|
pwrseq_unit_put(unit);
|
|
return ERR_PTR(ret);
|
|
}
|
|
}
|
|
|
|
ret = radix_tree_insert(processed_units, (unsigned long)data, unit);
|
|
if (ret) {
|
|
pwrseq_unit_put(unit);
|
|
return ERR_PTR(ret);
|
|
}
|
|
|
|
list_add_tail(&unit->list, unit_list);
|
|
|
|
return unit;
|
|
}
|
|
|
|
static int pwrseq_unit_setup_deps(const struct pwrseq_unit_data **data,
|
|
struct list_head *dep_list,
|
|
struct list_head *unit_list,
|
|
struct radix_tree_root *processed_units)
|
|
{
|
|
const struct pwrseq_unit_data *pos;
|
|
struct pwrseq_unit_dep *dep;
|
|
struct pwrseq_unit *unit;
|
|
int i;
|
|
|
|
for (i = 0; data[i]; i++) {
|
|
pos = data[i];
|
|
|
|
unit = pwrseq_unit_setup(pos, unit_list, processed_units);
|
|
if (IS_ERR(unit))
|
|
return PTR_ERR(unit);
|
|
|
|
dep = pwrseq_unit_dep_new(unit);
|
|
if (!dep) {
|
|
pwrseq_unit_put(unit);
|
|
return -ENOMEM;
|
|
}
|
|
|
|
list_add_tail(&dep->list, dep_list);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int pwrseq_do_setup_targets(const struct pwrseq_target_data **data,
|
|
struct pwrseq_device *pwrseq,
|
|
struct radix_tree_root *processed_units)
|
|
{
|
|
const struct pwrseq_target_data *pos;
|
|
struct pwrseq_target *target;
|
|
int ret, i;
|
|
|
|
for (i = 0; data[i]; i++) {
|
|
pos = data[i];
|
|
|
|
ret = pwrseq_check_target_deps(pos);
|
|
if (ret)
|
|
return ret;
|
|
|
|
target = pwrseq_target_new(pos);
|
|
if (!target)
|
|
return -ENOMEM;
|
|
|
|
target->unit = pwrseq_unit_setup(pos->unit, &pwrseq->units,
|
|
processed_units);
|
|
if (IS_ERR(target->unit)) {
|
|
ret = PTR_ERR(target->unit);
|
|
pwrseq_target_free(target);
|
|
return ret;
|
|
}
|
|
|
|
list_add_tail(&target->list, &pwrseq->targets);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int pwrseq_setup_targets(const struct pwrseq_target_data **targets,
|
|
struct pwrseq_device *pwrseq)
|
|
{
|
|
struct radix_tree_root processed_units;
|
|
struct radix_tree_iter iter;
|
|
void __rcu **slot;
|
|
int ret;
|
|
|
|
INIT_RADIX_TREE(&processed_units, GFP_KERNEL);
|
|
ret = pwrseq_do_setup_targets(targets, pwrseq, &processed_units);
|
|
radix_tree_for_each_slot(slot, &processed_units, &iter, 0)
|
|
radix_tree_delete(&processed_units, iter.index);
|
|
|
|
return ret;
|
|
}
|
|
|
|
/**
|
|
* pwrseq_device_register() - Register a new power sequencer.
|
|
* @config: Configuration of the new power sequencing device.
|
|
*
|
|
* The config structure is only used during the call and can be freed after
|
|
* the function returns. The config structure *must* have the parent device
|
|
* as well as the match() callback and at least one target set.
|
|
*
|
|
* Returns:
|
|
* Returns the address of the new pwrseq device or ERR_PTR() on failure.
|
|
*/
|
|
struct pwrseq_device *
|
|
pwrseq_device_register(const struct pwrseq_config *config)
|
|
{
|
|
struct pwrseq_device *pwrseq;
|
|
int ret, id;
|
|
|
|
if (!config->parent || !config->match || !config->targets ||
|
|
!config->targets[0])
|
|
return ERR_PTR(-EINVAL);
|
|
|
|
pwrseq = kzalloc(sizeof(*pwrseq), GFP_KERNEL);
|
|
if (!pwrseq)
|
|
return ERR_PTR(-ENOMEM);
|
|
|
|
pwrseq->dev.type = &pwrseq_device_type;
|
|
pwrseq->dev.bus = &pwrseq_bus;
|
|
pwrseq->dev.parent = config->parent;
|
|
device_set_node(&pwrseq->dev, dev_fwnode(config->parent));
|
|
dev_set_drvdata(&pwrseq->dev, config->drvdata);
|
|
|
|
id = ida_alloc(&pwrseq_ida, GFP_KERNEL);
|
|
if (id < 0) {
|
|
kfree(pwrseq);
|
|
return ERR_PTR(id);
|
|
}
|
|
|
|
pwrseq->id = id;
|
|
|
|
/*
|
|
* From this point onwards the device's release() callback is
|
|
* responsible for freeing resources.
|
|
*/
|
|
device_initialize(&pwrseq->dev);
|
|
|
|
ret = dev_set_name(&pwrseq->dev, "pwrseq.%d", pwrseq->id);
|
|
if (ret)
|
|
goto err_put_pwrseq;
|
|
|
|
pwrseq->owner = config->owner ?: THIS_MODULE;
|
|
pwrseq->match = config->match;
|
|
|
|
init_rwsem(&pwrseq->rw_lock);
|
|
mutex_init(&pwrseq->state_lock);
|
|
INIT_LIST_HEAD(&pwrseq->targets);
|
|
INIT_LIST_HEAD(&pwrseq->units);
|
|
|
|
ret = pwrseq_setup_targets(config->targets, pwrseq);
|
|
if (ret)
|
|
goto err_put_pwrseq;
|
|
|
|
scoped_guard(rwsem_write, &pwrseq_sem) {
|
|
ret = device_add(&pwrseq->dev);
|
|
if (ret)
|
|
goto err_put_pwrseq;
|
|
}
|
|
|
|
return pwrseq;
|
|
|
|
err_put_pwrseq:
|
|
pwrseq_device_put(pwrseq);
|
|
return ERR_PTR(ret);
|
|
}
|
|
EXPORT_SYMBOL_GPL(pwrseq_device_register);
|
|
|
|
/**
|
|
* pwrseq_device_unregister() - Unregister the power sequencer.
|
|
* @pwrseq: Power sequencer to unregister.
|
|
*/
|
|
void pwrseq_device_unregister(struct pwrseq_device *pwrseq)
|
|
{
|
|
struct device *dev = &pwrseq->dev;
|
|
struct pwrseq_target *target;
|
|
|
|
scoped_guard(mutex, &pwrseq->state_lock) {
|
|
guard(rwsem_write)(&pwrseq->rw_lock);
|
|
|
|
list_for_each_entry(target, &pwrseq->targets, list)
|
|
WARN(target->unit->enable_count,
|
|
"REMOVING POWER SEQUENCER WITH ACTIVE USERS\n");
|
|
|
|
guard(rwsem_write)(&pwrseq_sem);
|
|
|
|
device_del(dev);
|
|
}
|
|
|
|
pwrseq_device_put(pwrseq);
|
|
}
|
|
EXPORT_SYMBOL_GPL(pwrseq_device_unregister);
|
|
|
|
static void devm_pwrseq_device_unregister(void *data)
|
|
{
|
|
struct pwrseq_device *pwrseq = data;
|
|
|
|
pwrseq_device_unregister(pwrseq);
|
|
}
|
|
|
|
/**
|
|
* devm_pwrseq_device_register() - Managed variant of pwrseq_device_register().
|
|
* @dev: Managing device.
|
|
* @config: Configuration of the new power sequencing device.
|
|
*
|
|
* Returns:
|
|
* Returns the address of the new pwrseq device or ERR_PTR() on failure.
|
|
*/
|
|
struct pwrseq_device *
|
|
devm_pwrseq_device_register(struct device *dev,
|
|
const struct pwrseq_config *config)
|
|
{
|
|
struct pwrseq_device *pwrseq;
|
|
int ret;
|
|
|
|
pwrseq = pwrseq_device_register(config);
|
|
if (IS_ERR(pwrseq))
|
|
return pwrseq;
|
|
|
|
ret = devm_add_action_or_reset(dev, devm_pwrseq_device_unregister,
|
|
pwrseq);
|
|
if (ret)
|
|
return ERR_PTR(ret);
|
|
|
|
return pwrseq;
|
|
}
|
|
EXPORT_SYMBOL_GPL(devm_pwrseq_device_register);
|
|
|
|
/**
|
|
* pwrseq_device_get_drvdata() - Get the driver private data associated with
|
|
* this sequencer.
|
|
* @pwrseq: Power sequencer object.
|
|
*
|
|
* Returns:
|
|
* Address of the private driver data.
|
|
*/
|
|
void *pwrseq_device_get_drvdata(struct pwrseq_device *pwrseq)
|
|
{
|
|
return dev_get_drvdata(&pwrseq->dev);
|
|
}
|
|
EXPORT_SYMBOL_GPL(pwrseq_device_get_drvdata);
|
|
|
|
struct pwrseq_match_data {
|
|
struct pwrseq_desc *desc;
|
|
struct device *dev;
|
|
const char *target;
|
|
};
|
|
|
|
static int pwrseq_match_device(struct device *pwrseq_dev, void *data)
|
|
{
|
|
struct pwrseq_device *pwrseq = to_pwrseq_device(pwrseq_dev);
|
|
struct pwrseq_match_data *match_data = data;
|
|
struct pwrseq_target *target;
|
|
int ret;
|
|
|
|
lockdep_assert_held_read(&pwrseq_sem);
|
|
|
|
guard(rwsem_read)(&pwrseq->rw_lock);
|
|
if (!device_is_registered(&pwrseq->dev))
|
|
return 0;
|
|
|
|
ret = pwrseq->match(pwrseq, match_data->dev);
|
|
if (ret <= 0)
|
|
return ret;
|
|
|
|
/* We got the matching device, let's find the right target. */
|
|
list_for_each_entry(target, &pwrseq->targets, list) {
|
|
if (strcmp(target->name, match_data->target))
|
|
continue;
|
|
|
|
match_data->desc->target = target;
|
|
}
|
|
|
|
/*
|
|
* This device does not have this target. No point in deferring as it
|
|
* will not get a new target dynamically later.
|
|
*/
|
|
if (!match_data->desc->target)
|
|
return -ENOENT;
|
|
|
|
if (!try_module_get(pwrseq->owner))
|
|
return -EPROBE_DEFER;
|
|
|
|
match_data->desc->pwrseq = pwrseq_device_get(pwrseq);
|
|
|
|
return 1;
|
|
}
|
|
|
|
/**
|
|
* pwrseq_get() - Get the power sequencer associated with this device.
|
|
* @dev: Device for which to get the sequencer.
|
|
* @target: Name of the target exposed by the sequencer this device wants to
|
|
* reach.
|
|
*
|
|
* Returns:
|
|
* New power sequencer descriptor for use by the consumer driver or ERR_PTR()
|
|
* on failure.
|
|
*/
|
|
struct pwrseq_desc *pwrseq_get(struct device *dev, const char *target)
|
|
{
|
|
struct pwrseq_match_data match_data;
|
|
int ret;
|
|
|
|
struct pwrseq_desc *desc __free(kfree) = kzalloc(sizeof(*desc),
|
|
GFP_KERNEL);
|
|
if (!desc)
|
|
return ERR_PTR(-ENOMEM);
|
|
|
|
match_data.desc = desc;
|
|
match_data.dev = dev;
|
|
match_data.target = target;
|
|
|
|
guard(rwsem_read)(&pwrseq_sem);
|
|
|
|
ret = bus_for_each_dev(&pwrseq_bus, NULL, &match_data,
|
|
pwrseq_match_device);
|
|
if (ret < 0)
|
|
return ERR_PTR(ret);
|
|
if (ret == 0)
|
|
/* No device matched. */
|
|
return ERR_PTR(-EPROBE_DEFER);
|
|
|
|
return_ptr(desc);
|
|
}
|
|
EXPORT_SYMBOL_GPL(pwrseq_get);
|
|
|
|
/**
|
|
* pwrseq_put() - Release the power sequencer descriptor.
|
|
* @desc: Descriptor to release.
|
|
*/
|
|
void pwrseq_put(struct pwrseq_desc *desc)
|
|
{
|
|
struct pwrseq_device *pwrseq;
|
|
|
|
if (!desc)
|
|
return;
|
|
|
|
pwrseq = desc->pwrseq;
|
|
|
|
if (desc->powered_on)
|
|
pwrseq_power_off(desc);
|
|
|
|
kfree(desc);
|
|
module_put(pwrseq->owner);
|
|
pwrseq_device_put(pwrseq);
|
|
}
|
|
EXPORT_SYMBOL_GPL(pwrseq_put);
|
|
|
|
static void devm_pwrseq_put(void *data)
|
|
{
|
|
struct pwrseq_desc *desc = data;
|
|
|
|
pwrseq_put(desc);
|
|
}
|
|
|
|
/**
|
|
* devm_pwrseq_get() - Managed variant of pwrseq_get().
|
|
* @dev: Device for which to get the sequencer and which also manages its
|
|
* lifetime.
|
|
* @target: Name of the target exposed by the sequencer this device wants to
|
|
* reach.
|
|
*
|
|
* Returns:
|
|
* New power sequencer descriptor for use by the consumer driver or ERR_PTR()
|
|
* on failure.
|
|
*/
|
|
struct pwrseq_desc *devm_pwrseq_get(struct device *dev, const char *target)
|
|
{
|
|
struct pwrseq_desc *desc;
|
|
int ret;
|
|
|
|
desc = pwrseq_get(dev, target);
|
|
if (IS_ERR(desc))
|
|
return desc;
|
|
|
|
ret = devm_add_action_or_reset(dev, devm_pwrseq_put, desc);
|
|
if (ret)
|
|
return ERR_PTR(ret);
|
|
|
|
return desc;
|
|
}
|
|
EXPORT_SYMBOL_GPL(devm_pwrseq_get);
|
|
|
|
static int pwrseq_unit_enable(struct pwrseq_device *pwrseq,
|
|
struct pwrseq_unit *target);
|
|
static int pwrseq_unit_disable(struct pwrseq_device *pwrseq,
|
|
struct pwrseq_unit *target);
|
|
|
|
static int pwrseq_unit_enable_deps(struct pwrseq_device *pwrseq,
|
|
struct list_head *list)
|
|
{
|
|
struct pwrseq_unit_dep *pos;
|
|
int ret = 0;
|
|
|
|
list_for_each_entry(pos, list, list) {
|
|
ret = pwrseq_unit_enable(pwrseq, pos->unit);
|
|
if (ret) {
|
|
list_for_each_entry_continue_reverse(pos, list, list)
|
|
pwrseq_unit_disable(pwrseq, pos->unit);
|
|
break;
|
|
}
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int pwrseq_unit_disable_deps(struct pwrseq_device *pwrseq,
|
|
struct list_head *list)
|
|
{
|
|
struct pwrseq_unit_dep *pos;
|
|
int ret = 0;
|
|
|
|
list_for_each_entry_reverse(pos, list, list) {
|
|
ret = pwrseq_unit_disable(pwrseq, pos->unit);
|
|
if (ret) {
|
|
list_for_each_entry_continue(pos, list, list)
|
|
pwrseq_unit_enable(pwrseq, pos->unit);
|
|
break;
|
|
}
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int pwrseq_unit_enable(struct pwrseq_device *pwrseq,
|
|
struct pwrseq_unit *unit)
|
|
{
|
|
int ret;
|
|
|
|
lockdep_assert_held_read(&pwrseq->rw_lock);
|
|
lockdep_assert_held(&pwrseq->state_lock);
|
|
|
|
if (unit->enable_count != 0) {
|
|
unit->enable_count++;
|
|
return 0;
|
|
}
|
|
|
|
ret = pwrseq_unit_enable_deps(pwrseq, &unit->deps);
|
|
if (ret) {
|
|
dev_err(&pwrseq->dev,
|
|
"Failed to enable dependencies before power-on for target '%s': %d\n",
|
|
unit->name, ret);
|
|
return ret;
|
|
}
|
|
|
|
if (unit->enable) {
|
|
ret = unit->enable(pwrseq);
|
|
if (ret) {
|
|
dev_err(&pwrseq->dev,
|
|
"Failed to enable target '%s': %d\n",
|
|
unit->name, ret);
|
|
pwrseq_unit_disable_deps(pwrseq, &unit->deps);
|
|
return ret;
|
|
}
|
|
}
|
|
|
|
unit->enable_count++;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int pwrseq_unit_disable(struct pwrseq_device *pwrseq,
|
|
struct pwrseq_unit *unit)
|
|
{
|
|
int ret;
|
|
|
|
lockdep_assert_held_read(&pwrseq->rw_lock);
|
|
lockdep_assert_held(&pwrseq->state_lock);
|
|
|
|
if (unit->enable_count == 0) {
|
|
WARN(1, "Unmatched power-off for target '%s'\n",
|
|
unit->name);
|
|
return -EBUSY;
|
|
}
|
|
|
|
if (unit->enable_count != 1) {
|
|
unit->enable_count--;
|
|
return 0;
|
|
}
|
|
|
|
if (unit->disable) {
|
|
ret = unit->disable(pwrseq);
|
|
if (ret) {
|
|
dev_err(&pwrseq->dev,
|
|
"Failed to disable target '%s': %d\n",
|
|
unit->name, ret);
|
|
return ret;
|
|
}
|
|
}
|
|
|
|
ret = pwrseq_unit_disable_deps(pwrseq, &unit->deps);
|
|
if (ret) {
|
|
dev_err(&pwrseq->dev,
|
|
"Failed to disable dependencies after power-off for target '%s': %d\n",
|
|
unit->name, ret);
|
|
if (unit->enable)
|
|
unit->enable(pwrseq);
|
|
return ret;
|
|
}
|
|
|
|
unit->enable_count--;
|
|
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* pwrseq_power_on() - Issue a power-on request on behalf of the consumer
|
|
* device.
|
|
* @desc: Descriptor referencing the power sequencer.
|
|
*
|
|
* This function tells the power sequencer that the consumer wants to be
|
|
* powered-up. The sequencer may already have powered-up the device in which
|
|
* case the function returns 0. If the power-up sequence is already in
|
|
* progress, the function will block until it's done and return 0. If this is
|
|
* the first request, the device will be powered up.
|
|
*
|
|
* Returns:
|
|
* 0 on success, negative error number on failure.
|
|
*/
|
|
int pwrseq_power_on(struct pwrseq_desc *desc)
|
|
{
|
|
struct pwrseq_device *pwrseq;
|
|
struct pwrseq_target *target;
|
|
struct pwrseq_unit *unit;
|
|
int ret;
|
|
|
|
might_sleep();
|
|
|
|
if (!desc || desc->powered_on)
|
|
return 0;
|
|
|
|
pwrseq = desc->pwrseq;
|
|
target = desc->target;
|
|
unit = target->unit;
|
|
|
|
guard(rwsem_read)(&pwrseq->rw_lock);
|
|
if (!device_is_registered(&pwrseq->dev))
|
|
return -ENODEV;
|
|
|
|
scoped_guard(mutex, &pwrseq->state_lock) {
|
|
ret = pwrseq_unit_enable(pwrseq, unit);
|
|
if (!ret)
|
|
desc->powered_on = true;
|
|
}
|
|
|
|
if (target->post_enable) {
|
|
ret = target->post_enable(pwrseq);
|
|
if (ret) {
|
|
pwrseq_unit_disable(pwrseq, unit);
|
|
desc->powered_on = false;
|
|
}
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
EXPORT_SYMBOL_GPL(pwrseq_power_on);
|
|
|
|
/**
|
|
* pwrseq_power_off() - Issue a power-off request on behalf of the consumer
|
|
* device.
|
|
* @desc: Descriptor referencing the power sequencer.
|
|
*
|
|
* This undoes the effects of pwrseq_power_on(). It issues a power-off request
|
|
* on behalf of the consumer and when the last remaining user does so, the
|
|
* power-down sequence will be started. If one is in progress, the function
|
|
* will block until it's complete and then return.
|
|
*
|
|
* Returns:
|
|
* 0 on success, negative error number on failure.
|
|
*/
|
|
int pwrseq_power_off(struct pwrseq_desc *desc)
|
|
{
|
|
struct pwrseq_device *pwrseq;
|
|
struct pwrseq_unit *unit;
|
|
int ret;
|
|
|
|
might_sleep();
|
|
|
|
if (!desc || !desc->powered_on)
|
|
return 0;
|
|
|
|
pwrseq = desc->pwrseq;
|
|
unit = desc->target->unit;
|
|
|
|
guard(rwsem_read)(&pwrseq->rw_lock);
|
|
if (!device_is_registered(&pwrseq->dev))
|
|
return -ENODEV;
|
|
|
|
guard(mutex)(&pwrseq->state_lock);
|
|
|
|
ret = pwrseq_unit_disable(pwrseq, unit);
|
|
if (!ret)
|
|
desc->powered_on = false;
|
|
|
|
return ret;
|
|
}
|
|
EXPORT_SYMBOL_GPL(pwrseq_power_off);
|
|
|
|
#if IS_ENABLED(CONFIG_DEBUG_FS)
|
|
|
|
struct pwrseq_debugfs_count_ctx {
|
|
struct device *dev;
|
|
loff_t index;
|
|
};
|
|
|
|
static int pwrseq_debugfs_seq_count(struct device *dev, void *data)
|
|
{
|
|
struct pwrseq_debugfs_count_ctx *ctx = data;
|
|
|
|
ctx->dev = dev;
|
|
|
|
return ctx->index-- ? 0 : 1;
|
|
}
|
|
|
|
static void *pwrseq_debugfs_seq_start(struct seq_file *seq, loff_t *pos)
|
|
{
|
|
struct pwrseq_debugfs_count_ctx ctx;
|
|
|
|
ctx.dev = NULL;
|
|
ctx.index = *pos;
|
|
|
|
/*
|
|
* We're holding the lock for the entire printout so no need to fiddle
|
|
* with device reference count.
|
|
*/
|
|
down_read(&pwrseq_sem);
|
|
|
|
bus_for_each_dev(&pwrseq_bus, NULL, &ctx, pwrseq_debugfs_seq_count);
|
|
if (!ctx.index)
|
|
return NULL;
|
|
|
|
return ctx.dev;
|
|
}
|
|
|
|
static void *pwrseq_debugfs_seq_next(struct seq_file *seq, void *data,
|
|
loff_t *pos)
|
|
{
|
|
struct device *curr = data;
|
|
|
|
++*pos;
|
|
|
|
struct device *next __free(put_device) =
|
|
bus_find_next_device(&pwrseq_bus, curr);
|
|
return next;
|
|
}
|
|
|
|
static void pwrseq_debugfs_seq_show_target(struct seq_file *seq,
|
|
struct pwrseq_target *target)
|
|
{
|
|
seq_printf(seq, " target: [%s] (target unit: [%s])\n",
|
|
target->name, target->unit->name);
|
|
}
|
|
|
|
static void pwrseq_debugfs_seq_show_unit(struct seq_file *seq,
|
|
struct pwrseq_unit *unit)
|
|
{
|
|
struct pwrseq_unit_dep *ref;
|
|
|
|
seq_printf(seq, " unit: [%s] - enable count: %u\n",
|
|
unit->name, unit->enable_count);
|
|
|
|
if (list_empty(&unit->deps))
|
|
return;
|
|
|
|
seq_puts(seq, " dependencies:\n");
|
|
list_for_each_entry(ref, &unit->deps, list)
|
|
seq_printf(seq, " [%s]\n", ref->unit->name);
|
|
}
|
|
|
|
static int pwrseq_debugfs_seq_show(struct seq_file *seq, void *data)
|
|
{
|
|
struct device *dev = data;
|
|
struct pwrseq_device *pwrseq = to_pwrseq_device(dev);
|
|
struct pwrseq_target *target;
|
|
struct pwrseq_unit *unit;
|
|
|
|
seq_printf(seq, "%s:\n", dev_name(dev));
|
|
|
|
seq_puts(seq, " targets:\n");
|
|
list_for_each_entry(target, &pwrseq->targets, list)
|
|
pwrseq_debugfs_seq_show_target(seq, target);
|
|
|
|
seq_puts(seq, " units:\n");
|
|
list_for_each_entry(unit, &pwrseq->units, list)
|
|
pwrseq_debugfs_seq_show_unit(seq, unit);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void pwrseq_debugfs_seq_stop(struct seq_file *seq, void *data)
|
|
{
|
|
up_read(&pwrseq_sem);
|
|
}
|
|
|
|
static const struct seq_operations pwrseq_debugfs_sops = {
|
|
.start = pwrseq_debugfs_seq_start,
|
|
.next = pwrseq_debugfs_seq_next,
|
|
.show = pwrseq_debugfs_seq_show,
|
|
.stop = pwrseq_debugfs_seq_stop,
|
|
};
|
|
DEFINE_SEQ_ATTRIBUTE(pwrseq_debugfs);
|
|
|
|
static struct dentry *pwrseq_debugfs_dentry;
|
|
|
|
#endif /* CONFIG_DEBUG_FS */
|
|
|
|
static int __init pwrseq_init(void)
|
|
{
|
|
int ret;
|
|
|
|
ret = bus_register(&pwrseq_bus);
|
|
if (ret) {
|
|
pr_err("Failed to register the power sequencer bus\n");
|
|
return ret;
|
|
}
|
|
|
|
#if IS_ENABLED(CONFIG_DEBUG_FS)
|
|
pwrseq_debugfs_dentry = debugfs_create_file("pwrseq", 0444, NULL, NULL,
|
|
&pwrseq_debugfs_fops);
|
|
#endif /* CONFIG_DEBUG_FS */
|
|
|
|
return 0;
|
|
}
|
|
subsys_initcall(pwrseq_init);
|
|
|
|
static void __exit pwrseq_exit(void)
|
|
{
|
|
#if IS_ENABLED(CONFIG_DEBUG_FS)
|
|
debugfs_remove_recursive(pwrseq_debugfs_dentry);
|
|
#endif /* CONFIG_DEBUG_FS */
|
|
|
|
bus_unregister(&pwrseq_bus);
|
|
}
|
|
module_exit(pwrseq_exit);
|
|
|
|
MODULE_AUTHOR("Bartosz Golaszewski <bartosz.golaszewski@linaro.org>");
|
|
MODULE_DESCRIPTION("Power Sequencing subsystem core");
|
|
MODULE_LICENSE("GPL");
|