[linux-pm] [RFC] power trees

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



Hi All,

I've been experimenting with a power object tree for handling power
state dependencies.  Many major bus specifications fit well in a
power-state based model.  They include PCI/Cardbus, ACPI, IDE, and
probably even USB.  More complex dependencies generally rely on out of
tree elements (e.g. power planes, clocks, processor states, etc.) or are
isolated to a specific parent and its child.  Therefore, I think a power
tree would be useful for a wide range of device drivers.  Nonetheless,
the power tree is provided as an optional library only.

My implementation has two primary goals:

1.) To help device drivers track and control their device's power state

2.) To bubble power state dependency changes from a leaf node to the
last affected parent node in the chain.

The basic unit of the tree is referred to as a "pm_node".  Each
"pm_node" has a list of states it supports, a current state, and a
driver to handle state transitions.  Any "pm_node" can have children and
therefore act as a power container.

struct pm_state {
	char			*name;
	unsigned long		index;
	unsigned long		link_req;
	void			*link_data;
};

"index" is a driver specific value that indicates the state.  "link_req"
is a set of flags that indicate requirements from the parent "pm_node".
"link_data" provides additional requirement information.

struct pm_node {
	struct semaphore	sem;
	struct list_head	children;
	struct list_head	child_list;
	struct pm_node		*parent;
	struct pm_node		*tmp_path;
	struct device		*dev;

	struct pm_node_driver	*drv;
	unsigned int		current_state;
	unsigned int		target_state;

	unsigned int		num_states;
	struct pm_state		*states;
};

"states" is a list of power states sorted from most functional to least
functional.  "current_state" and "target_state" are index values for
that list.

struct pm_node_driver {
	int (*set_state) 	(struct pm_node *node);
	int (*raise_event)	(struct pm_node *domain,
				 struct pm_node *child,
				 unsigned long *new_state);
	void (*lower_event)	(struct pm_node *domain);
};

"raise_event" notifies the power domain when a child has increased its
state.  "new_state" allows for a domain to pass an index of the new
state it would need in response to the new requirements.  "lower_event"
is called when power is decreased.  It is expected to return immediately
and defer any power transitions for later (generally with a timeout
value).

I apologize for the unpolished/untested state of the code as it's
primarily intended to illustrate a concept.  I'll need to look over the
problem more carefully if we decide this is the right direction.  Power
dependency notifications are entirely iterative in this implementation.
Locking is always from leaf nodes down.  The "iterative" requirement
seems to be a little inflexible with notifications, so we may want to
start with a recursive algorithm if this proves to be too difficult.
Are there any known cases of deep power trees?  I look forward to any
comments or suggestions.

Thanks,
Adam

--- a/drivers/base/power/Makefile	2005-06-17 19:48:29.000000000 +0000
+++ b/drivers/base/power/Makefile	2005-08-09 15:52:50.000000000 +0000
@@ -1,5 +1,5 @@
 obj-y			:= shutdown.o
-obj-$(CONFIG_PM)	+= main.o suspend.o resume.o runtime.o sysfs.o
+obj-$(CONFIG_PM)	+= main.o node.o suspend.o resume.o runtime.o sysfs.o
 
 ifeq ($(CONFIG_DEBUG_DRIVER),y)
 EXTRA_CFLAGS += -DDEBUG
