[RFC v02 2/5] PowerCap: Add class driver

From: Srinivas Pandruvada
Date: Wed Aug 07 2013 - 12:15:53 EST


Added power cap class driver, which provides an API for client drivers
to use and provide a consistant sysfs interface to user mode.
For details on API refer to PowerCappingFramework.txt under
Documentation/powercap.

Signed-off-by: Srinivas Pandruvada <srinivas.pandruvada@xxxxxxxxxxxxxxx>
Signed-off-by: Jacob Pan <jacob.jun.pan@xxxxxxxxxxxxxxx>
Signed-off-by: Arjan van de Ven <arjan@xxxxxxxxxxxxxxx>
---
drivers/powercap/Kconfig | 16 +
drivers/powercap/Makefile | 5 +
drivers/powercap/powercap_sys.c | 995 ++++++++++++++++++++++++++++++++++++++++
include/linux/powercap.h | 300 ++++++++++++
4 files changed, 1316 insertions(+)
create mode 100644 drivers/powercap/Kconfig
create mode 100644 drivers/powercap/Makefile
create mode 100644 drivers/powercap/powercap_sys.c
create mode 100644 include/linux/powercap.h

diff --git a/drivers/powercap/Kconfig b/drivers/powercap/Kconfig
new file mode 100644
index 0000000..f70b7b9
--- /dev/null
+++ b/drivers/powercap/Kconfig
@@ -0,0 +1,16 @@
+#
+# Generic powercap sysfs drivers configuration
+#
+
+menuconfig POWERCAP_SUPPORT
+ tristate "Generic powercap sysfs driver"
+ help
+ A Power Capping Sysfs driver offers a generic mechanism for
+ power capping. Usually it's made up of one or more controllers,
+ power zones and constraints.
+ If you want this support, you should say Y or M here.
+
+if POWERCAP_SUPPORT
+# Add client driver config here.
+
+endif
diff --git a/drivers/powercap/Makefile b/drivers/powercap/Makefile
new file mode 100644
index 0000000..f2acfed
--- /dev/null
+++ b/drivers/powercap/Makefile
@@ -0,0 +1,5 @@
+#
+# Makefile for powercap drivers
+#
+
+obj-$(CONFIG_POWERCAP_SUPPORT) += powercap_sys.o
diff --git a/drivers/powercap/powercap_sys.c b/drivers/powercap/powercap_sys.c
new file mode 100644
index 0000000..c15c8d4
--- /dev/null
+++ b/drivers/powercap/powercap_sys.c
@@ -0,0 +1,995 @@
+/*
+ * powercap sysfs class driver
+ * Copyright (c) 2013, Intel Corporation.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU General Public License,
+ * version 2, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * this program; if not, write to the Free Software Foundation, Inc.
+ *
+ */
+
+#include <linux/module.h>
+#include <linux/device.h>
+#include <linux/err.h>
+#include <linux/slab.h>
+#include <linux/powercap.h>
+
+#define POWERCAP_CONSTRAINT_NAME_LEN 20
+#define POWERCAP_ZONE_CONSTRAINT_DEV_NAME_LEN 30
+
+/**
+ * struct powercap_zone_node- Defines a node containing power zones
+ * @next: Pointer to sibling
+ * @children_count: Number of children for this node
+ * @child: Pointer to first child
+ * @parent: Pointer to the parent
+ * @pcd_dev: pointer to power zone device in this node
+ *
+ * A power zone node to be part of a tree.
+ */
+struct powercap_zone_node {
+ struct powercap_zone_node *next;
+ int children_count;
+ struct powercap_zone_node *child;
+ struct powercap_zone_node *parent;
+ struct powercap_zone_device *pcd_dev;
+};
+
+/**
+ * struct powercap_zone_constraint_attrs - Define constraint attribute
+ * @name: Constraint attribute name.
+ * @attr: Device attribute
+ *
+ * Define each attribute, with a name, based on the constraint id.
+ */
+struct powercap_constraint_attr {
+ char name[POWERCAP_ZONE_CONSTRAINT_DEV_NAME_LEN + 1];
+ struct device_attribute attr;
+};
+
+/**
+ * struct powercap_zone_constraint- Defines instance of a constraint
+ * @id: Instance Id of this constraint.
+ * @pcd_dev: Pointer to the power zone for this constraint.
+ * @ops: Pointer to the constraint callbacks.
+ * @priv_data: Constaint private data
+ * @list: Link to other constraints for this power zone.
+ *
+ * This defines a constraint instance.
+ */
+struct powercap_zone_constraint {
+ int id;
+ struct powercap_zone_device *pcd_dev;
+ struct powercap_zone_constraint_ops *ops;
+ struct powercap_constraint_attr power_limit_attr;
+ struct powercap_constraint_attr time_window_attr;
+ struct powercap_constraint_attr max_power_attr;
+ struct powercap_constraint_attr min_power_attr;
+ struct powercap_constraint_attr max_time_window_attr;
+ struct powercap_constraint_attr min_time_window_attr;
+ struct powercap_constraint_attr name_attr;
+ struct list_head list;
+};
+
+/* A list of powercap controllers */
+static LIST_HEAD(powercap_cntrl_list);
+/* Mutex to protect list of powercap controllers */
+static DEFINE_MUTEX(powercap_cntrl_list_lock);
+
+/*
+ * Power Zone attributes: Each power zone registered with this framework
+ * contains two types of attributes:
+ * One with fixed name: E.g. energy_uj
+ * One with variable name: E.g. constraint_0_power_limit_uw
+ * Using two attribute groups, which will be used during device_register.
+ * The fixed name attributes are using static DEVICE_ATTR.
+ * For variable name device_attribute fields are initialized by assigning
+ * name (constraint_X_power_limit_uw, here X can be 0 to max integer).
+ */
+
+/* Power zone ro attributes define */
+#define powercap_attr_ro(_name) \
+ static DEVICE_ATTR(_name, 0444, show_##_name, NULL)
+
+/* Power zone rw attributes define */
+#define powercap_attr_rw(_name) \
+ static DEVICE_ATTR(_name, 0644, show_##_name, store_##_name)
+
+/* constraint attributes define rw */
+#define powercap_const_attr_rw(_name) \
+ static DEVICE_ATTR(_name, 0644, show_constraint_##_name, \
+ store_constraint_##_name)
+/* constraint attributes define ro */
+#define powercap_const_attr_ro(_name) \
+ static DEVICE_ATTR(_name, 0644, show_constraint_##_name, NULL)
+
+/* Power zone show function */
+#define define_device_show(_attr) \
+static ssize_t show_##_attr(struct device *dev, \
+ struct device_attribute *dev_attr,\
+ char *buf) \
+{ \
+ u64 value; \
+ ssize_t len = -EINVAL; \
+ struct powercap_zone_device *pcd_dev = dev_get_drvdata(dev); \
+ \
+ if (pcd_dev && pcd_dev->ops && pcd_dev->ops->get_##_attr) { \
+ mutex_lock(&pcd_dev->lock); \
+ if (!pcd_dev->ops->get_##_attr(pcd_dev, &value)) \
+ len = sprintf(buf, "%lld\n", value); \
+ mutex_unlock(&pcd_dev->lock); \
+ } \
+ \
+ return len; \
+}
+
+/* Power zone store function; only reset is possible */
+#define define_device_store(_attr) \
+static ssize_t store_##_attr(struct device *dev,\
+ struct device_attribute *dev_attr, \
+ const char *buf, size_t count) \
+{ \
+ int err; \
+ struct powercap_zone_device *pcd_dev = dev_get_drvdata(dev); \
+ u64 value; \
+ \
+ err = kstrtoull(buf, 10, &value); \
+ if (err) \
+ return -EINVAL; \
+ if (value) \
+ return -EINVAL; \
+ if (pcd_dev && pcd_dev->ops && pcd_dev->ops->reset_##_attr) { \
+ mutex_lock(&pcd_dev->lock); \
+ if (!pcd_dev->ops->reset_##_attr(pcd_dev)) { \
+ mutex_unlock(&pcd_dev->lock); \
+ return count; \
+ } \
+ mutex_unlock(&pcd_dev->lock); \
+ } \
+ \
+ return -EINVAL; \
+}
+
+/* Find constraint pointer from an ID */
+static struct powercap_zone_constraint *find_constraint(
+ struct powercap_zone_device *pcd_dev, int id)
+{
+ struct powercap_zone_constraint *pconst = NULL;
+
+ list_for_each_entry(pconst, &pcd_dev->constraint_list, list) {
+ if (pconst->id == id) {
+ return pconst;
+ break;
+ }
+ }
+
+ return pconst;
+}
+
+/* Power zone constraint show function */
+#define define_device_constraint_show(_attr) \
+static ssize_t show_constraint_##_attr(struct device *dev, \
+ struct device_attribute *dev_attr,\
+ char *buf) \
+{ \
+ u64 value; \
+ ssize_t len = -ENODATA; \
+ struct powercap_zone_device *pcd_dev = dev_get_drvdata(dev); \
+ int id; \
+ struct powercap_zone_constraint *pconst;\
+ \
+ if (!pcd_dev) \
+ return -EINVAL; \
+ if (!sscanf(dev_attr->attr.name, "constraint_%d_", &id)) \
+ return -EINVAL; \
+ mutex_lock(&pcd_dev->lock); \
+ pconst = find_constraint(pcd_dev, id); \
+ if (pconst && pconst->ops && pconst->ops->get_##_attr) { \
+ if (!pconst->ops->get_##_attr(pcd_dev, id, &value)) \
+ len = sprintf(buf, "%lld\n", value); \
+ } \
+ mutex_unlock(&pcd_dev->lock); \
+ \
+ return len; \
+}
+
+/* Power zone constraint store function */
+#define define_device_constraint_store(_attr) \
+static ssize_t store_constraint_##_attr(struct device *dev,\
+ struct device_attribute *dev_attr, \
+ const char *buf, size_t count) \
+{ \
+ int err; \
+ u64 value; \
+ struct powercap_zone_device *pcd_dev = dev_get_drvdata(dev); \
+ int id; \
+ struct powercap_zone_constraint *pconst;\
+ \
+ if (!pcd_dev) \
+ return -EINVAL; \
+ if (!sscanf(dev_attr->attr.name, "constraint_%d_", &id)) \
+ return -EINVAL; \
+ err = kstrtoull(buf, 10, &value); \
+ if (err) \
+ return -EINVAL; \
+ mutex_lock(&pcd_dev->lock); \
+ pconst = find_constraint(pcd_dev, id); \
+ if (pconst && pconst->ops && pconst->ops->set_##_attr) { \
+ if (!pconst->ops->set_##_attr(pcd_dev, id, value)) { \
+ mutex_unlock(&pcd_dev->lock); \
+ return count; \
+ } \
+ } \
+ mutex_unlock(&pcd_dev->lock); \
+ \
+ return -ENODATA; \
+}
+
+/* Power zone information callbacks */
+define_device_show(power_uw);
+define_device_store(power_uw);
+define_device_show(max_power_range_uw);
+define_device_show(energy_uj);
+define_device_store(energy_uj);
+define_device_show(max_energy_range_uj);
+
+/* Power zone attributes */
+powercap_attr_ro(max_power_range_uw);
+powercap_attr_rw(power_uw);
+powercap_attr_ro(max_energy_range_uj);
+powercap_attr_rw(energy_uj);
+
+/* Power zone constraint attributes callbacks */
+define_device_constraint_show(power_limit_uw);
+define_device_constraint_store(power_limit_uw);
+define_device_constraint_show(time_window_us);
+define_device_constraint_store(time_window_us);
+define_device_constraint_show(max_power_uw);
+define_device_constraint_show(min_power_uw);
+define_device_constraint_show(max_time_window_us);
+define_device_constraint_show(min_time_window_us);
+
+static ssize_t show_constraint_name(struct device *dev,
+ struct device_attribute *dev_attr,
+ char *buf)
+{
+ const char *name;
+ struct powercap_zone_device *pcd_dev = dev_get_drvdata(dev);
+ int id;
+ ssize_t len = -ENODATA;
+ struct powercap_zone_constraint *pconst;
+
+ if (!pcd_dev)
+ return -EINVAL;
+ if (!sscanf(dev_attr->attr.name, "constraint_%d_", &id))
+ return -EINVAL;
+ mutex_lock(&pcd_dev->lock);
+ pconst = find_constraint(pcd_dev, id);
+ if (pconst && pconst->ops && pconst->ops->get_name) {
+ name = pconst->ops->get_name(pcd_dev, id);
+ if (name) {
+ snprintf(buf, POWERCAP_CONSTRAINT_NAME_LEN,
+ "%s\n", name);
+ buf[POWERCAP_CONSTRAINT_NAME_LEN] = '\0';
+ len = strlen(buf);
+ }
+ }
+ mutex_unlock(&pcd_dev->lock);
+
+ return len;
+}
+
+static void create_constraint_attribute(struct powercap_zone_constraint *pconst,
+ const char *name,
+ int mode,
+ struct powercap_constraint_attr *attr,
+ ssize_t (*show)(struct device *,
+ struct device_attribute *, char *),
+ ssize_t (*store)(struct device *,
+ struct device_attribute *,
+ const char *, size_t)
+ )
+{
+ snprintf(attr->name, POWERCAP_ZONE_CONSTRAINT_DEV_NAME_LEN,
+ "constraint_%d_%s", pconst->id, name);
+ attr->name[POWERCAP_ZONE_CONSTRAINT_DEV_NAME_LEN] = '\0';
+ attr->attr.attr.name = attr->name;
+ attr->attr.attr.mode = mode;
+ attr->attr.show = show;
+ attr->attr.store = store;
+}
+
+/* Create a constraint attribute, if it has required call backs */
+static int create_constraints(struct powercap_zone_constraint *pconst)
+{
+ int count;
+ struct powercap_zone_device *pcd_dev;
+
+ if (!pconst->ops)
+ return -EINVAL;
+ if (!pconst->ops->get_power_limit_uw ||
+ !pconst->ops->set_power_limit_uw ||
+ !pconst->ops->get_time_window_us ||
+ !pconst->ops->set_time_window_us) {
+ return -EINVAL;
+ }
+ pcd_dev = pconst->pcd_dev;
+ count = pcd_dev->attrs.const_attr_count;
+ if (count >=
+ (POWERCAP_CONSTRAINTS_MAX_ATTRS - POWERCAP_CONSTRAINTS_ATTRS))
+ return -EINVAL;
+
+ create_constraint_attribute(pconst, "power_limit_uw",
+ S_IWUSR | S_IRUGO,
+ &pconst->power_limit_attr,
+ show_constraint_power_limit_uw,
+ store_constraint_power_limit_uw);
+ pcd_dev->attrs.const_dev_attrs[count++] =
+ &pconst->power_limit_attr.attr.attr;
+
+ create_constraint_attribute(pconst, "time_window_us",
+ S_IWUSR | S_IRUGO,
+ &pconst->time_window_attr,
+ show_constraint_time_window_us,
+ store_constraint_time_window_us);
+ pcd_dev->attrs.const_dev_attrs[count++] =
+ &pconst->time_window_attr.attr.attr;
+
+ if (pconst->ops->get_name) {
+ create_constraint_attribute(pconst, "name", S_IRUGO,
+ &pconst->name_attr,
+ show_constraint_name,
+ NULL);
+ pcd_dev->attrs.const_dev_attrs[count++] =
+ &pconst->name_attr.attr.attr;
+ }
+ if (pconst->ops->get_max_power_uw) {
+ create_constraint_attribute(pconst, "max_power_uw", S_IRUGO,
+ &pconst->max_power_attr,
+ show_constraint_max_power_uw,
+ NULL);
+ pcd_dev->attrs.const_dev_attrs[count++] =
+ &pconst->max_power_attr.attr.attr;
+ }
+ if (pconst->ops->get_min_power_uw) {
+ create_constraint_attribute(pconst, "min_power_uw", S_IRUGO,
+ &pconst->min_power_attr,
+ show_constraint_min_power_uw,
+ NULL);
+ pcd_dev->attrs.const_dev_attrs[count++] =
+ &pconst->min_power_attr.attr.attr;
+ }
+ if (pconst->ops->get_max_time_window_us) {
+ create_constraint_attribute(pconst, "max_time_window_us",
+ S_IRUGO,
+ &pconst->max_time_window_attr,
+ show_constraint_max_time_window_us,
+ NULL);
+ pcd_dev->attrs.const_dev_attrs[count++] =
+ &pconst->max_time_window_attr.attr.attr;
+ }
+ if (pconst->ops->get_min_time_window_us) {
+ create_constraint_attribute(pconst, "min_time_window_us",
+ S_IRUGO,
+ &pconst->min_time_window_attr,
+ show_constraint_min_time_window_us,
+ NULL);
+ pcd_dev->attrs.const_dev_attrs[count++] =
+ &pconst->min_time_window_attr.attr.attr;
+ }
+
+ pcd_dev->attrs.const_attr_count = count;
+
+ return 0;
+}
+
+struct powercap_zone_constraint *powercap_zone_add_constraint(
+ struct powercap_zone_device *pcd_dev,
+ struct powercap_zone_constraint_ops *ops)
+{
+ struct powercap_zone_constraint *pconst;
+ int result;
+
+ if (!pcd_dev)
+ return ERR_PTR(-EINVAL);
+
+ pconst = kzalloc(sizeof(*pconst), GFP_KERNEL);
+ if (!pconst)
+ return ERR_PTR(-ENOMEM);
+ /*
+ * No need to hold locks as this is called when device
+ * is not created or power zone is not in existance
+ */
+ pconst->pcd_dev = pcd_dev;
+ pconst->ops = ops;
+ pconst->id = pcd_dev->const_id_cnt;
+ result = create_constraints(pconst);
+ if (result) {
+ kfree(pconst);
+ return ERR_PTR(result);
+ }
+ pcd_dev->const_id_cnt++;
+ list_add_tail(&pconst->list, &pcd_dev->constraint_list);
+
+ return pconst;
+}
+
+static void delete_constraints(struct powercap_zone_device *pcd_dev)
+{
+ struct powercap_zone_constraint *p, *n;
+
+ /*
+ * No need to hold locks as this is called either when device
+ * is not created or from device_release callback
+ */
+ list_for_each_entry_safe(p, n, &pcd_dev->constraint_list, list) {
+ list_del(&p->list);
+ kfree(p);
+ }
+}
+
+static int create_constraint_attributes(struct powercap_zone_device *pcd_dev,
+ int nr_constraints,
+ struct powercap_zone_constraint_ops *const_ops)
+{
+ int i;
+ int ret = 0;
+
+ if (pcd_dev->attrs.attr_grp_cnt >= POWERCAP_MAX_ATTR_GROUPS)
+ return -ENOMEM;
+
+ for (i = 0; i < nr_constraints; ++i) {
+ if (!powercap_zone_add_constraint(pcd_dev, const_ops)) {
+ ret = -ENOMEM;
+ break;
+ }
+ }
+ if (ret) {
+ delete_constraints(pcd_dev);
+ return ret;
+ }
+ pcd_dev->attrs.const_dev_attrs[pcd_dev->attrs.const_attr_count] = NULL;
+ pcd_dev->attrs.dev_const_attr_group.attrs =
+ pcd_dev->attrs.const_dev_attrs;
+ pcd_dev->attrs.dev_attr_groups[pcd_dev->attrs.attr_grp_cnt] =
+ &pcd_dev->attrs.dev_const_attr_group;
+
+ pcd_dev->attrs.attr_grp_cnt++;
+
+ return ret;
+}
+
+/* Allocate a node of a tree */
+static struct powercap_zone_node *create_node(
+ struct powercap_zone_device *pcd_dev)
+{
+ struct powercap_zone_node *node;
+
+ node = kzalloc(sizeof(*node), GFP_KERNEL);
+ if (!node)
+ return NULL;
+
+ node->pcd_dev = pcd_dev;
+
+ return node;
+}
+
+/* Insert a node into a tree */
+static void insert_node(struct powercap_controller *ctrl,
+ struct powercap_zone_node *elem,
+ struct powercap_zone_node *parent)
+{
+ struct powercap_zone_node *node;
+
+ mutex_lock(&ctrl->node_lock);
+ if (!ctrl->root_node) {
+ ctrl->root_node = elem;
+ mutex_unlock(&ctrl->node_lock);
+ return;
+ }
+ if (!parent)
+ parent = ctrl->root_node;
+ elem->parent = parent;
+ if (!parent->child)
+ parent->child = elem;
+ else {
+ /* Not a first child */
+ node = parent->child;
+ while (node->next)
+ node = node->next;
+ node->next = elem;
+ }
+ parent->children_count++;
+ mutex_unlock(&ctrl->node_lock);
+}
+
+/*
+ * Delete a node, once node is deleted, its children nodes will be deleted.
+ * This is only deleting node, not freeing any memory for the zone.
+ * For each zone, del_callback is called, which unregister the device
+ * for the power zone. The memory is freed in the device release callback.
+ */
+static void delete_node(struct powercap_controller *ctrl,
+ struct powercap_zone_node *node,
+ void (*del_callback)(struct powercap_zone_device *))
+{
+ struct powercap_zone_node *node_store;
+ struct powercap_zone_node *node_limit = node;
+ bool root_node_delete = false;
+
+
+ mutex_lock(&ctrl->node_lock);
+
+ if (node == ctrl->root_node)
+ root_node_delete = true;
+
+ while (node) {
+ node_store = node;
+ if (node->child) {
+ node = node->child;
+ } else {
+ /* reached leaf node */
+ struct powercap_zone_node *_tnode;
+ if (node_store->pcd_dev) {
+ dev_dbg(&node_store->pcd_dev->device,
+ "Delete child %s of parent %s\n",
+ node_store->pcd_dev->name,
+ node_store->parent ?
+ node_store->parent->pcd_dev->name : "ROOT");
+ }
+ /* Point node to next sibling */
+ node = node_store->next;
+ if (!node)
+ node = node_store->parent; /* back to root */
+ /*
+ *Before the leaf is deleted, remove references from
+ *parent and siblings
+ */
+ _tnode = node_store->parent;
+ if (_tnode) {
+ _tnode->children_count--;
+ if (_tnode->child == node_store) {
+ /*very first child*/
+ _tnode->child = node_store->next;
+ } else {
+ /*Not a first child*/
+ struct powercap_zone_node *_node =
+ _tnode->child;
+ struct powercap_zone_node *_pnode =
+ _node;
+
+ while (_node != node_store) {
+ _pnode = _node;
+ _node = _node->next;
+ }
+ _pnode->next = node_store->next;
+ }
+ }
+ if (node_store->pcd_dev) {
+ /* Ready to delete */
+ (*del_callback)(node_store->pcd_dev);
+ }
+ if (node_store == node_limit) {
+ kfree(node_store);
+ break;
+ }
+ kfree(node_store); /* Leaf node is freed */
+ /* zone memory is freed in the device_release */
+ }
+ }
+ /*
+ * If the request was for root node,
+ * then whole tree is deleted
+ */
+ if (root_node_delete)
+ ctrl->root_node = NULL;
+
+ mutex_unlock(&ctrl->node_lock);
+}
+
+/* Search a tree for a controller for a zone instance */
+static bool search_node(struct powercap_controller *ctrl,
+ struct powercap_zone_device *pcd_dev)
+{
+ bool valid = true;
+ bool found = false;
+ struct powercap_zone_node *node;
+
+ mutex_lock(&ctrl->node_lock);
+
+ node = ctrl->root_node;
+
+ while (node) {
+ if (valid) {
+ if (node->pcd_dev == pcd_dev) {
+ found = true;
+ break;
+ }
+ }
+ /* First check if is node has children, then siblings */
+ if (node->child && valid) {
+ node = node->child;
+ valid = true;
+ } else if (node->next) {
+ node = node->next;
+ valid = true;
+ } else {
+ /* Reached leaf, go back to parent and traverse */
+ node = node->parent;
+ valid = false;
+ }
+ }
+ mutex_unlock(&ctrl->node_lock);
+
+ return found;
+}
+
+/* Check the presence of a controller in the controller list */
+static bool check_controller_validity(void *controller)
+{
+ struct powercap_controller *pos = NULL;
+ bool found = false;
+
+ mutex_lock(&powercap_cntrl_list_lock);
+
+ list_for_each_entry(pos, &powercap_cntrl_list, ctrl_inst) {
+ if (pos == controller) {
+ found = true;
+ break;
+ }
+ }
+ mutex_unlock(&powercap_cntrl_list_lock);
+
+ return found;
+}
+
+static ssize_t show_name(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ struct powercap_zone_device *pcd_dev = dev_get_drvdata(dev);
+
+ return sprintf(buf, "%s\n", pcd_dev->name);
+}
+
+static DEVICE_ATTR(name, 0444, show_name, NULL);
+
+/* Create zone and attributes in sysfs */
+static int create_power_zone_attributes(struct powercap_zone_device *pcd_dev)
+{
+
+ int count = 0;
+
+ pcd_dev->attrs.attr_grp_cnt = 0;
+ /**
+ * Limit is POWERCAP_ZONE_MAX_ATTRS = 10, Only adding 5 attrs.
+ * So not checking range after each addition
+ */
+ pcd_dev->attrs.zone_dev_attrs[count++] = &dev_attr_name.attr;
+ if (pcd_dev->ops->get_max_energy_range_uj)
+ pcd_dev->attrs.zone_dev_attrs[count++] =
+ &dev_attr_max_energy_range_uj.attr;
+ if (pcd_dev->ops->get_energy_uj)
+ pcd_dev->attrs.zone_dev_attrs[count++] =
+ &dev_attr_energy_uj.attr;
+ if (pcd_dev->ops->get_power_uw)
+ pcd_dev->attrs.zone_dev_attrs[count++] =
+ &dev_attr_power_uw.attr;
+ if (pcd_dev->ops->get_max_power_range_uw)
+ pcd_dev->attrs.zone_dev_attrs[count++] =
+ &dev_attr_max_power_range_uw.attr;
+ pcd_dev->attrs.zone_dev_attrs[count] = NULL;
+ pcd_dev->attrs.zone_attr_count = count;
+ pcd_dev->attrs.dev_zone_attr_group.attrs =
+ pcd_dev->attrs.zone_dev_attrs;
+ pcd_dev->attrs.dev_attr_groups[0] =
+ &pcd_dev->attrs.dev_zone_attr_group;
+ pcd_dev->attrs.attr_grp_cnt++;
+
+ return 0;
+}
+
+static void delete_zone(struct powercap_zone_device *pcd_dev)
+{
+ struct powercap_zone_constraint *p;
+
+ dev_dbg(&pcd_dev->device, "deleting %s\n", pcd_dev->name);
+ mutex_lock(&pcd_dev->lock);
+
+ list_for_each_entry(p, &pcd_dev->constraint_list, list) {
+ if (p->ops->cleanup)
+ p->ops->cleanup(pcd_dev, p->id);
+ }
+ if (pcd_dev->ops->cleanup)
+ pcd_dev->ops->cleanup(pcd_dev);
+
+ mutex_unlock(&pcd_dev->lock);
+
+ device_unregister(&pcd_dev->device);
+}
+
+static void powercap_release(struct device *dev)
+{
+ if (dev->parent) {
+ struct powercap_zone_device *pcd_dev = dev_get_drvdata(dev);
+
+ dev_dbg(dev, "powercap_release zone %s\n", pcd_dev->name);
+
+ delete_constraints(pcd_dev);
+ /* Remove id from parent idr struct */
+ idr_remove(pcd_dev->par_idr, pcd_dev->id);
+ /* Destroy idrs allocated for this zone */
+ idr_destroy(&pcd_dev->idr);
+ kfree(pcd_dev->zone_dev_name);
+ mutex_destroy(&pcd_dev->lock);
+ kfree(pcd_dev);
+ } else {
+ struct powercap_controller *instance = dev_get_drvdata(dev);
+
+ dev_dbg(dev, "powercap_release controller %s\n",
+ instance->name);
+ idr_destroy(&instance->idr);
+ mutex_destroy(&instance->node_lock);
+ kfree(instance);
+ }
+}
+
+static ssize_t type_show(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ if (dev->parent)
+ strcpy(buf, "power-zone\n");
+ else
+ strcpy(buf, "controller\n");
+
+ return strlen(buf);
+}
+
+static struct device_attribute powercap_def_attrs[] = {
+ __ATTR_RO(type),
+ __ATTR_NULL
+};
+
+static struct class powercap_class = {
+ .name = "power_cap",
+ .dev_release = powercap_release,
+ .dev_attrs = powercap_def_attrs,
+};
+
+struct powercap_zone_device *powercap_zone_register(
+ struct powercap_controller *controller,
+ const char *name,
+ struct powercap_zone_device *parent,
+ const struct powercap_zone_ops *ops,
+ int nr_constraints,
+ struct powercap_zone_constraint_ops *const_ops)
+{
+ int result;
+ struct powercap_zone_device *pcd_dev;
+ struct device *dev_ptr;
+ struct idr *idr_ptr;
+ char *parent_name;
+ int name_sz;
+
+ if (!name || !controller)
+ return ERR_PTR(-EINVAL);
+ if (strlen(name) > POWERCAP_ZONE_NAME_LENGTH)
+ return ERR_PTR(-EINVAL);
+ if (!ops)
+ return ERR_PTR(-EINVAL);
+ if (!ops->get_energy_uj && !ops->get_power_uw)
+ return ERR_PTR(-EINVAL);
+ if (!check_controller_validity(controller))
+ return ERR_PTR(-EINVAL);
+ if (parent && !search_node(controller, parent))
+ return ERR_PTR(-EINVAL);
+ pcd_dev = kzalloc(sizeof(*pcd_dev), GFP_KERNEL);
+ if (!pcd_dev)
+ return ERR_PTR(-ENOMEM);
+
+ INIT_LIST_HEAD(&pcd_dev->constraint_list);
+ mutex_init(&pcd_dev->lock);
+ pcd_dev->ops = ops;
+ pcd_dev->controller_inst = controller;
+ strncpy(pcd_dev->name, name, POWERCAP_ZONE_NAME_LENGTH);
+ pcd_dev->name[POWERCAP_ZONE_NAME_LENGTH] = '\0';
+ if (!parent) {
+ dev_ptr = &controller->device;
+ idr_ptr = &controller->idr;
+ parent_name = controller->name;
+ } else {
+ dev_ptr = &parent->device;
+ idr_ptr = &parent->idr;
+ parent_name = parent->zone_dev_name;
+ }
+ pcd_dev->par_idr = idr_ptr;
+ pcd_dev->device.class = &powercap_class;
+
+ /* allocate enough which can accommodate parent + ":" + int value */
+ name_sz = strlen(parent_name) + sizeof(int)*2 + 2;
+ pcd_dev->zone_dev_name = kmalloc(name_sz + 1, GFP_KERNEL);
+ if (!pcd_dev->zone_dev_name) {
+ result = -ENOMEM;
+ goto err_name_alloc;
+ }
+ mutex_lock(&controller->node_lock);
+ /* Using idr to get the unique id */
+ result = idr_alloc(pcd_dev->par_idr, NULL, 0, 0, GFP_KERNEL);
+ if (result < 0) {
+ mutex_unlock(&controller->node_lock);
+ goto err_idr_alloc;
+ }
+ pcd_dev->id = result;
+ idr_init(&pcd_dev->idr);
+
+ snprintf(pcd_dev->zone_dev_name, name_sz - 1, "%s:%x",
+ parent_name, pcd_dev->id);
+ pcd_dev->zone_dev_name[name_sz] = '\0';
+ dev_set_name(&pcd_dev->device, pcd_dev->zone_dev_name);
+ pcd_dev->device.parent = dev_ptr;
+
+ create_power_zone_attributes(pcd_dev);
+ result = create_constraint_attributes(pcd_dev, nr_constraints,
+ const_ops);
+ if (result) {
+ idr_remove(pcd_dev->par_idr, pcd_dev->id);
+ mutex_unlock(&controller->node_lock);
+ goto err_dev_reg;
+ }
+ pcd_dev->attrs.dev_attr_groups[pcd_dev->attrs.attr_grp_cnt] = NULL;
+ pcd_dev->device.groups = pcd_dev->attrs.dev_attr_groups;
+
+ result = device_register(&pcd_dev->device);
+ if (result) {
+ delete_constraints(pcd_dev);
+ idr_remove(pcd_dev->par_idr, pcd_dev->id);
+ mutex_unlock(&controller->node_lock);
+ goto err_dev_reg;
+ }
+ mutex_unlock(&controller->node_lock);
+
+ dev_set_drvdata(&pcd_dev->device, pcd_dev);
+ pcd_dev->node = create_node(pcd_dev);
+ if (!pcd_dev->node) {
+ result = -ENOMEM;
+ goto err_dev_reg_done;
+ }
+ if (parent)
+ insert_node(controller, pcd_dev->node, parent->node);
+ else
+ insert_node(controller, pcd_dev->node, NULL);
+
+ return pcd_dev;
+
+err_dev_reg:
+ idr_destroy(&pcd_dev->idr);
+err_idr_alloc:
+ kfree(pcd_dev->zone_dev_name);
+err_name_alloc:
+ mutex_destroy(&pcd_dev->lock);
+ kfree(pcd_dev);
+ return ERR_PTR(result);
+
+err_dev_reg_done:
+ device_unregister(&pcd_dev->device);
+ return ERR_PTR(result);
+}
+EXPORT_SYMBOL_GPL(powercap_zone_register);
+
+int powercap_zone_unregister(struct powercap_controller *controller,
+ struct powercap_zone_device *pcd_dev)
+{
+ if (!pcd_dev || !controller)
+ return -EINVAL;
+
+ if (!search_node(controller, pcd_dev))
+ return -EINVAL;
+
+ delete_node(controller, pcd_dev->node, delete_zone);
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(powercap_zone_unregister);
+
+struct powercap_controller *powercap_allocate_controller(
+ const char *controller_name)
+{
+ struct powercap_controller *cntrl;
+ int result;
+
+ if (!controller_name)
+ return ERR_PTR(-EINVAL);
+
+ if (strlen(controller_name) > POWERCAP_CTRL_NAME_LENGTH)
+ return ERR_PTR(-EINVAL);
+
+ cntrl = kzalloc(sizeof(struct powercap_controller), GFP_KERNEL);
+ if (!cntrl)
+ return ERR_PTR(-ENOMEM);
+
+ mutex_init(&cntrl->node_lock);
+ INIT_LIST_HEAD(&cntrl->ctrl_inst);
+ strncpy(cntrl->name, controller_name, POWERCAP_CTRL_NAME_LENGTH);
+ cntrl->name[POWERCAP_CTRL_NAME_LENGTH] = '\0';
+ cntrl->device.class = &powercap_class;
+ dev_set_name(&cntrl->device, cntrl->name);
+ result = device_register(&cntrl->device);
+ if (result) {
+ kfree(cntrl);
+ return ERR_PTR(result);
+ }
+ dev_set_drvdata(&cntrl->device, cntrl);
+
+ cntrl->root_node = create_node(NULL);
+ if (!cntrl->root_node) {
+ result = -ENOMEM;
+ goto unregister;
+ }
+ idr_init(&cntrl->idr);
+ mutex_lock(&powercap_cntrl_list_lock);
+ list_add_tail(&cntrl->ctrl_inst, &powercap_cntrl_list);
+ mutex_unlock(&powercap_cntrl_list_lock);
+
+ return cntrl;
+
+unregister:
+ device_unregister(&cntrl->device);
+ return ERR_PTR(result);
+}
+EXPORT_SYMBOL_GPL(powercap_allocate_controller);
+
+void powercap_deallocate_controller(struct powercap_controller *instance)
+{
+ struct powercap_controller *pos = NULL;
+
+ mutex_lock(&powercap_cntrl_list_lock);
+
+ list_for_each_entry(pos, &powercap_cntrl_list, ctrl_inst) {
+ if (pos == instance)
+ break;
+ }
+ if (pos != instance) {
+ /* instance not found */
+ mutex_unlock(&powercap_cntrl_list_lock);
+ return;
+ }
+ list_del(&instance->ctrl_inst);
+ delete_node(instance, instance->root_node, delete_zone);
+
+ mutex_unlock(&powercap_cntrl_list_lock);
+
+ device_unregister(&instance->device);
+}
+EXPORT_SYMBOL_GPL(powercap_deallocate_controller);
+
+static int __init powercap_init(void)
+{
+ int result = 0;
+
+ result = class_register(&powercap_class);
+ if (result)
+ return result;
+
+ return result;
+}
+
+static void __exit powercap_exit(void)
+{
+ class_unregister(&powercap_class);
+}
+
+fs_initcall(powercap_init);
+module_exit(powercap_exit);
+
+MODULE_DESCRIPTION("PowerCap sysfs Driver");
+MODULE_AUTHOR("Srinivas Pandruvada <srinivas.pandruvada@xxxxxxxxxxxxxxx>");
+MODULE_LICENSE("GPL v2");
diff --git a/include/linux/powercap.h b/include/linux/powercap.h
new file mode 100644
index 0000000..9967bd0
--- /dev/null
+++ b/include/linux/powercap.h
@@ -0,0 +1,300 @@
+/*
+ * powercap.h : Exports all power class sysfs interface
+ * Copyright (c) 2013, Intel Corporation.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU General Public License,
+ * version 2, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * this program; if not, write to the Free Software Foundation, Inc.
+ *
+ */
+
+#ifndef __POWERCAP_H__
+#define __POWERCAP_H__
+
+#include <linux/device.h>
+#include <linux/idr.h>
+
+/*
+ * A power cap class device can contain multiple powercap controllers.
+ * Each controller can have multiple power zones, which can be independently
+ * controlled. Each power zone can have one or more constraints.
+ */
+
+#define POWERCAP_CTRL_NAME_LENGTH 30
+#define POWERCAP_ZONE_NAME_LENGTH 30
+
+struct powercap_zone_device;
+struct powercap_zone_constraint;
+
+/**
+ * struct powercap_zone_constraint_ops - Define constraint callbacks
+ * @set_power_limit_uw: Set power limit in micro-watts.
+ * @get_power_limit_uw: Get power limit in micro-watts.
+ * @set_time_window_us: Set time window in micro-seconds.
+ * @get_time_window_us: Get time window in micro-seconds.
+ * @get_max_power_uw: Get max power allowed in micro-watts.
+ * @get_min_power_uw: Get min power allowed in micro-watts.
+ * @get_max_time_window_us: Get max time window allowed in micro-seconds.
+ * @get_min_time_window_us: Get min time window allowed in micro-seconds.
+ * @get_name: Get the name of constraint
+ * @cleanup: Do any clean up before the constraint is freed
+ * This structure is used to define the constraint callbacks for the client
+ * drivers. The following callbacks are mandatory and can't be NULL:
+ * set_power_limit_uw
+ * get_power_limit_uw
+ * set_time_window_us
+ * get_time_window_us
+ * get_name
+ */
+struct powercap_zone_constraint_ops {
+ int (*set_power_limit_uw)
+ (struct powercap_zone_device *, int, u64);
+ int (*get_power_limit_uw)
+ (struct powercap_zone_device *, int, u64 *);
+
+ int (*set_time_window_us)
+ (struct powercap_zone_device *, int, u64);
+ int (*get_time_window_us)
+ (struct powercap_zone_device *, int, u64 *);
+
+ int (*get_max_power_uw)
+ (struct powercap_zone_device *, int, u64 *);
+ int (*get_min_power_uw)
+ (struct powercap_zone_device *, int, u64 *);
+
+ int (*get_max_time_window_us)
+ (struct powercap_zone_device *, int, u64 *);
+ int (*get_min_time_window_us)
+ (struct powercap_zone_device *, int, u64 *);
+ const char *(*get_name) (struct powercap_zone_device *, int);
+ void (*cleanup) (struct powercap_zone_device *, int);
+};
+
+/**
+ * struct powercap_controller- Defines a powercap controller
+ * @name: name of controller
+ * @device: device for this controller
+ * @idr : idr to have unique id for its child
+ * @root_node: Root holding power zones for this controller
+ * @node_lock: mutex for node
+ * @ctrl_inst: link to the controller list
+ *
+ * Defines powercap controller instance
+ */
+struct powercap_controller {
+ char name[POWERCAP_CTRL_NAME_LENGTH + 1];
+ struct device device;
+ struct idr idr;
+ void *root_node;
+ struct mutex node_lock;
+ struct list_head ctrl_inst;
+};
+
+/**
+ * struct powercap_zone_ops - Define power zone callbacks
+ * @get_max_energy_range_uj: Get maximum range of energy counter in
+ * micro-joules.
+ * @get_energy_uj: Get current energy counter in micro-joules.
+ * @reset_energy_uj: Reset micro-joules energy counter.
+ * @get_max_power_range_uw: Get maximum range of power counter in
+ * micro-watts.
+ * @get_power_uw: Get current power counter in micro-watts.
+ * @reset_power_uw: Reset micro-watts power counter.
+ * @cleanup: Do any clean up before the zone is freed
+ *
+ * This structure defines zone callbacks to be implemented by client drivers.
+ * Client drives can define both energy and power related callbacks. But at
+ * the least one type (either power or energy) is mandatory.
+ */
+struct powercap_zone_ops {
+ int (*get_max_energy_range_uj)
+ (struct powercap_zone_device *, u64 *);
+ int (*get_energy_uj)
+ (struct powercap_zone_device *, u64 *);
+ int (*reset_energy_uj)
+ (struct powercap_zone_device *);
+
+ int (*get_max_power_range_uw)
+ (struct powercap_zone_device *, u64 *);
+ int (*get_power_uw)
+ (struct powercap_zone_device *, u64 *);
+ int (*reset_power_uw) (struct powercap_zone_device *);
+
+ void (*cleanup) (struct powercap_zone_device *);
+};
+
+#define POWERCAP_ZONE_MAX_ATTRS 10 /* Currently only max 5 */
+#define POWERCAP_CONSTRAINTS_ATTRS 8 /* 5 attrs/constraints */
+#define POWERCAP_CONSTRAINTS_MAX_ATTRS 10 * POWERCAP_CONSTRAINTS_ATTRS
+ /* For 10 constraints per zone */
+#define POWERCAP_MAX_ATTR_GROUPS 2 /* One for zone and constraint */
+/**
+ * struct powercap_zone_attr- Defines a per zone attribute group
+ * @zone_dev_attrs: Device attribute list for power zone.
+ * @zone_attr_count: Number of power zone attributes.
+ * @const_dev_attrs: Constraint attributes.
+ * @const_attr_count: Number of constraint related attributes
+ * @dev_zone_attr_group: Attribute group for power zone attributes
+ * @dev_const_attr_group: Attribute group for constraints
+ * @attr_grp_cnt: Number of attribute groups
+ * @dev_attr_groups: Used to assign to dev->group
+ *
+ * Used to add an attribute group unique to a zone based on registry.
+ */
+struct powercap_zone_attr {
+ struct attribute *zone_dev_attrs[POWERCAP_ZONE_MAX_ATTRS];
+ int zone_attr_count;
+ struct attribute *const_dev_attrs[POWERCAP_CONSTRAINTS_MAX_ATTRS];
+ int const_attr_count;
+ struct attribute_group dev_zone_attr_group;
+ struct attribute_group dev_const_attr_group;
+ int attr_grp_cnt;
+ const struct attribute_group
+ *dev_attr_groups[POWERCAP_MAX_ATTR_GROUPS + 1];
+};
+
+/**
+ * struct powercap_zone_device- Defines instance of a power cap zone
+ * @id: Unique id
+ * @zone_dev_name: Zone device sysfs node name
+ * @name: Power zone name.
+ * @controller_inst: Controller instance for this zone
+ * @ops: Pointer to the zone operation structure.
+ * @device: Instance of a device.
+ * @attrs: Attributes associated with this device
+ * @node: Node pointer to insert to a tree data structure.
+ * @const_id_cnt: Constraint id count
+ * @lock: Mutex to protect zone related operations.
+ * @idr: Instance to an idr entry for children zones.
+ * @par_idr: To remove reference from the parent idr
+ * @drv_data: Driver private data
+ * @constraint_list: Link to the next power zone for this controller.
+ *
+ * This defines a power zone instance. The fields of this structure are
+ * private, and should not be used by client drivers.
+ */
+struct powercap_zone_device {
+ int id;
+ char *zone_dev_name;
+ char name[POWERCAP_ZONE_NAME_LENGTH + 1];
+ void *controller_inst;
+ const struct powercap_zone_ops *ops;
+ struct device device;
+ struct powercap_zone_attr attrs;
+ void *node;
+ int const_id_cnt;
+ struct mutex lock;
+ struct idr idr;
+ struct idr *par_idr;
+ void *drv_data;
+ struct list_head constraint_list;
+};
+
+/* For clients to get their device pointer, may be used for dev_dbgs */
+#define POWERCAP_GET_DEV(p_zone) (&pzone->device)
+
+/**
+* powercap_set_zone_data() - Set private data for a zone
+* @pcd_dev: A pointer to the valid zone instance.
+* @pdata: A pointer to the user private data.
+*
+* Allows client drivers to associate some private data to zone instance.
+*/
+static inline void powercap_set_zone_data(struct powercap_zone_device *pcd_dev,
+ void *pdata)
+{
+ if (pcd_dev)
+ pcd_dev->drv_data = pdata;
+}
+
+/**
+* powercap_get_zone_data() - Get private data for a zone
+* @pcd_dev: A pointer to the valid zone instance.
+*
+* Allows client drivers to get private data associate with a zone,
+* using call to powercap_set_zone_data.
+*/
+static inline void *powercap_get_zone_data(struct powercap_zone_device *pcd_dev)
+{
+ if (pcd_dev)
+ return pcd_dev->drv_data;
+ return NULL;
+}
+
+/* Controller allocate/deallocate API */
+
+/**
+* powercap_allocate_controller() - Allocates a controller
+* @controller_name: The Name of this controller, which will be shown
+* in the sysfs Interface.
+*
+* Used to create a controller with the power capping class. Here controller
+* can represent a type of technology, which can control a range of power zones.
+* For example a controller can be RAPL (Running Average Power Limit)
+* Intel 64 and IA-32 Processor Architectures. The name can be any string
+* which must be unique, otherwise this function returns NULL.
+* On successful allocation, this API returns a pointer to the
+* controller instance.
+*/
+struct powercap_controller *powercap_allocate_controller(
+ const char *controller_name);
+
+/**
+* powercap_deallocate_controller() - Deallocates a controller
+* @instance: A pointer to the valid controller instance.
+*
+* Used to deallocate a controller with the power capping class. This
+* takes only one argument, which is the pointer to the instance returned
+* by a call to powercap_allocate_controller.
+* When a controller is deallocated, all zones and associated constraints
+* are freed.
+*/
+void powercap_deallocate_controller(struct powercap_controller *instance);
+
+/* Zone register/unregister API */
+
+/**
+* powercap_zone_register() - Register a power zone
+* @controller: A controller instance under which this zone operates.
+* @name: A name for this zone.
+* @parent: A pointer to the parent power zone instance if any or NULL
+* @ops: Pointer to zone operation callback structure.
+* @no_constraints: Number of constraints for this zone
+* @const_ops: Pointer to constraint callback structure
+*
+* Used to register a power zone for a controller. Zones are organized in
+* a tree like structure in sysfs under a controller.
+* A power zone must a register a pointer to a structure representing zone
+* callbacks.
+* A power zone can have a some other power zone as a parent or it can be
+* NULL to appear as a direct descendant of a controller.
+* Each power zone can have number of constraints. Constraints appears
+* under zones as attributes with unique id.
+*/
+struct powercap_zone_device *powercap_zone_register(
+ struct powercap_controller *controller,
+ const char *name,
+ struct powercap_zone_device *parent,
+ const struct powercap_zone_ops *ops,
+ int no_constraints,
+ struct powercap_zone_constraint_ops *const_ops);
+/**
+* powercap_zone_unregister() - Unregister a zone device
+* @controller: A pointer to the valid instance of a controller.
+* @pcd_dev: A pointer to the valid zone instance for a controller
+*
+* Used to unregister a zone device for a controller. Once a zone is
+* unregistered then all its children and associated constraints are freed.
+*/
+int powercap_zone_unregister(struct powercap_controller *controller,
+ struct powercap_zone_device *pcd_dev);
+
+#endif
--
1.8.3.1

--
To unsubscribe from this list: send the line "unsubscribe linux-kernel" in
the body of a message to majordomo@xxxxxxxxxxxxxxx
More majordomo info at http://vger.kernel.org/majordomo-info.html
Please read the FAQ at http://www.tux.org/lkml/