--- a/drivers/base/power/node.c	1970-01-01 00:00:00.000000000 +0000
+++ b/drivers/base/power/node.c	2005-08-11 03:46:46.000000000 +0000
@@ -0,0 +1,188 @@
+/*
+ * drivers/base/power/node.c - power node registration and control
+ *
+ * This file is released under the GPLv2
+ */
+
+#include <linux/config.h>
+#include <linux/device.h>
+#include <linux/module.h>
+#include "power.h"
+
+
+/*
+ * Registration
+ */
+
+/**
+ * pm_register_node - registers a power management node with the driver core
+ * @node - the node
+ */
+int pm_register_node(struct pm_node *node)
+{
+	if (!node->drv || !node->current_state)
+		return -EINVAL;
+
+	init_MUTEX(&node->sem);
+	INIT_LIST_HEAD(&node->children);
+
+	if (node->parent) {
+		down(&node->parent->sem);
+		list_add_tail(&node->child_list, &node->parent->children);
+		up(&node->parent->sem);
+	}
+
+	return 0;
+}
+
+EXPORT_SYMBOL_GPL(pm_register_node);
+
+/**
+ * pm_unregister_node - removes a power management node from the driver core
+ * @node - the node
+ */
+void pm_unregister_node(struct pm_node *node)
+{
+	if (node->parent) {
+		down(&node->parent->sem);
+		list_del(&node->child_list);
+		up(&node->parent->sem);
+	}
+}
+
+EXPORT_SYMBOL_GPL(pm_unregister_node);
+
+
+/*
+ * Power State Control
+ */
+
+static int pm_set_target(struct pm_node *node, unsigned long state_index)
+{
+	int i;
+
+	for (i = 0; i < node->num_states; i++) {
+		struct pm_state *s = &node->states[i];
+		if (s->index == state_index) {
+			node->target_state = i;
+			return 0;
+		}
+	}
+
+	return -ENODEV;
+}
+
+static void pm_recover_nodes(struct pm_node *bottom)
+{
+	struct pm_node *next, *pos = bottom;
+
+	while (pos->tmp_path) {
+		next = pos->tmp_path;
+		pos->tmp_path = NULL;
+		pos->target_state = pos->current_state;
+		up(&pos->sem);
+		pos = next;
+	}
+}
+
+static int pm_raise_nodes(struct pm_node *node)
+{
+	int ret;
+	unsigned long state_index;
+	struct pm_node *prev = node;
+	struct pm_node *pos = node->parent;
+
+	for (;;) {
+		down(&pos->sem);
+		pos->tmp_path = prev;
+
+		ret = pos->drv->raise_event(pos, prev, &state_index);
+		if (ret)
+			goto fail;
+		
+		pm_set_target(pos, state_index);
+
+		if (pos->current_state == pos->target_state) {
+			pos = prev;
+			break;
+		}
+
+		if (!pos->parent)
+			break;
+
+		prev = pos;
+		pos = pos->parent;
+	}
+
+	while (pos) {
+		ret = node->drv->set_state(node);
+		if (ret)
+			goto fail;
+
+		if (pos->parent) {
+			pos->parent->tmp_path = NULL;
+			up(&pos->parent->sem);
+		}
+
+		pos = pos->tmp_path;
+	}
+
+	return 0;
+
+fail:
+	pm_recover_nodes(pos);
+	return ret;
+}
+
+static int pm_lower_nodes(struct pm_node *node)
+{
+	int ret;
+	struct pm_node *pos = node->parent;
+
+	ret = node->drv->set_state(node);
+	if (ret)
+		return ret;
+
+	down(&pos->sem);
+	pos->drv->lower_event(pos);
+	up(&pos->sem);
+
+	return 0;
+}
+ 
+/**
+ * pm_set_state - sets the power state of a pm_node
+ * @node - the node
+ * @state - the index value of the target power state
+ */
+int pm_set_state(struct pm_node *node, unsigned long state)
+{
+	int ret;
+
+	down(&node->sem);
+
+	if (!pm_set_target(node, state)) {
+		up(&node->sem);
+		return -EINVAL;
+	}
+	
+	if (node->target_state == node->current_state) {
+		up(&node->sem);
+		return 0;
+	}
+
+	if (!node->parent)
+		ret = node->drv->set_state(node);
+	else if (node->target_state > node->current_state) 
+		ret = pm_raise_nodes(node);
+	else
+		ret = pm_lower_nodes(node);
+
+	up(&node->sem);
+	return ret;
+}
+
+EXPORT_SYMBOL_GPL(pm_set_state);
--- a/include/linux/pm.h	2005-08-09 17:49:51.000000000 +0000
+++ b/include/linux/pm.h	2005-08-11 03:16:13.000000000 +0000
@@ -14,6 +14,7 @@
 #include <linux/config.h>
 #include <linux/list.h>
 #include <asm/atomic.h>
+#include <asm/semaphore.h>
 
 #include <linux/pm_legacy.h>
 
@@ -108,6 +109,58 @@
 }
 #endif
 
+
+/*
+ * Power Nodes
+ */
+
+struct pm_state {
+	char			*name;
+	unsigned long		index;
+	unsigned long		link_req;
+	void			*link_data;
+};
+
+struct pm_node;
+
+struct pm_node_driver {
+	int (*set_state) 	(struct pm_node *node);
+	int (*raise_event)	(struct pm_node *domain,
+				 struct pm_node *child,
+				 unsigned long *new_state);
+	void (*lower_event)	(struct pm_node *domain);
+};
+
+struct pm_node {
+	struct semaphore	sem;
+	struct list_head	children;
+	struct list_head	child_list;
+	struct pm_node		*parent;
+	struct pm_node		*tmp_path;
+	struct device		*dev;
+
+	struct pm_node_driver	*drv;
+	unsigned int		current_state;
+	unsigned int		target_state;
+
+	unsigned int		num_states;
+	struct pm_state		*states;
+};
+
+static inline struct pm_state * __pm_get_state(struct pm_node *node)
+{
+	return &node->states[node->current_state];
+}
+
+static inline struct pm_state * __pm_get_target_state(struct pm_node *node)
+{
+	return &node->states[node->target_state];
+}
+
+extern int pm_register_node(struct pm_node *node);
+extern void pm_unregister_node(struct pm_node *node);
+extern int pm_set_state(struct pm_node *node, unsigned long state);
+
 #endif /* __KERNEL__ */
 
 #endif /* _LINUX_PM_H */





[Index of Archives]     [Linux ACPI]     [Netdev]     [Ethernet Bridging]     [Linux Wireless]     [CPU Freq]     [Kernel Newbies]     [Fedora Kernel]     [Security]     [Linux for Hams]     [Netfilter]     [Bugtraq]     [Yosemite News]     [MIPS Linux]     [ARM Linux]     [Linux RAID]     [Linux Admin]     [Samba]

  Powered by Linux