All of lore.kernel.org
 help / color / mirror / Atom feed
* [PATCH 0/2] ACPI 4.0 power meter
@ 2009-07-25  0:43 ` Darrick J. Wong
  0 siblings, 0 replies; 38+ messages in thread
From: Darrick J. Wong @ 2009-07-25  0:43 UTC (permalink / raw)
  To: Darrick J. Wong, Len Brown, Andrew Morton
  Cc: linux-kernel, lm-sensors, linux-acpi, Zhang Rui

Hi all,

This is a two-part patchset.  The first patch extends the hwmon sysfs interface
to include power capping features and extra data about the accuracy and
internal operation of the power meter.  The second patch implements a hwmon
driver for ACPI 4.0 compliant power meters that uses the expanded sysfs
interface.  I don't have an example DSDT from any actual piece of hardware, but
I can send some sample ASL code that I've been using to outfit kvm with a power
meter if anyone requests it.

(I suppose it could be integrated into the kernel source tree as some sort of
test case?)

--D

^ permalink raw reply	[flat|nested] 38+ messages in thread

* [lm-sensors] [PATCH 0/2] ACPI 4.0 power meter
@ 2009-07-25  0:43 ` Darrick J. Wong
  0 siblings, 0 replies; 38+ messages in thread
From: Darrick J. Wong @ 2009-07-25  0:43 UTC (permalink / raw)
  To: Darrick J. Wong, Len Brown, Andrew Morton
  Cc: linux-kernel, lm-sensors, linux-acpi, Zhang Rui

Hi all,

This is a two-part patchset.  The first patch extends the hwmon sysfs interface
to include power capping features and extra data about the accuracy and
internal operation of the power meter.  The second patch implements a hwmon
driver for ACPI 4.0 compliant power meters that uses the expanded sysfs
interface.  I don't have an example DSDT from any actual piece of hardware, but
I can send some sample ASL code that I've been using to outfit kvm with a power
meter if anyone requests it.

(I suppose it could be integrated into the kernel source tree as some sort of
test case?)

--D

_______________________________________________
lm-sensors mailing list
lm-sensors@lm-sensors.org
http://lists.lm-sensors.org/mailman/listinfo/lm-sensors

^ permalink raw reply	[flat|nested] 38+ messages in thread

* [PATCH 1/2] hwmon: Enhance the sysfs API for power meters.
  2009-07-25  0:43 ` [lm-sensors] " Darrick J. Wong
@ 2009-07-25  0:43   ` Darrick J. Wong
  -1 siblings, 0 replies; 38+ messages in thread
From: Darrick J. Wong @ 2009-07-25  0:43 UTC (permalink / raw)
  To: Darrick J. Wong, Len Brown, Andrew Morton
  Cc: linux-kernel, lm-sensors, linux-acpi, Zhang Rui

Augment the documentation of the hwmon sysfs API to accomodate ACPI power
meters.

Signed-off-by: Darrick J. Wong <djwong@us.ibm.com>
---
 Documentation/hwmon/sysfs-interface |   44 +++++++++++++++++++++++++++++++++++
 1 files changed, 44 insertions(+), 0 deletions(-)


diff --git a/Documentation/hwmon/sysfs-interface b/Documentation/hwmon/sysfs-interface
index dcbd502..e196701 100644
--- a/Documentation/hwmon/sysfs-interface
+++ b/Documentation/hwmon/sysfs-interface
@@ -357,6 +357,14 @@ power[1-*]_average_interval	Power use averaging interval
 				Unit: milliseconds
 				RW
 
+power[1-*]_average_interval_max	Maximum power use averaging interval
+				Unit: milliseconds
+				RO
+
+power[1-*]_average_interval_min	Minimum power use averaging interval
+				Unit: milliseconds
+				RO
+
 power[1-*]_average_highest	Historical average maximum power use
 				Unit: microWatt
 				RO
@@ -365,6 +373,16 @@ power[1-*]_average_lowest	Historical average minimum power use
 				Unit: microWatt
 				RO
 
+power[1-*]_average_max		A notification is sent when power use
+				rises above this value.
+				Unit: microWatt
+				RW
+
+power[1-*]_average_min		A notification is sent when power use
+				sinks below this value.
+				Unit: microWatt
+				RW
+
 power[1-*]_input		Instantaneous power use
 				Unit: microWatt
 				RO
@@ -381,6 +399,32 @@ power[1-*]_reset_history	Reset input_highest, input_lowest,
 				average_highest and average_lowest.
 				WO
 
+power[1-*]_accuracy		Accuracy of the power meter.
+				Unit: Percent
+				RO
+
+power[1-*]_alarm		1 if the system is drawing more power than the
+				cap allows; 0 otherwise.
+				RO
+
+power[1-*]_cap			If power use rises above this limit, the
+				system should take action to reduce power use.
+				Unit: microWatt
+				RW
+
+power[1-*]_cap_hyst		Margin of hysteresis built around capping and
+				notification.
+				Unit: microWatt
+				RW
+
+power[1-*]_cap_max		Maximum cap that can be set.
+				Unit: microWatt
+				RO
+
+power[1-*]_cap_min		Minimum cap that can be set.
+				Unit: microWatt
+				RO
+
 **********
 * Energy *
 **********

^ permalink raw reply related	[flat|nested] 38+ messages in thread

* [lm-sensors] [PATCH 1/2] hwmon: Enhance the sysfs API for power
@ 2009-07-25  0:43   ` Darrick J. Wong
  0 siblings, 0 replies; 38+ messages in thread
From: Darrick J. Wong @ 2009-07-25  0:43 UTC (permalink / raw)
  To: Darrick J. Wong, Len Brown, Andrew Morton
  Cc: linux-kernel, lm-sensors, linux-acpi, Zhang Rui

Augment the documentation of the hwmon sysfs API to accomodate ACPI power
meters.

Signed-off-by: Darrick J. Wong <djwong@us.ibm.com>
---
 Documentation/hwmon/sysfs-interface |   44 +++++++++++++++++++++++++++++++++++
 1 files changed, 44 insertions(+), 0 deletions(-)


diff --git a/Documentation/hwmon/sysfs-interface b/Documentation/hwmon/sysfs-interface
index dcbd502..e196701 100644
--- a/Documentation/hwmon/sysfs-interface
+++ b/Documentation/hwmon/sysfs-interface
@@ -357,6 +357,14 @@ power[1-*]_average_interval	Power use averaging interval
 				Unit: milliseconds
 				RW
 
+power[1-*]_average_interval_max	Maximum power use averaging interval
+				Unit: milliseconds
+				RO
+
+power[1-*]_average_interval_min	Minimum power use averaging interval
+				Unit: milliseconds
+				RO
+
 power[1-*]_average_highest	Historical average maximum power use
 				Unit: microWatt
 				RO
@@ -365,6 +373,16 @@ power[1-*]_average_lowest	Historical average minimum power use
 				Unit: microWatt
 				RO
 
+power[1-*]_average_max		A notification is sent when power use
+				rises above this value.
+				Unit: microWatt
+				RW
+
+power[1-*]_average_min		A notification is sent when power use
+				sinks below this value.
+				Unit: microWatt
+				RW
+
 power[1-*]_input		Instantaneous power use
 				Unit: microWatt
 				RO
@@ -381,6 +399,32 @@ power[1-*]_reset_history	Reset input_highest, input_lowest,
 				average_highest and average_lowest.
 				WO
 
+power[1-*]_accuracy		Accuracy of the power meter.
+				Unit: Percent
+				RO
+
+power[1-*]_alarm		1 if the system is drawing more power than the
+				cap allows; 0 otherwise.
+				RO
+
+power[1-*]_cap			If power use rises above this limit, the
+				system should take action to reduce power use.
+				Unit: microWatt
+				RW
+
+power[1-*]_cap_hyst		Margin of hysteresis built around capping and
+				notification.
+				Unit: microWatt
+				RW
+
+power[1-*]_cap_max		Maximum cap that can be set.
+				Unit: microWatt
+				RO
+
+power[1-*]_cap_min		Minimum cap that can be set.
+				Unit: microWatt
+				RO
+
 **********
 * Energy *
 **********


_______________________________________________
lm-sensors mailing list
lm-sensors@lm-sensors.org
http://lists.lm-sensors.org/mailman/listinfo/lm-sensors

^ permalink raw reply related	[flat|nested] 38+ messages in thread

* [PATCH 2/2] acpi_power_meter: hwmon driver for ACPI 4.0 power meters
  2009-07-25  0:43 ` [lm-sensors] " Darrick J. Wong
@ 2009-07-25  0:43   ` Darrick J. Wong
  -1 siblings, 0 replies; 38+ messages in thread
From: Darrick J. Wong @ 2009-07-25  0:43 UTC (permalink / raw)
  To: Darrick J. Wong, Len Brown, Andrew Morton
  Cc: linux-kernel, lm-sensors, linux-acpi, Zhang Rui

This driver exposes ACPI 4.0 compliant power meters as hardware monitoring
devices.

Signed-off-by: Darrick J. Wong <djwong@us.ibm.com>
---
 Documentation/hwmon/acpi_power_meter |   25 +
 drivers/acpi/Kconfig                 |   12 +
 drivers/acpi/Makefile                |    1 
 drivers/acpi/power_meter.c           |  776 ++++++++++++++++++++++++++++++++++
 4 files changed, 814 insertions(+), 0 deletions(-)
 create mode 100644 Documentation/hwmon/acpi_power_meter
 create mode 100644 drivers/acpi/power_meter.c


diff --git a/Documentation/hwmon/acpi_power_meter b/Documentation/hwmon/acpi_power_meter
new file mode 100644
index 0000000..0af2e9a
--- /dev/null
+++ b/Documentation/hwmon/acpi_power_meter
@@ -0,0 +1,25 @@
+Kernel driver power_meter
+=========================
+
+This driver talks to ACPI 4.0 power meters.
+
+Supported systems:
+  * Any recent system with ACPI 4.0.
+    Prefix: 'power_meter'
+    Datasheet: http://acpi.info/, section 10.4.
+
+Author: Darrick J. Wong
+
+Description
+-----------
+
+This driver implements sensor reading support for the power meters exposed in
+the ACPI 4.0 spec (Chapter 10.4).  These devices have a simple set of
+features--a power meter that returns average power use over a configurable
+interval, an optional capping mechanism, and a couple of trip points.
+
+Special Features
+----------------
+
+The power[1-*]_is_battery knob indicates if the power supply is a battery.
+Both power[1-*]_average_{min,max} must be set before the trip points will work.
diff --git a/drivers/acpi/Kconfig b/drivers/acpi/Kconfig
index 7ec7d88..8ac801d 100644
--- a/drivers/acpi/Kconfig
+++ b/drivers/acpi/Kconfig
@@ -82,6 +82,18 @@ config ACPI_PROCFS_POWER
 
 	  Say N to delete power /proc/acpi/ directories that have moved to /sys/
 
+config ACPI_POWER_METER
+	tristate "ACPI 4.0 power meter"
+	depends on HWMON
+	default m
+	help
+	  This driver exposes ACPI 4.0 power meters as hardware monitoring
+	  devices.  Say Y (or M) if you have an Intel or AMD computer with
+	  a power meter.
+
+	  To compile this driver as a module, choose M here:
+	  the module will be called power-meter.
+
 config ACPI_SYSFS_POWER
 	bool "Future power /sys interface"
 	select POWER_SUPPLY
diff --git a/drivers/acpi/Makefile b/drivers/acpi/Makefile
index 03a985b..82cd49d 100644
--- a/drivers/acpi/Makefile
+++ b/drivers/acpi/Makefile
@@ -56,6 +56,7 @@ obj-$(CONFIG_ACPI_HOTPLUG_MEMORY) += acpi_memhotplug.o
 obj-$(CONFIG_ACPI_BATTERY)	+= battery.o
 obj-$(CONFIG_ACPI_SBS)		+= sbshc.o
 obj-$(CONFIG_ACPI_SBS)		+= sbs.o
+obj-$(CONFIG_ACPI_POWER_METER)	+= power_meter.o
 
 # processor has its own "processor." module_param namespace
 processor-y			:= processor_core.o processor_throttling.o
diff --git a/drivers/acpi/power_meter.c b/drivers/acpi/power_meter.c
new file mode 100644
index 0000000..9418bbd
--- /dev/null
+++ b/drivers/acpi/power_meter.c
@@ -0,0 +1,776 @@
+/*
+ * A hwmon driver for ACPI 4.0 power meters
+ * Copyright (C) 2009 IBM
+ *
+ * Author: Darrick J. Wong <djwong@us.ibm.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+
+#include <linux/module.h>
+#include <linux/hwmon.h>
+#include <linux/hwmon-sysfs.h>
+#include <linux/jiffies.h>
+#include <linux/mutex.h>
+#include <linux/kdev_t.h>
+#include <linux/sched.h>
+#include <linux/time.h>
+#include <acpi/acpi_drivers.h>
+#include <acpi/acpi_bus.h>
+
+#define ACPI_POWER_METER_NAME		"power_meter"
+ACPI_MODULE_NAME(ACPI_POWER_METER_NAME);
+#define ACPI_POWER_METER_DEVICE_NAME	"Power Meter"
+#define ACPI_POWER_METER_CLASS		"power_meter_resource"
+
+#define NUM_SENSORS			14
+
+#define POWER_METER_CAN_MEASURE	(1 << 0)
+#define POWER_METER_CAN_TRIP	(1 << 1)
+#define POWER_METER_CAN_CAP	(1 << 2)
+#define POWER_METER_CAN_NOTIFY	(1 << 3)
+#define POWER_METER_IS_BATTERY	(1 << 8)
+#define UNKNOWN_HYSTERESIS	0xFFFFFFFF
+
+#define METER_NOTIFY_CONFIG	0x80
+#define METER_NOTIFY_TRIP	0x81
+#define METER_NOTIFY_CAP	0x82
+#define METER_NOTIFY_CAPPING	0x83
+#define METER_NOTIFY_INTERVAL	0x84
+
+static struct acpi_device_id power_meter_ids[] = {
+	{"ACPI000D", 0},
+	{"", 0},
+};
+MODULE_DEVICE_TABLE(acpi, power_meter_ids);
+
+struct acpi_power_meter_capabilities {
+	acpi_integer		flags;
+	acpi_integer		units;
+	acpi_integer		type;
+	acpi_integer		accuracy;
+	acpi_integer		sampling_time;
+	acpi_integer		min_avg_interval;
+	acpi_integer		max_avg_interval;
+	acpi_integer		hysteresis;
+	acpi_integer		configurable_cap;
+	acpi_integer		min_cap;
+	acpi_integer		max_cap;
+};
+
+struct acpi_power_meter_resource {
+	struct acpi_device	*acpi_dev;
+	acpi_bus_id		name;
+	struct mutex		lock;
+	struct device		*hwmon_dev;
+	struct acpi_power_meter_capabilities	caps;
+	acpi_integer		power;
+	acpi_integer		cap;
+	acpi_integer		avg_interval;
+	int			sensors_valid;
+	unsigned long		sensors_last_updated;
+	struct sensor_device_attribute	sensors[NUM_SENSORS];
+	int			num_sensors;
+	int			trip[2];
+};
+
+struct ro_sensor_template {
+	char *label;
+	ssize_t (*show)(struct device *dev,
+			struct device_attribute *devattr,
+			char *buf);
+	int index;
+};
+
+struct rw_sensor_template {
+	char *label;
+	ssize_t (*show)(struct device *dev,
+			struct device_attribute *devattr,
+			char *buf);
+	ssize_t (*set)(struct device *dev,
+		       struct device_attribute *devattr,
+		       const char *buf, size_t count);
+	int index;
+};
+
+/* Averaging interval */
+static int update_avg_interval(struct acpi_power_meter_resource *resource)
+{
+	unsigned long long data;
+	acpi_status status;
+
+	status = acpi_evaluate_integer(resource->acpi_dev->handle, "_GAI",
+				       NULL, &data);
+	if (ACPI_FAILURE(status)) {
+		ACPI_EXCEPTION((AE_INFO, status, "Evaluating _GAI"));
+		return -ENODEV;
+	}
+
+	resource->avg_interval = data;
+	return 0;
+}
+
+static ssize_t show_avg_interval(struct device *dev,
+				 struct device_attribute *devattr,
+				 char *buf)
+{
+	struct acpi_device *acpi_dev = to_acpi_device(dev);
+	struct acpi_power_meter_resource *resource = acpi_dev->driver_data;
+
+	mutex_lock(&resource->lock);
+	update_avg_interval(resource);
+	mutex_unlock(&resource->lock);
+
+	return sprintf(buf, "%llu\n", resource->avg_interval);
+}
+
+static ssize_t set_avg_interval(struct device *dev,
+				struct device_attribute *devattr,
+				const char *buf, size_t count)
+{
+	struct acpi_device *acpi_dev = to_acpi_device(dev);
+	struct acpi_power_meter_resource *resource = acpi_dev->driver_data;
+	union acpi_object arg0 = { ACPI_TYPE_INTEGER };
+	struct acpi_object_list args = { 1, &arg0 };
+	int res;
+	unsigned long temp;
+	unsigned long long data;
+	acpi_status status;
+
+	res = strict_strtoul(buf, 10, &temp);
+	if (res)
+		return res;
+
+	if (temp > resource->caps.max_avg_interval ||
+	    temp < resource->caps.min_avg_interval)
+		return -EINVAL;
+	arg0.integer.value = temp;
+
+	mutex_lock(&resource->lock);
+	status = acpi_evaluate_integer(resource->acpi_dev->handle, "_PAI",
+				       &args, &data);
+	if (!ACPI_FAILURE(status))
+		resource->avg_interval = temp;
+	mutex_unlock(&resource->lock);
+
+	if (ACPI_FAILURE(status)) {
+		ACPI_EXCEPTION((AE_INFO, status, "Evaluating _PAI"));
+		return -EINVAL;
+	}
+
+	if (data)
+		return -EINVAL;
+
+	return count;
+}
+
+/* Cap functions */
+static int update_cap(struct acpi_power_meter_resource *resource)
+{
+	unsigned long long data;
+	acpi_status status;
+
+	status = acpi_evaluate_integer(resource->acpi_dev->handle, "_GHL",
+				       NULL, &data);
+	if (ACPI_FAILURE(status)) {
+		ACPI_EXCEPTION((AE_INFO, status, "Evaluating _GHL"));
+		return -ENODEV;
+	}
+
+	resource->cap = data;
+	return 0;
+}
+
+static ssize_t show_cap(struct device *dev,
+			struct device_attribute *devattr,
+			char *buf)
+{
+	struct acpi_device *acpi_dev = to_acpi_device(dev);
+	struct acpi_power_meter_resource *resource = acpi_dev->driver_data;
+
+	mutex_lock(&resource->lock);
+	update_cap(resource);
+	mutex_unlock(&resource->lock);
+
+	return sprintf(buf, "%llu\n", resource->cap * 1000);
+}
+
+static ssize_t set_cap(struct device *dev, struct device_attribute *devattr,
+		       const char *buf, size_t count)
+{
+	struct acpi_device *acpi_dev = to_acpi_device(dev);
+	struct acpi_power_meter_resource *resource = acpi_dev->driver_data;
+	union acpi_object arg0 = { ACPI_TYPE_INTEGER };
+	struct acpi_object_list args = { 1, &arg0 };
+	int res;
+	unsigned long temp;
+	unsigned long long data;
+	acpi_status status;
+
+	res = strict_strtoul(buf, 10, &temp);
+	if (res)
+		return res;
+
+	temp /= 1000;
+	if (temp > resource->caps.max_cap || temp < resource->caps.min_cap)
+		return -EINVAL;
+	arg0.integer.value = temp;
+
+	mutex_lock(&resource->lock);
+	status = acpi_evaluate_integer(resource->acpi_dev->handle, "_SHL",
+				       &args, &data);
+	if (!ACPI_FAILURE(status))
+		resource->cap = temp;
+	mutex_unlock(&resource->lock);
+
+	if (ACPI_FAILURE(status)) {
+		ACPI_EXCEPTION((AE_INFO, status, "Evaluating _SHL"));
+		return -EINVAL;
+	}
+
+	if (data)
+		return -EINVAL;
+
+	return count;
+}
+
+/* Power meter trip points */
+static int set_acpi_trip(struct acpi_power_meter_resource *resource)
+{
+	union acpi_object arg_objs[] = {
+		{ACPI_TYPE_INTEGER},
+		{ACPI_TYPE_INTEGER}
+	};
+	struct acpi_object_list args = { 2, arg_objs };
+	unsigned long long data;
+	acpi_status status;
+
+	/* Both trip levels must be set */
+	if (resource->trip[0] < 0 || resource->trip[1] < 0)
+		return 0;
+
+	/* This driver stores min, max; ACPI wants max, min. */
+	arg_objs[0].integer.value = resource->trip[1];
+	arg_objs[1].integer.value = resource->trip[0];
+
+	status = acpi_evaluate_integer(resource->acpi_dev->handle, "_PTP",
+				       &args, &data);
+	if (ACPI_FAILURE(status)) {
+		ACPI_EXCEPTION((AE_INFO, status, "Evaluating _PTP"));
+		return -EINVAL;
+	}
+
+	return data;
+}
+
+static ssize_t set_trip(struct device *dev, struct device_attribute *devattr,
+			const char *buf, size_t count)
+{
+	struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr);
+	struct acpi_device *acpi_dev = to_acpi_device(dev);
+	struct acpi_power_meter_resource *resource = acpi_dev->driver_data;
+	int res;
+	unsigned long temp;
+
+	res = strict_strtoul(buf, 10, &temp);
+	if (res)
+		return res;
+
+	temp /= 1000;
+	if (temp < 0)
+		return -EINVAL;
+
+	mutex_lock(&resource->lock);
+	resource->trip[attr->index - 7] = temp;
+	res = set_acpi_trip(resource);
+	mutex_unlock(&resource->lock);
+
+	if (res)
+		return res;
+
+	return count;
+}
+
+/* Power meter */
+static int update_meter(struct acpi_power_meter_resource *resource)
+{
+	unsigned long long data;
+	acpi_status status;
+	unsigned long local_jiffies = jiffies;
+
+	if (time_before(local_jiffies, resource->sensors_last_updated +
+			msecs_to_jiffies(resource->caps.sampling_time)) &&
+			resource->sensors_valid)
+		return 0;
+
+	status = acpi_evaluate_integer(resource->acpi_dev->handle, "_PMM",
+				       NULL, &data);
+	if (ACPI_FAILURE(status)) {
+		ACPI_EXCEPTION((AE_INFO, status, "Evaluating _PMM"));
+		return -ENODEV;
+	}
+
+	resource->power = data;
+	resource->sensors_valid = 1;
+	resource->sensors_last_updated = jiffies;
+	return 0;
+}
+
+static ssize_t show_power(struct device *dev,
+			  struct device_attribute *devattr,
+			  char *buf)
+{
+	struct acpi_device *acpi_dev = to_acpi_device(dev);
+	struct acpi_power_meter_resource *resource = acpi_dev->driver_data;
+
+	mutex_lock(&resource->lock);
+	update_meter(resource);
+	mutex_unlock(&resource->lock);
+
+	return sprintf(buf, "%llu\n", resource->power * 1000);
+}
+
+/* Miscellaneous */
+static ssize_t show_val(struct device *dev,
+			struct device_attribute *devattr,
+			char *buf)
+{
+	struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr);
+	struct acpi_device *acpi_dev = to_acpi_device(dev);
+	struct acpi_power_meter_resource *resource = acpi_dev->driver_data;
+	acpi_integer val = 0;
+
+	switch (attr->index) {
+	case 0:
+		val = resource->caps.min_avg_interval;
+		break;
+	case 1:
+		val = resource->caps.max_avg_interval;
+		break;
+	case 2:
+		val = resource->caps.min_cap * 1000;
+		break;
+	case 3:
+		val = resource->caps.max_cap * 1000;
+		break;
+	case 4:
+		if (resource->caps.hysteresis == UNKNOWN_HYSTERESIS)
+			return sprintf(buf, "unknown\n");
+
+		val = resource->caps.hysteresis * 1000;
+		break;
+	case 5:
+		if (resource->caps.flags & POWER_METER_IS_BATTERY)
+			val = 1;
+		else
+			val = 0;
+		break;
+	case 6:
+		if (resource->power > resource->cap)
+			val = 1;
+		else
+			val = 0;
+		break;
+	case 7:
+	case 8:
+		if (resource->trip[attr->index - 7] < 0)
+			return sprintf(buf, "unknown\n");
+
+		val = resource->trip[attr->index - 7] * 1000;
+		break;
+	default:
+		BUG();
+	}
+
+	return sprintf(buf, "%llu\n", val);
+}
+
+static ssize_t show_accuracy(struct device *dev,
+			     struct device_attribute *devattr,
+			     char *buf)
+{
+	struct acpi_device *acpi_dev = to_acpi_device(dev);
+	struct acpi_power_meter_resource *resource = acpi_dev->driver_data;
+	unsigned int acc = resource->caps.accuracy;
+
+	return sprintf(buf, "%u.%u%%\n", acc / 1000, acc % 1000);
+}
+
+static ssize_t show_name(struct device *dev,
+			 struct device_attribute *devattr,
+			 char *buf)
+{
+	return sprintf(buf, "%s\n", ACPI_POWER_METER_NAME);
+}
+
+/* Sensor descriptions.  If you add a sensor, update NUM_SENSORS above! */
+static struct ro_sensor_template meter_ro_attrs[] = {
+{"power1_average", show_power, 0},
+{"power1_accuracy", show_accuracy, 0},
+{"power1_average_interval_min", show_val, 0},
+{"power1_average_interval_max", show_val, 1},
+{"power1_is_battery", show_val, 5},
+{NULL, NULL, 0},
+};
+
+static struct rw_sensor_template meter_rw_attrs[] = {
+{"power1_average_interval", show_avg_interval, set_avg_interval, 0},
+{NULL, NULL, NULL, 0},
+};
+
+static struct ro_sensor_template misc_cap_attrs[] = {
+{"power1_cap_min", show_val, 2},
+{"power1_cap_max", show_val, 3},
+{"power1_cap_hyst", show_val, 4},
+{"power1_alarm", show_val, 6},
+{NULL, NULL, 0},
+};
+
+static struct ro_sensor_template ro_cap_attrs[] = {
+{"power1_cap", show_cap, 0},
+{NULL, NULL, 0},
+};
+
+static struct rw_sensor_template rw_cap_attrs[] = {
+{"power1_cap", show_cap, set_cap, 0},
+{NULL, NULL, NULL, 0},
+};
+
+static struct rw_sensor_template trip_attrs[] = {
+{"power1_average_min", show_val, set_trip, 7},
+{"power1_average_max", show_val, set_trip, 8},
+{NULL, NULL, NULL, 0},
+};
+
+static struct ro_sensor_template misc_attrs[] = {
+{"name", show_name, 0},
+{NULL, NULL, 0},
+};
+
+/* Registration and deregistration */
+static int register_ro_attrs(struct acpi_power_meter_resource *resource,
+			     struct ro_sensor_template *ro)
+{
+	struct device *dev = &resource->acpi_dev->dev;
+	struct sensor_device_attribute *sensors =
+		&resource->sensors[resource->num_sensors];
+	int res;
+
+	while (ro->label) {
+		sensors->dev_attr.attr.name = ro->label;
+		sensors->dev_attr.attr.mode = S_IRUGO;
+		sensors->dev_attr.show = ro->show;
+		sensors->index = ro->index;
+
+		res = device_create_file(dev, &sensors->dev_attr);
+		if (res) {
+			sensors->dev_attr.attr.name = NULL;
+			goto error;
+		}
+		sensors++;
+		resource->num_sensors++;
+		ro++;
+	}
+
+error:
+	return res;
+}
+
+static int register_rw_attrs(struct acpi_power_meter_resource *resource,
+			     struct rw_sensor_template *rw)
+{
+	struct device *dev = &resource->acpi_dev->dev;
+	struct sensor_device_attribute *sensors =
+		&resource->sensors[resource->num_sensors];
+	int res;
+
+	while (rw->label) {
+		sensors->dev_attr.attr.name = rw->label;
+		sensors->dev_attr.attr.mode = S_IRUGO | S_IWUSR;
+		sensors->dev_attr.show = rw->show;
+		sensors->dev_attr.store = rw->set;
+		sensors->index = rw->index;
+
+		res = device_create_file(dev, &sensors->dev_attr);
+		if (res) {
+			sensors->dev_attr.attr.name = NULL;
+			goto error;
+		}
+		sensors++;
+		resource->num_sensors++;
+		rw++;
+	}
+
+error:
+	return res;
+}
+
+static void remove_attrs(struct acpi_power_meter_resource *resource)
+{
+	int i;
+
+	for (i = 0; i < resource->num_sensors; i++) {
+		if (!resource->sensors[i].dev_attr.attr.name)
+			continue;
+		device_remove_file(&resource->acpi_dev->dev,
+				   &resource->sensors[i].dev_attr);
+	}
+
+	resource->num_sensors = 0;
+}
+
+static int setup_attrs(struct acpi_power_meter_resource *resource)
+{
+	int res = 0;
+
+	if (resource->caps.flags & POWER_METER_CAN_MEASURE) {
+		res = register_ro_attrs(resource, meter_ro_attrs);
+		if (res)
+			goto error;
+		res = register_rw_attrs(resource, meter_rw_attrs);
+		if (res)
+			goto error;
+	}
+
+	if (resource->caps.flags & POWER_METER_CAN_CAP) {
+		if (resource->caps.configurable_cap) {
+			res = register_rw_attrs(resource, rw_cap_attrs);
+			if (res)
+				goto error;
+		} else {
+			res = register_ro_attrs(resource, ro_cap_attrs);
+			if (res)
+				goto error;
+		}
+		res = register_ro_attrs(resource, misc_cap_attrs);
+		if (res)
+			goto error;
+	}
+
+	if (resource->caps.flags & POWER_METER_CAN_TRIP) {
+		res = register_rw_attrs(resource, trip_attrs);
+		if (res)
+			goto error;
+	}
+
+	res = register_ro_attrs(resource, misc_attrs);
+	if (res)
+		goto error;
+
+	return res;
+error:
+	remove_attrs(resource);
+	return res;
+}
+
+static int read_capabilities(struct acpi_power_meter_resource *resource)
+{
+	int res = 0;
+	struct acpi_buffer buffer = { ACPI_ALLOCATE_BUFFER, NULL };
+	struct acpi_buffer state = { 0, NULL };
+	struct acpi_buffer format = { sizeof("NNNNNNNNNNN"), "NNNNNNNNNNN" };
+	union acpi_object *pss;
+	acpi_status status;
+
+	status = acpi_evaluate_object(resource->acpi_dev->handle, "_PMC", NULL,
+				      &buffer);
+	if (ACPI_FAILURE(status)) {
+		ACPI_EXCEPTION((AE_INFO, status, "Evaluating _PMC"));
+		return -ENODEV;
+	}
+
+	pss = buffer.pointer;
+	if (!pss ||
+	    pss->type != ACPI_TYPE_PACKAGE ||
+	    pss->package.count != 14) {
+		dev_err(&resource->acpi_dev->dev, PREFIX "Invalid _PMC data\n");
+		res = -EFAULT;
+		goto end;
+	}
+
+	state.length = sizeof(struct acpi_power_meter_capabilities);
+	state.pointer = &resource->caps;
+
+	status = acpi_extract_package(pss, &format, &state);
+	if (ACPI_FAILURE(status)) {
+		ACPI_EXCEPTION((AE_INFO, status, "Invalid data"));
+		res = -EFAULT;
+		goto end;
+	}
+
+	if (resource->caps.units) {
+		dev_err(&resource->acpi_dev->dev, PREFIX "Unknown unit %llu.\n",
+			resource->caps.units);
+		res = -EINVAL;
+		goto end;
+	}
+
+	dev_info(&resource->acpi_dev->dev, "Found ACPI power meter.\n");
+end:
+	kfree(buffer.pointer);
+	return res;
+}
+
+/* Handle ACPI event notifications */
+static void acpi_power_meter_notify(struct acpi_device *device, u32 event)
+{
+	struct acpi_power_meter_resource *resource;
+	int res;
+
+	if (!device || !acpi_driver_data(device))
+		return;
+
+	resource = acpi_driver_data(device);
+
+	mutex_lock(&resource->lock);
+	switch (event) {
+	case METER_NOTIFY_CONFIG:
+		res = read_capabilities(resource);
+		if (res)
+			break;
+
+		remove_attrs(resource);
+		setup_attrs(resource);
+		break;
+	case METER_NOTIFY_TRIP:
+		update_meter(resource);
+		break;
+	case METER_NOTIFY_CAP:
+		update_cap(resource);
+		break;
+	case METER_NOTIFY_INTERVAL:
+		update_avg_interval(resource);
+		break;
+	case METER_NOTIFY_CAPPING:
+		dev_info(&device->dev, "Capping in progress.\n");
+		break;
+	default:
+		BUG();
+	}
+	mutex_unlock(&resource->lock);
+}
+
+static int acpi_power_meter_add(struct acpi_device *device)
+{
+	int res;
+	struct acpi_power_meter_resource *resource;
+
+	if (!device)
+		return -EINVAL;
+
+	resource = kzalloc(sizeof(struct acpi_power_meter_resource),
+			   GFP_KERNEL);
+	if (!resource)
+		return -ENOMEM;
+
+	resource->sensors_valid = 0;
+	resource->acpi_dev = device;
+	mutex_init(&resource->lock);
+	strcpy(acpi_device_name(device), ACPI_POWER_METER_DEVICE_NAME);
+	strcpy(acpi_device_class(device), ACPI_POWER_METER_CLASS);
+	device->driver_data = resource;
+
+	res = read_capabilities(resource);
+	if (res)
+		goto exit_free;
+
+	resource->trip[0] = resource->trip[1] = -1;
+
+	res = setup_attrs(resource);
+	if (res)
+		goto exit_free;
+
+	resource->hwmon_dev = hwmon_device_register(&device->dev);
+	if (IS_ERR(resource->hwmon_dev)) {
+		res = PTR_ERR(resource->hwmon_dev);
+		goto exit_remove;
+	}
+
+	res = 0;
+	goto exit;
+
+exit_remove:
+	remove_attrs(resource);
+exit_free:
+	kfree(resource);
+exit:
+	return res;
+}
+
+static int acpi_power_meter_remove(struct acpi_device *device, int type)
+{
+	struct acpi_power_meter_resource *resource;
+
+	if (!device || !acpi_driver_data(device))
+		return -EINVAL;
+
+	resource = acpi_driver_data(device);
+	hwmon_device_unregister(resource->hwmon_dev);
+
+	remove_attrs(resource);
+
+	kfree(resource);
+	return 0;
+}
+
+static int acpi_power_meter_resume(struct acpi_device *device)
+{
+	struct acpi_power_meter_resource *resource;
+
+	if (!device || !acpi_driver_data(device))
+		return -EINVAL;
+
+	resource = acpi_driver_data(device);
+	read_capabilities(resource);
+
+	return 0;
+}
+
+static struct acpi_driver acpi_power_meter_driver = {
+	.name = "power_meter",
+	.class = ACPI_POWER_METER_CLASS,
+	.ids = power_meter_ids,
+	.ops = {
+		.add = acpi_power_meter_add,
+		.remove = acpi_power_meter_remove,
+		.resume = acpi_power_meter_resume,
+		.notify = acpi_power_meter_notify,
+		},
+};
+
+/* Module init/exit routines */
+
+static int __init acpi_power_meter_init(void)
+{
+	int result;
+
+	result = acpi_bus_register_driver(&acpi_power_meter_driver);
+	if (result < 0)
+		return -ENODEV;
+
+	return 0;
+}
+
+static void __exit acpi_power_meter_exit(void)
+{
+	acpi_bus_unregister_driver(&acpi_power_meter_driver);
+}
+
+MODULE_AUTHOR("Darrick J. Wong <djwong@us.ibm.com>");
+MODULE_DESCRIPTION("ACPI 4.0 power meter driver");
+MODULE_LICENSE("GPL");
+
+module_init(acpi_power_meter_init);
+module_exit(acpi_power_meter_exit);


^ permalink raw reply related	[flat|nested] 38+ messages in thread

* [lm-sensors] [PATCH 2/2] acpi_power_meter: hwmon driver for ACPI
@ 2009-07-25  0:43   ` Darrick J. Wong
  0 siblings, 0 replies; 38+ messages in thread
From: Darrick J. Wong @ 2009-07-25  0:43 UTC (permalink / raw)
  To: Darrick J. Wong, Len Brown, Andrew Morton
  Cc: linux-kernel, lm-sensors, linux-acpi, Zhang Rui

This driver exposes ACPI 4.0 compliant power meters as hardware monitoring
devices.

Signed-off-by: Darrick J. Wong <djwong@us.ibm.com>
---
 Documentation/hwmon/acpi_power_meter |   25 +
 drivers/acpi/Kconfig                 |   12 +
 drivers/acpi/Makefile                |    1 
 drivers/acpi/power_meter.c           |  776 ++++++++++++++++++++++++++++++++++
 4 files changed, 814 insertions(+), 0 deletions(-)
 create mode 100644 Documentation/hwmon/acpi_power_meter
 create mode 100644 drivers/acpi/power_meter.c


diff --git a/Documentation/hwmon/acpi_power_meter b/Documentation/hwmon/acpi_power_meter
new file mode 100644
index 0000000..0af2e9a
--- /dev/null
+++ b/Documentation/hwmon/acpi_power_meter
@@ -0,0 +1,25 @@
+Kernel driver power_meter
+============+
+This driver talks to ACPI 4.0 power meters.
+
+Supported systems:
+  * Any recent system with ACPI 4.0.
+    Prefix: 'power_meter'
+    Datasheet: http://acpi.info/, section 10.4.
+
+Author: Darrick J. Wong
+
+Description
+-----------
+
+This driver implements sensor reading support for the power meters exposed in
+the ACPI 4.0 spec (Chapter 10.4).  These devices have a simple set of
+features--a power meter that returns average power use over a configurable
+interval, an optional capping mechanism, and a couple of trip points.
+
+Special Features
+----------------
+
+The power[1-*]_is_battery knob indicates if the power supply is a battery.
+Both power[1-*]_average_{min,max} must be set before the trip points will work.
diff --git a/drivers/acpi/Kconfig b/drivers/acpi/Kconfig
index 7ec7d88..8ac801d 100644
--- a/drivers/acpi/Kconfig
+++ b/drivers/acpi/Kconfig
@@ -82,6 +82,18 @@ config ACPI_PROCFS_POWER
 
 	  Say N to delete power /proc/acpi/ directories that have moved to /sys/
 
+config ACPI_POWER_METER
+	tristate "ACPI 4.0 power meter"
+	depends on HWMON
+	default m
+	help
+	  This driver exposes ACPI 4.0 power meters as hardware monitoring
+	  devices.  Say Y (or M) if you have an Intel or AMD computer with
+	  a power meter.
+
+	  To compile this driver as a module, choose M here:
+	  the module will be called power-meter.
+
 config ACPI_SYSFS_POWER
 	bool "Future power /sys interface"
 	select POWER_SUPPLY
diff --git a/drivers/acpi/Makefile b/drivers/acpi/Makefile
index 03a985b..82cd49d 100644
--- a/drivers/acpi/Makefile
+++ b/drivers/acpi/Makefile
@@ -56,6 +56,7 @@ obj-$(CONFIG_ACPI_HOTPLUG_MEMORY) += acpi_memhotplug.o
 obj-$(CONFIG_ACPI_BATTERY)	+= battery.o
 obj-$(CONFIG_ACPI_SBS)		+= sbshc.o
 obj-$(CONFIG_ACPI_SBS)		+= sbs.o
+obj-$(CONFIG_ACPI_POWER_METER)	+= power_meter.o
 
 # processor has its own "processor." module_param namespace
 processor-y			:= processor_core.o processor_throttling.o
diff --git a/drivers/acpi/power_meter.c b/drivers/acpi/power_meter.c
new file mode 100644
index 0000000..9418bbd
--- /dev/null
+++ b/drivers/acpi/power_meter.c
@@ -0,0 +1,776 @@
+/*
+ * A hwmon driver for ACPI 4.0 power meters
+ * Copyright (C) 2009 IBM
+ *
+ * Author: Darrick J. Wong <djwong@us.ibm.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+
+#include <linux/module.h>
+#include <linux/hwmon.h>
+#include <linux/hwmon-sysfs.h>
+#include <linux/jiffies.h>
+#include <linux/mutex.h>
+#include <linux/kdev_t.h>
+#include <linux/sched.h>
+#include <linux/time.h>
+#include <acpi/acpi_drivers.h>
+#include <acpi/acpi_bus.h>
+
+#define ACPI_POWER_METER_NAME		"power_meter"
+ACPI_MODULE_NAME(ACPI_POWER_METER_NAME);
+#define ACPI_POWER_METER_DEVICE_NAME	"Power Meter"
+#define ACPI_POWER_METER_CLASS		"power_meter_resource"
+
+#define NUM_SENSORS			14
+
+#define POWER_METER_CAN_MEASURE	(1 << 0)
+#define POWER_METER_CAN_TRIP	(1 << 1)
+#define POWER_METER_CAN_CAP	(1 << 2)
+#define POWER_METER_CAN_NOTIFY	(1 << 3)
+#define POWER_METER_IS_BATTERY	(1 << 8)
+#define UNKNOWN_HYSTERESIS	0xFFFFFFFF
+
+#define METER_NOTIFY_CONFIG	0x80
+#define METER_NOTIFY_TRIP	0x81
+#define METER_NOTIFY_CAP	0x82
+#define METER_NOTIFY_CAPPING	0x83
+#define METER_NOTIFY_INTERVAL	0x84
+
+static struct acpi_device_id power_meter_ids[] = {
+	{"ACPI000D", 0},
+	{"", 0},
+};
+MODULE_DEVICE_TABLE(acpi, power_meter_ids);
+
+struct acpi_power_meter_capabilities {
+	acpi_integer		flags;
+	acpi_integer		units;
+	acpi_integer		type;
+	acpi_integer		accuracy;
+	acpi_integer		sampling_time;
+	acpi_integer		min_avg_interval;
+	acpi_integer		max_avg_interval;
+	acpi_integer		hysteresis;
+	acpi_integer		configurable_cap;
+	acpi_integer		min_cap;
+	acpi_integer		max_cap;
+};
+
+struct acpi_power_meter_resource {
+	struct acpi_device	*acpi_dev;
+	acpi_bus_id		name;
+	struct mutex		lock;
+	struct device		*hwmon_dev;
+	struct acpi_power_meter_capabilities	caps;
+	acpi_integer		power;
+	acpi_integer		cap;
+	acpi_integer		avg_interval;
+	int			sensors_valid;
+	unsigned long		sensors_last_updated;
+	struct sensor_device_attribute	sensors[NUM_SENSORS];
+	int			num_sensors;
+	int			trip[2];
+};
+
+struct ro_sensor_template {
+	char *label;
+	ssize_t (*show)(struct device *dev,
+			struct device_attribute *devattr,
+			char *buf);
+	int index;
+};
+
+struct rw_sensor_template {
+	char *label;
+	ssize_t (*show)(struct device *dev,
+			struct device_attribute *devattr,
+			char *buf);
+	ssize_t (*set)(struct device *dev,
+		       struct device_attribute *devattr,
+		       const char *buf, size_t count);
+	int index;
+};
+
+/* Averaging interval */
+static int update_avg_interval(struct acpi_power_meter_resource *resource)
+{
+	unsigned long long data;
+	acpi_status status;
+
+	status = acpi_evaluate_integer(resource->acpi_dev->handle, "_GAI",
+				       NULL, &data);
+	if (ACPI_FAILURE(status)) {
+		ACPI_EXCEPTION((AE_INFO, status, "Evaluating _GAI"));
+		return -ENODEV;
+	}
+
+	resource->avg_interval = data;
+	return 0;
+}
+
+static ssize_t show_avg_interval(struct device *dev,
+				 struct device_attribute *devattr,
+				 char *buf)
+{
+	struct acpi_device *acpi_dev = to_acpi_device(dev);
+	struct acpi_power_meter_resource *resource = acpi_dev->driver_data;
+
+	mutex_lock(&resource->lock);
+	update_avg_interval(resource);
+	mutex_unlock(&resource->lock);
+
+	return sprintf(buf, "%llu\n", resource->avg_interval);
+}
+
+static ssize_t set_avg_interval(struct device *dev,
+				struct device_attribute *devattr,
+				const char *buf, size_t count)
+{
+	struct acpi_device *acpi_dev = to_acpi_device(dev);
+	struct acpi_power_meter_resource *resource = acpi_dev->driver_data;
+	union acpi_object arg0 = { ACPI_TYPE_INTEGER };
+	struct acpi_object_list args = { 1, &arg0 };
+	int res;
+	unsigned long temp;
+	unsigned long long data;
+	acpi_status status;
+
+	res = strict_strtoul(buf, 10, &temp);
+	if (res)
+		return res;
+
+	if (temp > resource->caps.max_avg_interval ||
+	    temp < resource->caps.min_avg_interval)
+		return -EINVAL;
+	arg0.integer.value = temp;
+
+	mutex_lock(&resource->lock);
+	status = acpi_evaluate_integer(resource->acpi_dev->handle, "_PAI",
+				       &args, &data);
+	if (!ACPI_FAILURE(status))
+		resource->avg_interval = temp;
+	mutex_unlock(&resource->lock);
+
+	if (ACPI_FAILURE(status)) {
+		ACPI_EXCEPTION((AE_INFO, status, "Evaluating _PAI"));
+		return -EINVAL;
+	}
+
+	if (data)
+		return -EINVAL;
+
+	return count;
+}
+
+/* Cap functions */
+static int update_cap(struct acpi_power_meter_resource *resource)
+{
+	unsigned long long data;
+	acpi_status status;
+
+	status = acpi_evaluate_integer(resource->acpi_dev->handle, "_GHL",
+				       NULL, &data);
+	if (ACPI_FAILURE(status)) {
+		ACPI_EXCEPTION((AE_INFO, status, "Evaluating _GHL"));
+		return -ENODEV;
+	}
+
+	resource->cap = data;
+	return 0;
+}
+
+static ssize_t show_cap(struct device *dev,
+			struct device_attribute *devattr,
+			char *buf)
+{
+	struct acpi_device *acpi_dev = to_acpi_device(dev);
+	struct acpi_power_meter_resource *resource = acpi_dev->driver_data;
+
+	mutex_lock(&resource->lock);
+	update_cap(resource);
+	mutex_unlock(&resource->lock);
+
+	return sprintf(buf, "%llu\n", resource->cap * 1000);
+}
+
+static ssize_t set_cap(struct device *dev, struct device_attribute *devattr,
+		       const char *buf, size_t count)
+{
+	struct acpi_device *acpi_dev = to_acpi_device(dev);
+	struct acpi_power_meter_resource *resource = acpi_dev->driver_data;
+	union acpi_object arg0 = { ACPI_TYPE_INTEGER };
+	struct acpi_object_list args = { 1, &arg0 };
+	int res;
+	unsigned long temp;
+	unsigned long long data;
+	acpi_status status;
+
+	res = strict_strtoul(buf, 10, &temp);
+	if (res)
+		return res;
+
+	temp /= 1000;
+	if (temp > resource->caps.max_cap || temp < resource->caps.min_cap)
+		return -EINVAL;
+	arg0.integer.value = temp;
+
+	mutex_lock(&resource->lock);
+	status = acpi_evaluate_integer(resource->acpi_dev->handle, "_SHL",
+				       &args, &data);
+	if (!ACPI_FAILURE(status))
+		resource->cap = temp;
+	mutex_unlock(&resource->lock);
+
+	if (ACPI_FAILURE(status)) {
+		ACPI_EXCEPTION((AE_INFO, status, "Evaluating _SHL"));
+		return -EINVAL;
+	}
+
+	if (data)
+		return -EINVAL;
+
+	return count;
+}
+
+/* Power meter trip points */
+static int set_acpi_trip(struct acpi_power_meter_resource *resource)
+{
+	union acpi_object arg_objs[] = {
+		{ACPI_TYPE_INTEGER},
+		{ACPI_TYPE_INTEGER}
+	};
+	struct acpi_object_list args = { 2, arg_objs };
+	unsigned long long data;
+	acpi_status status;
+
+	/* Both trip levels must be set */
+	if (resource->trip[0] < 0 || resource->trip[1] < 0)
+		return 0;
+
+	/* This driver stores min, max; ACPI wants max, min. */
+	arg_objs[0].integer.value = resource->trip[1];
+	arg_objs[1].integer.value = resource->trip[0];
+
+	status = acpi_evaluate_integer(resource->acpi_dev->handle, "_PTP",
+				       &args, &data);
+	if (ACPI_FAILURE(status)) {
+		ACPI_EXCEPTION((AE_INFO, status, "Evaluating _PTP"));
+		return -EINVAL;
+	}
+
+	return data;
+}
+
+static ssize_t set_trip(struct device *dev, struct device_attribute *devattr,
+			const char *buf, size_t count)
+{
+	struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr);
+	struct acpi_device *acpi_dev = to_acpi_device(dev);
+	struct acpi_power_meter_resource *resource = acpi_dev->driver_data;
+	int res;
+	unsigned long temp;
+
+	res = strict_strtoul(buf, 10, &temp);
+	if (res)
+		return res;
+
+	temp /= 1000;
+	if (temp < 0)
+		return -EINVAL;
+
+	mutex_lock(&resource->lock);
+	resource->trip[attr->index - 7] = temp;
+	res = set_acpi_trip(resource);
+	mutex_unlock(&resource->lock);
+
+	if (res)
+		return res;
+
+	return count;
+}
+
+/* Power meter */
+static int update_meter(struct acpi_power_meter_resource *resource)
+{
+	unsigned long long data;
+	acpi_status status;
+	unsigned long local_jiffies = jiffies;
+
+	if (time_before(local_jiffies, resource->sensors_last_updated +
+			msecs_to_jiffies(resource->caps.sampling_time)) &&
+			resource->sensors_valid)
+		return 0;
+
+	status = acpi_evaluate_integer(resource->acpi_dev->handle, "_PMM",
+				       NULL, &data);
+	if (ACPI_FAILURE(status)) {
+		ACPI_EXCEPTION((AE_INFO, status, "Evaluating _PMM"));
+		return -ENODEV;
+	}
+
+	resource->power = data;
+	resource->sensors_valid = 1;
+	resource->sensors_last_updated = jiffies;
+	return 0;
+}
+
+static ssize_t show_power(struct device *dev,
+			  struct device_attribute *devattr,
+			  char *buf)
+{
+	struct acpi_device *acpi_dev = to_acpi_device(dev);
+	struct acpi_power_meter_resource *resource = acpi_dev->driver_data;
+
+	mutex_lock(&resource->lock);
+	update_meter(resource);
+	mutex_unlock(&resource->lock);
+
+	return sprintf(buf, "%llu\n", resource->power * 1000);
+}
+
+/* Miscellaneous */
+static ssize_t show_val(struct device *dev,
+			struct device_attribute *devattr,
+			char *buf)
+{
+	struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr);
+	struct acpi_device *acpi_dev = to_acpi_device(dev);
+	struct acpi_power_meter_resource *resource = acpi_dev->driver_data;
+	acpi_integer val = 0;
+
+	switch (attr->index) {
+	case 0:
+		val = resource->caps.min_avg_interval;
+		break;
+	case 1:
+		val = resource->caps.max_avg_interval;
+		break;
+	case 2:
+		val = resource->caps.min_cap * 1000;
+		break;
+	case 3:
+		val = resource->caps.max_cap * 1000;
+		break;
+	case 4:
+		if (resource->caps.hysteresis = UNKNOWN_HYSTERESIS)
+			return sprintf(buf, "unknown\n");
+
+		val = resource->caps.hysteresis * 1000;
+		break;
+	case 5:
+		if (resource->caps.flags & POWER_METER_IS_BATTERY)
+			val = 1;
+		else
+			val = 0;
+		break;
+	case 6:
+		if (resource->power > resource->cap)
+			val = 1;
+		else
+			val = 0;
+		break;
+	case 7:
+	case 8:
+		if (resource->trip[attr->index - 7] < 0)
+			return sprintf(buf, "unknown\n");
+
+		val = resource->trip[attr->index - 7] * 1000;
+		break;
+	default:
+		BUG();
+	}
+
+	return sprintf(buf, "%llu\n", val);
+}
+
+static ssize_t show_accuracy(struct device *dev,
+			     struct device_attribute *devattr,
+			     char *buf)
+{
+	struct acpi_device *acpi_dev = to_acpi_device(dev);
+	struct acpi_power_meter_resource *resource = acpi_dev->driver_data;
+	unsigned int acc = resource->caps.accuracy;
+
+	return sprintf(buf, "%u.%u%%\n", acc / 1000, acc % 1000);
+}
+
+static ssize_t show_name(struct device *dev,
+			 struct device_attribute *devattr,
+			 char *buf)
+{
+	return sprintf(buf, "%s\n", ACPI_POWER_METER_NAME);
+}
+
+/* Sensor descriptions.  If you add a sensor, update NUM_SENSORS above! */
+static struct ro_sensor_template meter_ro_attrs[] = {
+{"power1_average", show_power, 0},
+{"power1_accuracy", show_accuracy, 0},
+{"power1_average_interval_min", show_val, 0},
+{"power1_average_interval_max", show_val, 1},
+{"power1_is_battery", show_val, 5},
+{NULL, NULL, 0},
+};
+
+static struct rw_sensor_template meter_rw_attrs[] = {
+{"power1_average_interval", show_avg_interval, set_avg_interval, 0},
+{NULL, NULL, NULL, 0},
+};
+
+static struct ro_sensor_template misc_cap_attrs[] = {
+{"power1_cap_min", show_val, 2},
+{"power1_cap_max", show_val, 3},
+{"power1_cap_hyst", show_val, 4},
+{"power1_alarm", show_val, 6},
+{NULL, NULL, 0},
+};
+
+static struct ro_sensor_template ro_cap_attrs[] = {
+{"power1_cap", show_cap, 0},
+{NULL, NULL, 0},
+};
+
+static struct rw_sensor_template rw_cap_attrs[] = {
+{"power1_cap", show_cap, set_cap, 0},
+{NULL, NULL, NULL, 0},
+};
+
+static struct rw_sensor_template trip_attrs[] = {
+{"power1_average_min", show_val, set_trip, 7},
+{"power1_average_max", show_val, set_trip, 8},
+{NULL, NULL, NULL, 0},
+};
+
+static struct ro_sensor_template misc_attrs[] = {
+{"name", show_name, 0},
+{NULL, NULL, 0},
+};
+
+/* Registration and deregistration */
+static int register_ro_attrs(struct acpi_power_meter_resource *resource,
+			     struct ro_sensor_template *ro)
+{
+	struct device *dev = &resource->acpi_dev->dev;
+	struct sensor_device_attribute *sensors +		&resource->sensors[resource->num_sensors];
+	int res;
+
+	while (ro->label) {
+		sensors->dev_attr.attr.name = ro->label;
+		sensors->dev_attr.attr.mode = S_IRUGO;
+		sensors->dev_attr.show = ro->show;
+		sensors->index = ro->index;
+
+		res = device_create_file(dev, &sensors->dev_attr);
+		if (res) {
+			sensors->dev_attr.attr.name = NULL;
+			goto error;
+		}
+		sensors++;
+		resource->num_sensors++;
+		ro++;
+	}
+
+error:
+	return res;
+}
+
+static int register_rw_attrs(struct acpi_power_meter_resource *resource,
+			     struct rw_sensor_template *rw)
+{
+	struct device *dev = &resource->acpi_dev->dev;
+	struct sensor_device_attribute *sensors +		&resource->sensors[resource->num_sensors];
+	int res;
+
+	while (rw->label) {
+		sensors->dev_attr.attr.name = rw->label;
+		sensors->dev_attr.attr.mode = S_IRUGO | S_IWUSR;
+		sensors->dev_attr.show = rw->show;
+		sensors->dev_attr.store = rw->set;
+		sensors->index = rw->index;
+
+		res = device_create_file(dev, &sensors->dev_attr);
+		if (res) {
+			sensors->dev_attr.attr.name = NULL;
+			goto error;
+		}
+		sensors++;
+		resource->num_sensors++;
+		rw++;
+	}
+
+error:
+	return res;
+}
+
+static void remove_attrs(struct acpi_power_meter_resource *resource)
+{
+	int i;
+
+	for (i = 0; i < resource->num_sensors; i++) {
+		if (!resource->sensors[i].dev_attr.attr.name)
+			continue;
+		device_remove_file(&resource->acpi_dev->dev,
+				   &resource->sensors[i].dev_attr);
+	}
+
+	resource->num_sensors = 0;
+}
+
+static int setup_attrs(struct acpi_power_meter_resource *resource)
+{
+	int res = 0;
+
+	if (resource->caps.flags & POWER_METER_CAN_MEASURE) {
+		res = register_ro_attrs(resource, meter_ro_attrs);
+		if (res)
+			goto error;
+		res = register_rw_attrs(resource, meter_rw_attrs);
+		if (res)
+			goto error;
+	}
+
+	if (resource->caps.flags & POWER_METER_CAN_CAP) {
+		if (resource->caps.configurable_cap) {
+			res = register_rw_attrs(resource, rw_cap_attrs);
+			if (res)
+				goto error;
+		} else {
+			res = register_ro_attrs(resource, ro_cap_attrs);
+			if (res)
+				goto error;
+		}
+		res = register_ro_attrs(resource, misc_cap_attrs);
+		if (res)
+			goto error;
+	}
+
+	if (resource->caps.flags & POWER_METER_CAN_TRIP) {
+		res = register_rw_attrs(resource, trip_attrs);
+		if (res)
+			goto error;
+	}
+
+	res = register_ro_attrs(resource, misc_attrs);
+	if (res)
+		goto error;
+
+	return res;
+error:
+	remove_attrs(resource);
+	return res;
+}
+
+static int read_capabilities(struct acpi_power_meter_resource *resource)
+{
+	int res = 0;
+	struct acpi_buffer buffer = { ACPI_ALLOCATE_BUFFER, NULL };
+	struct acpi_buffer state = { 0, NULL };
+	struct acpi_buffer format = { sizeof("NNNNNNNNNNN"), "NNNNNNNNNNN" };
+	union acpi_object *pss;
+	acpi_status status;
+
+	status = acpi_evaluate_object(resource->acpi_dev->handle, "_PMC", NULL,
+				      &buffer);
+	if (ACPI_FAILURE(status)) {
+		ACPI_EXCEPTION((AE_INFO, status, "Evaluating _PMC"));
+		return -ENODEV;
+	}
+
+	pss = buffer.pointer;
+	if (!pss ||
+	    pss->type != ACPI_TYPE_PACKAGE ||
+	    pss->package.count != 14) {
+		dev_err(&resource->acpi_dev->dev, PREFIX "Invalid _PMC data\n");
+		res = -EFAULT;
+		goto end;
+	}
+
+	state.length = sizeof(struct acpi_power_meter_capabilities);
+	state.pointer = &resource->caps;
+
+	status = acpi_extract_package(pss, &format, &state);
+	if (ACPI_FAILURE(status)) {
+		ACPI_EXCEPTION((AE_INFO, status, "Invalid data"));
+		res = -EFAULT;
+		goto end;
+	}
+
+	if (resource->caps.units) {
+		dev_err(&resource->acpi_dev->dev, PREFIX "Unknown unit %llu.\n",
+			resource->caps.units);
+		res = -EINVAL;
+		goto end;
+	}
+
+	dev_info(&resource->acpi_dev->dev, "Found ACPI power meter.\n");
+end:
+	kfree(buffer.pointer);
+	return res;
+}
+
+/* Handle ACPI event notifications */
+static void acpi_power_meter_notify(struct acpi_device *device, u32 event)
+{
+	struct acpi_power_meter_resource *resource;
+	int res;
+
+	if (!device || !acpi_driver_data(device))
+		return;
+
+	resource = acpi_driver_data(device);
+
+	mutex_lock(&resource->lock);
+	switch (event) {
+	case METER_NOTIFY_CONFIG:
+		res = read_capabilities(resource);
+		if (res)
+			break;
+
+		remove_attrs(resource);
+		setup_attrs(resource);
+		break;
+	case METER_NOTIFY_TRIP:
+		update_meter(resource);
+		break;
+	case METER_NOTIFY_CAP:
+		update_cap(resource);
+		break;
+	case METER_NOTIFY_INTERVAL:
+		update_avg_interval(resource);
+		break;
+	case METER_NOTIFY_CAPPING:
+		dev_info(&device->dev, "Capping in progress.\n");
+		break;
+	default:
+		BUG();
+	}
+	mutex_unlock(&resource->lock);
+}
+
+static int acpi_power_meter_add(struct acpi_device *device)
+{
+	int res;
+	struct acpi_power_meter_resource *resource;
+
+	if (!device)
+		return -EINVAL;
+
+	resource = kzalloc(sizeof(struct acpi_power_meter_resource),
+			   GFP_KERNEL);
+	if (!resource)
+		return -ENOMEM;
+
+	resource->sensors_valid = 0;
+	resource->acpi_dev = device;
+	mutex_init(&resource->lock);
+	strcpy(acpi_device_name(device), ACPI_POWER_METER_DEVICE_NAME);
+	strcpy(acpi_device_class(device), ACPI_POWER_METER_CLASS);
+	device->driver_data = resource;
+
+	res = read_capabilities(resource);
+	if (res)
+		goto exit_free;
+
+	resource->trip[0] = resource->trip[1] = -1;
+
+	res = setup_attrs(resource);
+	if (res)
+		goto exit_free;
+
+	resource->hwmon_dev = hwmon_device_register(&device->dev);
+	if (IS_ERR(resource->hwmon_dev)) {
+		res = PTR_ERR(resource->hwmon_dev);
+		goto exit_remove;
+	}
+
+	res = 0;
+	goto exit;
+
+exit_remove:
+	remove_attrs(resource);
+exit_free:
+	kfree(resource);
+exit:
+	return res;
+}
+
+static int acpi_power_meter_remove(struct acpi_device *device, int type)
+{
+	struct acpi_power_meter_resource *resource;
+
+	if (!device || !acpi_driver_data(device))
+		return -EINVAL;
+
+	resource = acpi_driver_data(device);
+	hwmon_device_unregister(resource->hwmon_dev);
+
+	remove_attrs(resource);
+
+	kfree(resource);
+	return 0;
+}
+
+static int acpi_power_meter_resume(struct acpi_device *device)
+{
+	struct acpi_power_meter_resource *resource;
+
+	if (!device || !acpi_driver_data(device))
+		return -EINVAL;
+
+	resource = acpi_driver_data(device);
+	read_capabilities(resource);
+
+	return 0;
+}
+
+static struct acpi_driver acpi_power_meter_driver = {
+	.name = "power_meter",
+	.class = ACPI_POWER_METER_CLASS,
+	.ids = power_meter_ids,
+	.ops = {
+		.add = acpi_power_meter_add,
+		.remove = acpi_power_meter_remove,
+		.resume = acpi_power_meter_resume,
+		.notify = acpi_power_meter_notify,
+		},
+};
+
+/* Module init/exit routines */
+
+static int __init acpi_power_meter_init(void)
+{
+	int result;
+
+	result = acpi_bus_register_driver(&acpi_power_meter_driver);
+	if (result < 0)
+		return -ENODEV;
+
+	return 0;
+}
+
+static void __exit acpi_power_meter_exit(void)
+{
+	acpi_bus_unregister_driver(&acpi_power_meter_driver);
+}
+
+MODULE_AUTHOR("Darrick J. Wong <djwong@us.ibm.com>");
+MODULE_DESCRIPTION("ACPI 4.0 power meter driver");
+MODULE_LICENSE("GPL");
+
+module_init(acpi_power_meter_init);
+module_exit(acpi_power_meter_exit);


_______________________________________________
lm-sensors mailing list
lm-sensors@lm-sensors.org
http://lists.lm-sensors.org/mailman/listinfo/lm-sensors

^ permalink raw reply related	[flat|nested] 38+ messages in thread

* Re: [PATCH 2/2] acpi_power_meter: hwmon driver for ACPI 4.0 power meters
  2009-07-25  0:43   ` [lm-sensors] [PATCH 2/2] acpi_power_meter: hwmon driver for ACPI Darrick J. Wong
@ 2009-07-27  1:44     ` ykzhao
  -1 siblings, 0 replies; 38+ messages in thread
From: ykzhao @ 2009-07-27  1:44 UTC (permalink / raw)
  To: Darrick J. Wong
  Cc: Len Brown, Andrew Morton, linux-kernel, lm-sensors, linux-acpi,
	Zhang, Rui

On Sat, 2009-07-25 at 08:43 +0800, Darrick J. Wong wrote:
> This driver exposes ACPI 4.0 compliant power meters as hardware monitoring
> devices.
It seems OK to me. But it seems that some attributes can't be obtained
by using the hwmon sys I/F.
   a. Can we add a sys I/F that can exports the Power meter capability?
display all the content of _PMC as what we have done in battery
   b. Add a sys I/F that displays the name of device list measured by
the power meter if there exists the _PMD object.

Thanks.

> 
> Signed-off-by: Darrick J. Wong <djwong@us.ibm.com>
> ---
>  Documentation/hwmon/acpi_power_meter |   25 +
>  drivers/acpi/Kconfig                 |   12 +
>  drivers/acpi/Makefile                |    1
>  drivers/acpi/power_meter.c           |  776 ++++++++++++++++++++++++++++++++++
>  4 files changed, 814 insertions(+), 0 deletions(-)
>  create mode 100644 Documentation/hwmon/acpi_power_meter
>  create mode 100644 drivers/acpi/power_meter.c
> 
> 
> diff --git a/Documentation/hwmon/acpi_power_meter b/Documentation/hwmon/acpi_power_meter
> new file mode 100644
> index 0000000..0af2e9a
> --- /dev/null
> +++ b/Documentation/hwmon/acpi_power_meter
> @@ -0,0 +1,25 @@
> +Kernel driver power_meter
> +=========================
> +
> +This driver talks to ACPI 4.0 power meters.
> +
> +Supported systems:
> +  * Any recent system with ACPI 4.0.
> +    Prefix: 'power_meter'
> +    Datasheet: http://acpi.info/, section 10.4.
> +
> +Author: Darrick J. Wong
> +
> +Description
> +-----------
> +
> +This driver implements sensor reading support for the power meters exposed in
> +the ACPI 4.0 spec (Chapter 10.4).  These devices have a simple set of
> +features--a power meter that returns average power use over a configurable
> +interval, an optional capping mechanism, and a couple of trip points.
> +
> +Special Features
> +----------------
> +
> +The power[1-*]_is_battery knob indicates if the power supply is a battery.
> +Both power[1-*]_average_{min,max} must be set before the trip points will work.
> diff --git a/drivers/acpi/Kconfig b/drivers/acpi/Kconfig
> index 7ec7d88..8ac801d 100644
> --- a/drivers/acpi/Kconfig
> +++ b/drivers/acpi/Kconfig
> @@ -82,6 +82,18 @@ config ACPI_PROCFS_POWER
> 
>           Say N to delete power /proc/acpi/ directories that have moved to /sys/
> 
> +config ACPI_POWER_METER
> +       tristate "ACPI 4.0 power meter"
> +       depends on HWMON
> +       default m
> +       help
> +         This driver exposes ACPI 4.0 power meters as hardware monitoring
> +         devices.  Say Y (or M) if you have an Intel or AMD computer with
> +         a power meter.
> +
> +         To compile this driver as a module, choose M here:
> +         the module will be called power-meter.
> +
>  config ACPI_SYSFS_POWER
>         bool "Future power /sys interface"
>         select POWER_SUPPLY
> diff --git a/drivers/acpi/Makefile b/drivers/acpi/Makefile
> index 03a985b..82cd49d 100644
> --- a/drivers/acpi/Makefile
> +++ b/drivers/acpi/Makefile
> @@ -56,6 +56,7 @@ obj-$(CONFIG_ACPI_HOTPLUG_MEMORY) += acpi_memhotplug.o
>  obj-$(CONFIG_ACPI_BATTERY)     += battery.o
>  obj-$(CONFIG_ACPI_SBS)         += sbshc.o
>  obj-$(CONFIG_ACPI_SBS)         += sbs.o
> +obj-$(CONFIG_ACPI_POWER_METER) += power_meter.o
> 
>  # processor has its own "processor." module_param namespace
>  processor-y                    := processor_core.o processor_throttling.o
> diff --git a/drivers/acpi/power_meter.c b/drivers/acpi/power_meter.c
> new file mode 100644
> index 0000000..9418bbd
> --- /dev/null
> +++ b/drivers/acpi/power_meter.c
> @@ -0,0 +1,776 @@
> +/*
> + * A hwmon driver for ACPI 4.0 power meters
> + * Copyright (C) 2009 IBM
> + *
> + * Author: Darrick J. Wong <djwong@us.ibm.com>
> + *
> + * This program is free software; you can redistribute it and/or modify
> + * it under the terms of the GNU General Public License as published by
> + * the Free Software Foundation; either version 2 of the License, or
> + * (at your option) any later version.
> + *
> + * This program is distributed in the hope that 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
> + */
> +
> +#include <linux/module.h>
> +#include <linux/hwmon.h>
> +#include <linux/hwmon-sysfs.h>
> +#include <linux/jiffies.h>
> +#include <linux/mutex.h>
> +#include <linux/kdev_t.h>
> +#include <linux/sched.h>
> +#include <linux/time.h>
> +#include <acpi/acpi_drivers.h>
> +#include <acpi/acpi_bus.h>
> +
> +#define ACPI_POWER_METER_NAME          "power_meter"
> +ACPI_MODULE_NAME(ACPI_POWER_METER_NAME);
> +#define ACPI_POWER_METER_DEVICE_NAME   "Power Meter"
> +#define ACPI_POWER_METER_CLASS         "power_meter_resource"
> +
> +#define NUM_SENSORS                    14
> +
> +#define POWER_METER_CAN_MEASURE        (1 << 0)
> +#define POWER_METER_CAN_TRIP   (1 << 1)
> +#define POWER_METER_CAN_CAP    (1 << 2)
> +#define POWER_METER_CAN_NOTIFY (1 << 3)
> +#define POWER_METER_IS_BATTERY (1 << 8)
> +#define UNKNOWN_HYSTERESIS     0xFFFFFFFF
> +
> +#define METER_NOTIFY_CONFIG    0x80
> +#define METER_NOTIFY_TRIP      0x81
> +#define METER_NOTIFY_CAP       0x82
> +#define METER_NOTIFY_CAPPING   0x83
> +#define METER_NOTIFY_INTERVAL  0x84
> +
> +static struct acpi_device_id power_meter_ids[] = {
> +       {"ACPI000D", 0},
> +       {"", 0},
> +};
> +MODULE_DEVICE_TABLE(acpi, power_meter_ids);
> +
> +struct acpi_power_meter_capabilities {
> +       acpi_integer            flags;
> +       acpi_integer            units;
> +       acpi_integer            type;
> +       acpi_integer            accuracy;
> +       acpi_integer            sampling_time;
> +       acpi_integer            min_avg_interval;
> +       acpi_integer            max_avg_interval;
> +       acpi_integer            hysteresis;
> +       acpi_integer            configurable_cap;
> +       acpi_integer            min_cap;
> +       acpi_integer            max_cap;
> +};
> +
> +struct acpi_power_meter_resource {
> +       struct acpi_device      *acpi_dev;
> +       acpi_bus_id             name;
> +       struct mutex            lock;
> +       struct device           *hwmon_dev;
> +       struct acpi_power_meter_capabilities    caps;
> +       acpi_integer            power;
> +       acpi_integer            cap;
> +       acpi_integer            avg_interval;
> +       int                     sensors_valid;
> +       unsigned long           sensors_last_updated;
> +       struct sensor_device_attribute  sensors[NUM_SENSORS];
> +       int                     num_sensors;
> +       int                     trip[2];
> +};
> +
> +struct ro_sensor_template {
> +       char *label;
> +       ssize_t (*show)(struct device *dev,
> +                       struct device_attribute *devattr,
> +                       char *buf);
> +       int index;
> +};
> +
> +struct rw_sensor_template {
> +       char *label;
> +       ssize_t (*show)(struct device *dev,
> +                       struct device_attribute *devattr,
> +                       char *buf);
> +       ssize_t (*set)(struct device *dev,
> +                      struct device_attribute *devattr,
> +                      const char *buf, size_t count);
> +       int index;
> +};
> +
> +/* Averaging interval */
> +static int update_avg_interval(struct acpi_power_meter_resource *resource)
> +{
> +       unsigned long long data;
> +       acpi_status status;
> +
> +       status = acpi_evaluate_integer(resource->acpi_dev->handle, "_GAI",
> +                                      NULL, &data);
> +       if (ACPI_FAILURE(status)) {
> +               ACPI_EXCEPTION((AE_INFO, status, "Evaluating _GAI"));
> +               return -ENODEV;
> +       }
> +
> +       resource->avg_interval = data;
> +       return 0;
> +}
> +
> +static ssize_t show_avg_interval(struct device *dev,
> +                                struct device_attribute *devattr,
> +                                char *buf)
> +{
> +       struct acpi_device *acpi_dev = to_acpi_device(dev);
> +       struct acpi_power_meter_resource *resource = acpi_dev->driver_data;
> +
> +       mutex_lock(&resource->lock);
> +       update_avg_interval(resource);
> +       mutex_unlock(&resource->lock);
> +
> +       return sprintf(buf, "%llu\n", resource->avg_interval);
> +}
> +
> +static ssize_t set_avg_interval(struct device *dev,
> +                               struct device_attribute *devattr,
> +                               const char *buf, size_t count)
> +{
> +       struct acpi_device *acpi_dev = to_acpi_device(dev);
> +       struct acpi_power_meter_resource *resource = acpi_dev->driver_data;
> +       union acpi_object arg0 = { ACPI_TYPE_INTEGER };
> +       struct acpi_object_list args = { 1, &arg0 };
> +       int res;
> +       unsigned long temp;
> +       unsigned long long data;
> +       acpi_status status;
> +
> +       res = strict_strtoul(buf, 10, &temp);
> +       if (res)
> +               return res;
> +
> +       if (temp > resource->caps.max_avg_interval ||
> +           temp < resource->caps.min_avg_interval)
> +               return -EINVAL;
> +       arg0.integer.value = temp;
> +
> +       mutex_lock(&resource->lock);
> +       status = acpi_evaluate_integer(resource->acpi_dev->handle, "_PAI",
> +                                      &args, &data);
> +       if (!ACPI_FAILURE(status))
> +               resource->avg_interval = temp;
> +       mutex_unlock(&resource->lock);
> +
> +       if (ACPI_FAILURE(status)) {
> +               ACPI_EXCEPTION((AE_INFO, status, "Evaluating _PAI"));
> +               return -EINVAL;
> +       }
> +
> +       if (data)
> +               return -EINVAL;
> +
> +       return count;
> +}
> +
> +/* Cap functions */
> +static int update_cap(struct acpi_power_meter_resource *resource)
> +{
> +       unsigned long long data;
> +       acpi_status status;
> +
> +       status = acpi_evaluate_integer(resource->acpi_dev->handle, "_GHL",
> +                                      NULL, &data);
> +       if (ACPI_FAILURE(status)) {
> +               ACPI_EXCEPTION((AE_INFO, status, "Evaluating _GHL"));
> +               return -ENODEV;
> +       }
> +
> +       resource->cap = data;
> +       return 0;
> +}
> +
> +static ssize_t show_cap(struct device *dev,
> +                       struct device_attribute *devattr,
> +                       char *buf)
> +{
> +       struct acpi_device *acpi_dev = to_acpi_device(dev);
> +       struct acpi_power_meter_resource *resource = acpi_dev->driver_data;
> +
> +       mutex_lock(&resource->lock);
> +       update_cap(resource);
> +       mutex_unlock(&resource->lock);
> +
> +       return sprintf(buf, "%llu\n", resource->cap * 1000);
> +}
> +
> +static ssize_t set_cap(struct device *dev, struct device_attribute *devattr,
> +                      const char *buf, size_t count)
> +{
> +       struct acpi_device *acpi_dev = to_acpi_device(dev);
> +       struct acpi_power_meter_resource *resource = acpi_dev->driver_data;
> +       union acpi_object arg0 = { ACPI_TYPE_INTEGER };
> +       struct acpi_object_list args = { 1, &arg0 };
> +       int res;
> +       unsigned long temp;
> +       unsigned long long data;
> +       acpi_status status;
> +
> +       res = strict_strtoul(buf, 10, &temp);
> +       if (res)
> +               return res;
> +
> +       temp /= 1000;
> +       if (temp > resource->caps.max_cap || temp < resource->caps.min_cap)
> +               return -EINVAL;
> +       arg0.integer.value = temp;
> +
> +       mutex_lock(&resource->lock);
> +       status = acpi_evaluate_integer(resource->acpi_dev->handle, "_SHL",
> +                                      &args, &data);
> +       if (!ACPI_FAILURE(status))
> +               resource->cap = temp;
> +       mutex_unlock(&resource->lock);
> +
> +       if (ACPI_FAILURE(status)) {
> +               ACPI_EXCEPTION((AE_INFO, status, "Evaluating _SHL"));
> +               return -EINVAL;
> +       }
> +
> +       if (data)
> +               return -EINVAL;
> +
> +       return count;
> +}
> +
> +/* Power meter trip points */
> +static int set_acpi_trip(struct acpi_power_meter_resource *resource)
> +{
> +       union acpi_object arg_objs[] = {
> +               {ACPI_TYPE_INTEGER},
> +               {ACPI_TYPE_INTEGER}
> +       };
> +       struct acpi_object_list args = { 2, arg_objs };
> +       unsigned long long data;
> +       acpi_status status;
> +
> +       /* Both trip levels must be set */
> +       if (resource->trip[0] < 0 || resource->trip[1] < 0)
> +               return 0;
> +
> +       /* This driver stores min, max; ACPI wants max, min. */
> +       arg_objs[0].integer.value = resource->trip[1];
> +       arg_objs[1].integer.value = resource->trip[0];
> +
> +       status = acpi_evaluate_integer(resource->acpi_dev->handle, "_PTP",
> +                                      &args, &data);
> +       if (ACPI_FAILURE(status)) {
> +               ACPI_EXCEPTION((AE_INFO, status, "Evaluating _PTP"));
> +               return -EINVAL;
> +       }
> +
> +       return data;
> +}
> +
> +static ssize_t set_trip(struct device *dev, struct device_attribute *devattr,
> +                       const char *buf, size_t count)
> +{
> +       struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr);
> +       struct acpi_device *acpi_dev = to_acpi_device(dev);
> +       struct acpi_power_meter_resource *resource = acpi_dev->driver_data;
> +       int res;
> +       unsigned long temp;
> +
> +       res = strict_strtoul(buf, 10, &temp);
> +       if (res)
> +               return res;
> +
> +       temp /= 1000;
> +       if (temp < 0)
> +               return -EINVAL;
> +
> +       mutex_lock(&resource->lock);
> +       resource->trip[attr->index - 7] = temp;
> +       res = set_acpi_trip(resource);
> +       mutex_unlock(&resource->lock);
> +
> +       if (res)
> +               return res;
> +
> +       return count;
> +}
> +
> +/* Power meter */
> +static int update_meter(struct acpi_power_meter_resource *resource)
> +{
> +       unsigned long long data;
> +       acpi_status status;
> +       unsigned long local_jiffies = jiffies;
> +
> +       if (time_before(local_jiffies, resource->sensors_last_updated +
> +                       msecs_to_jiffies(resource->caps.sampling_time)) &&
> +                       resource->sensors_valid)
> +               return 0;
> +
> +       status = acpi_evaluate_integer(resource->acpi_dev->handle, "_PMM",
> +                                      NULL, &data);
> +       if (ACPI_FAILURE(status)) {
> +               ACPI_EXCEPTION((AE_INFO, status, "Evaluating _PMM"));
> +               return -ENODEV;
> +       }
> +
> +       resource->power = data;
> +       resource->sensors_valid = 1;
> +       resource->sensors_last_updated = jiffies;
> +       return 0;
> +}
> +
> +static ssize_t show_power(struct device *dev,
> +                         struct device_attribute *devattr,
> +                         char *buf)
> +{
> +       struct acpi_device *acpi_dev = to_acpi_device(dev);
> +       struct acpi_power_meter_resource *resource = acpi_dev->driver_data;
> +
> +       mutex_lock(&resource->lock);
> +       update_meter(resource);
> +       mutex_unlock(&resource->lock);
> +
> +       return sprintf(buf, "%llu\n", resource->power * 1000);
> +}
> +
> +/* Miscellaneous */
> +static ssize_t show_val(struct device *dev,
> +                       struct device_attribute *devattr,
> +                       char *buf)
> +{
> +       struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr);
> +       struct acpi_device *acpi_dev = to_acpi_device(dev);
> +       struct acpi_power_meter_resource *resource = acpi_dev->driver_data;
> +       acpi_integer val = 0;
> +
> +       switch (attr->index) {
> +       case 0:
> +               val = resource->caps.min_avg_interval;
> +               break;
> +       case 1:
> +               val = resource->caps.max_avg_interval;
> +               break;
> +       case 2:
> +               val = resource->caps.min_cap * 1000;
> +               break;
> +       case 3:
> +               val = resource->caps.max_cap * 1000;
> +               break;
> +       case 4:
> +               if (resource->caps.hysteresis == UNKNOWN_HYSTERESIS)
> +                       return sprintf(buf, "unknown\n");
> +
> +               val = resource->caps.hysteresis * 1000;
> +               break;
> +       case 5:
> +               if (resource->caps.flags & POWER_METER_IS_BATTERY)
> +                       val = 1;
> +               else
> +                       val = 0;
> +               break;
> +       case 6:
> +               if (resource->power > resource->cap)
> +                       val = 1;
> +               else
> +                       val = 0;
> +               break;
> +       case 7:
> +       case 8:
> +               if (resource->trip[attr->index - 7] < 0)
> +                       return sprintf(buf, "unknown\n");
> +
> +               val = resource->trip[attr->index - 7] * 1000;
> +               break;
> +       default:
> +               BUG();
> +       }
> +
> +       return sprintf(buf, "%llu\n", val);
> +}
> +
> +static ssize_t show_accuracy(struct device *dev,
> +                            struct device_attribute *devattr,
> +                            char *buf)
> +{
> +       struct acpi_device *acpi_dev = to_acpi_device(dev);
> +       struct acpi_power_meter_resource *resource = acpi_dev->driver_data;
> +       unsigned int acc = resource->caps.accuracy;
> +
> +       return sprintf(buf, "%u.%u%%\n", acc / 1000, acc % 1000);
> +}
> +
> +static ssize_t show_name(struct device *dev,
> +                        struct device_attribute *devattr,
> +                        char *buf)
> +{
> +       return sprintf(buf, "%s\n", ACPI_POWER_METER_NAME);
> +}
> +
> +/* Sensor descriptions.  If you add a sensor, update NUM_SENSORS above! */
> +static struct ro_sensor_template meter_ro_attrs[] = {
> +{"power1_average", show_power, 0},
> +{"power1_accuracy", show_accuracy, 0},
> +{"power1_average_interval_min", show_val, 0},
> +{"power1_average_interval_max", show_val, 1},
> +{"power1_is_battery", show_val, 5},
> +{NULL, NULL, 0},
> +};
> +
> +static struct rw_sensor_template meter_rw_attrs[] = {
> +{"power1_average_interval", show_avg_interval, set_avg_interval, 0},
> +{NULL, NULL, NULL, 0},
> +};
> +
> +static struct ro_sensor_template misc_cap_attrs[] = {
> +{"power1_cap_min", show_val, 2},
> +{"power1_cap_max", show_val, 3},
> +{"power1_cap_hyst", show_val, 4},
> +{"power1_alarm", show_val, 6},
> +{NULL, NULL, 0},
> +};
> +
> +static struct ro_sensor_template ro_cap_attrs[] = {
> +{"power1_cap", show_cap, 0},
> +{NULL, NULL, 0},
> +};
> +
> +static struct rw_sensor_template rw_cap_attrs[] = {
> +{"power1_cap", show_cap, set_cap, 0},
> +{NULL, NULL, NULL, 0},
> +};
> +
> +static struct rw_sensor_template trip_attrs[] = {
> +{"power1_average_min", show_val, set_trip, 7},
> +{"power1_average_max", show_val, set_trip, 8},
> +{NULL, NULL, NULL, 0},
> +};
> +
> +static struct ro_sensor_template misc_attrs[] = {
> +{"name", show_name, 0},
> +{NULL, NULL, 0},
> +};
> +
> +/* Registration and deregistration */
> +static int register_ro_attrs(struct acpi_power_meter_resource *resource,
> +                            struct ro_sensor_template *ro)
> +{
> +       struct device *dev = &resource->acpi_dev->dev;
> +       struct sensor_device_attribute *sensors =
> +               &resource->sensors[resource->num_sensors];
> +       int res;
> +
> +       while (ro->label) {
> +               sensors->dev_attr.attr.name = ro->label;
> +               sensors->dev_attr.attr.mode = S_IRUGO;
> +               sensors->dev_attr.show = ro->show;
> +               sensors->index = ro->index;
> +
> +               res = device_create_file(dev, &sensors->dev_attr);
> +               if (res) {
> +                       sensors->dev_attr.attr.name = NULL;
> +                       goto error;
> +               }
> +               sensors++;
> +               resource->num_sensors++;
> +               ro++;
> +       }
> +
> +error:
> +       return res;
> +}
> +
> +static int register_rw_attrs(struct acpi_power_meter_resource *resource,
> +                            struct rw_sensor_template *rw)
> +{
> +       struct device *dev = &resource->acpi_dev->dev;
> +       struct sensor_device_attribute *sensors =
> +               &resource->sensors[resource->num_sensors];
> +       int res;
> +
> +       while (rw->label) {
> +               sensors->dev_attr.attr.name = rw->label;
> +               sensors->dev_attr.attr.mode = S_IRUGO | S_IWUSR;
> +               sensors->dev_attr.show = rw->show;
> +               sensors->dev_attr.store = rw->set;
> +               sensors->index = rw->index;
> +
> +               res = device_create_file(dev, &sensors->dev_attr);
> +               if (res) {
> +                       sensors->dev_attr.attr.name = NULL;
> +                       goto error;
> +               }
> +               sensors++;
> +               resource->num_sensors++;
> +               rw++;
> +       }
> +
> +error:
> +       return res;
> +}
> +
> +static void remove_attrs(struct acpi_power_meter_resource *resource)
> +{
> +       int i;
> +
> +       for (i = 0; i < resource->num_sensors; i++) {
> +               if (!resource->sensors[i].dev_attr.attr.name)
> +                       continue;
> +               device_remove_file(&resource->acpi_dev->dev,
> +                                  &resource->sensors[i].dev_attr);
> +       }
> +
> +       resource->num_sensors = 0;
> +}
> +
> +static int setup_attrs(struct acpi_power_meter_resource *resource)
> +{
> +       int res = 0;
> +
> +       if (resource->caps.flags & POWER_METER_CAN_MEASURE) {
> +               res = register_ro_attrs(resource, meter_ro_attrs);
> +               if (res)
> +                       goto error;
> +               res = register_rw_attrs(resource, meter_rw_attrs);
> +               if (res)
> +                       goto error;
> +       }
> +
> +       if (resource->caps.flags & POWER_METER_CAN_CAP) {
> +               if (resource->caps.configurable_cap) {
> +                       res = register_rw_attrs(resource, rw_cap_attrs);
> +                       if (res)
> +                               goto error;
> +               } else {
> +                       res = register_ro_attrs(resource, ro_cap_attrs);
> +                       if (res)
> +                               goto error;
> +               }
> +               res = register_ro_attrs(resource, misc_cap_attrs);
> +               if (res)
> +                       goto error;
> +       }
> +
> +       if (resource->caps.flags & POWER_METER_CAN_TRIP) {
> +               res = register_rw_attrs(resource, trip_attrs);
> +               if (res)
> +                       goto error;
> +       }
> +
> +       res = register_ro_attrs(resource, misc_attrs);
> +       if (res)
> +               goto error;
> +
> +       return res;
> +error:
> +       remove_attrs(resource);
> +       return res;
> +}
> +
> +static int read_capabilities(struct acpi_power_meter_resource *resource)
> +{
> +       int res = 0;
> +       struct acpi_buffer buffer = { ACPI_ALLOCATE_BUFFER, NULL };
> +       struct acpi_buffer state = { 0, NULL };
> +       struct acpi_buffer format = { sizeof("NNNNNNNNNNN"), "NNNNNNNNNNN" };
> +       union acpi_object *pss;
> +       acpi_status status;
> +
> +       status = acpi_evaluate_object(resource->acpi_dev->handle, "_PMC", NULL,
> +                                     &buffer);
> +       if (ACPI_FAILURE(status)) {
> +               ACPI_EXCEPTION((AE_INFO, status, "Evaluating _PMC"));
> +               return -ENODEV;
> +       }
> +
> +       pss = buffer.pointer;
> +       if (!pss ||
> +           pss->type != ACPI_TYPE_PACKAGE ||
> +           pss->package.count != 14) {
> +               dev_err(&resource->acpi_dev->dev, PREFIX "Invalid _PMC data\n");
> +               res = -EFAULT;
> +               goto end;
> +       }
> +
> +       state.length = sizeof(struct acpi_power_meter_capabilities);
> +       state.pointer = &resource->caps;
> +
> +       status = acpi_extract_package(pss, &format, &state);
> +       if (ACPI_FAILURE(status)) {
> +               ACPI_EXCEPTION((AE_INFO, status, "Invalid data"));
> +               res = -EFAULT;
> +               goto end;
> +       }
> +
> +       if (resource->caps.units) {
> +               dev_err(&resource->acpi_dev->dev, PREFIX "Unknown unit %llu.\n",
> +                       resource->caps.units);
> +               res = -EINVAL;
> +               goto end;
> +       }
> +
> +       dev_info(&resource->acpi_dev->dev, "Found ACPI power meter.\n");
> +end:
> +       kfree(buffer.pointer);
> +       return res;
> +}
> +
> +/* Handle ACPI event notifications */
> +static void acpi_power_meter_notify(struct acpi_device *device, u32 event)
> +{
> +       struct acpi_power_meter_resource *resource;
> +       int res;
> +
> +       if (!device || !acpi_driver_data(device))
> +               return;
> +
> +       resource = acpi_driver_data(device);
> +
> +       mutex_lock(&resource->lock);
> +       switch (event) {
> +       case METER_NOTIFY_CONFIG:
> +               res = read_capabilities(resource);
> +               if (res)
> +                       break;
> +
> +               remove_attrs(resource);
> +               setup_attrs(resource);
> +               break;
> +       case METER_NOTIFY_TRIP:
> +               update_meter(resource);
> +               break;
> +       case METER_NOTIFY_CAP:
> +               update_cap(resource);
> +               break;
> +       case METER_NOTIFY_INTERVAL:
> +               update_avg_interval(resource);
> +               break;
> +       case METER_NOTIFY_CAPPING:
> +               dev_info(&device->dev, "Capping in progress.\n");
> +               break;
> +       default:
> +               BUG();
> +       }
> +       mutex_unlock(&resource->lock);
> +}
> +
> +static int acpi_power_meter_add(struct acpi_device *device)
> +{
> +       int res;
> +       struct acpi_power_meter_resource *resource;
> +
> +       if (!device)
> +               return -EINVAL;
> +
> +       resource = kzalloc(sizeof(struct acpi_power_meter_resource),
> +                          GFP_KERNEL);
> +       if (!resource)
> +               return -ENOMEM;
> +
> +       resource->sensors_valid = 0;
> +       resource->acpi_dev = device;
> +       mutex_init(&resource->lock);
> +       strcpy(acpi_device_name(device), ACPI_POWER_METER_DEVICE_NAME);
> +       strcpy(acpi_device_class(device), ACPI_POWER_METER_CLASS);
> +       device->driver_data = resource;
> +
> +       res = read_capabilities(resource);
> +       if (res)
> +               goto exit_free;
> +
> +       resource->trip[0] = resource->trip[1] = -1;
> +
> +       res = setup_attrs(resource);
> +       if (res)
> +               goto exit_free;
> +
> +       resource->hwmon_dev = hwmon_device_register(&device->dev);
> +       if (IS_ERR(resource->hwmon_dev)) {
> +               res = PTR_ERR(resource->hwmon_dev);
> +               goto exit_remove;
> +       }
> +
> +       res = 0;
> +       goto exit;
> +
> +exit_remove:
> +       remove_attrs(resource);
> +exit_free:
> +       kfree(resource);
> +exit:
> +       return res;
> +}
> +
> +static int acpi_power_meter_remove(struct acpi_device *device, int type)
> +{
> +       struct acpi_power_meter_resource *resource;
> +
> +       if (!device || !acpi_driver_data(device))
> +               return -EINVAL;
> +
> +       resource = acpi_driver_data(device);
> +       hwmon_device_unregister(resource->hwmon_dev);
> +
> +       remove_attrs(resource);
> +
> +       kfree(resource);
> +       return 0;
> +}
> +
> +static int acpi_power_meter_resume(struct acpi_device *device)
> +{
> +       struct acpi_power_meter_resource *resource;
> +
> +       if (!device || !acpi_driver_data(device))
> +               return -EINVAL;
> +
> +       resource = acpi_driver_data(device);
> +       read_capabilities(resource);
> +
> +       return 0;
> +}
> +
> +static struct acpi_driver acpi_power_meter_driver = {
> +       .name = "power_meter",
> +       .class = ACPI_POWER_METER_CLASS,
> +       .ids = power_meter_ids,
> +       .ops = {
> +               .add = acpi_power_meter_add,
> +               .remove = acpi_power_meter_remove,
> +               .resume = acpi_power_meter_resume,
> +               .notify = acpi_power_meter_notify,
> +               },
> +};
> +
> +/* Module init/exit routines */
> +
> +static int __init acpi_power_meter_init(void)
> +{
> +       int result;
> +
> +       result = acpi_bus_register_driver(&acpi_power_meter_driver);
> +       if (result < 0)
> +               return -ENODEV;
> +
> +       return 0;
> +}
> +
> +static void __exit acpi_power_meter_exit(void)
> +{
> +       acpi_bus_unregister_driver(&acpi_power_meter_driver);
> +}
> +
> +MODULE_AUTHOR("Darrick J. Wong <djwong@us.ibm.com>");
> +MODULE_DESCRIPTION("ACPI 4.0 power meter driver");
> +MODULE_LICENSE("GPL");
> +
> +module_init(acpi_power_meter_init);
> +module_exit(acpi_power_meter_exit);
> 
> --
> To unsubscribe from this list: send the line "unsubscribe linux-acpi" in
> the body of a message to majordomo@vger.kernel.org
> More majordomo info at  http://vger.kernel.org/majordomo-info.html

^ permalink raw reply	[flat|nested] 38+ messages in thread

* Re: [lm-sensors] [PATCH 2/2] acpi_power_meter: hwmon driver for
@ 2009-07-27  1:44     ` ykzhao
  0 siblings, 0 replies; 38+ messages in thread
From: ykzhao @ 2009-07-27  1:44 UTC (permalink / raw)
  To: Darrick J. Wong
  Cc: Len Brown, Andrew Morton, linux-kernel, lm-sensors, linux-acpi,
	Zhang, Rui

On Sat, 2009-07-25 at 08:43 +0800, Darrick J. Wong wrote:
> This driver exposes ACPI 4.0 compliant power meters as hardware monitoring
> devices.
It seems OK to me. But it seems that some attributes can't be obtained
by using the hwmon sys I/F.
   a. Can we add a sys I/F that can exports the Power meter capability?
display all the content of _PMC as what we have done in battery
   b. Add a sys I/F that displays the name of device list measured by
the power meter if there exists the _PMD object.

Thanks.

> 
> Signed-off-by: Darrick J. Wong <djwong@us.ibm.com>
> ---
>  Documentation/hwmon/acpi_power_meter |   25 +
>  drivers/acpi/Kconfig                 |   12 +
>  drivers/acpi/Makefile                |    1
>  drivers/acpi/power_meter.c           |  776 ++++++++++++++++++++++++++++++++++
>  4 files changed, 814 insertions(+), 0 deletions(-)
>  create mode 100644 Documentation/hwmon/acpi_power_meter
>  create mode 100644 drivers/acpi/power_meter.c
> 
> 
> diff --git a/Documentation/hwmon/acpi_power_meter b/Documentation/hwmon/acpi_power_meter
> new file mode 100644
> index 0000000..0af2e9a
> --- /dev/null
> +++ b/Documentation/hwmon/acpi_power_meter
> @@ -0,0 +1,25 @@
> +Kernel driver power_meter
> +============> +
> +This driver talks to ACPI 4.0 power meters.
> +
> +Supported systems:
> +  * Any recent system with ACPI 4.0.
> +    Prefix: 'power_meter'
> +    Datasheet: http://acpi.info/, section 10.4.
> +
> +Author: Darrick J. Wong
> +
> +Description
> +-----------
> +
> +This driver implements sensor reading support for the power meters exposed in
> +the ACPI 4.0 spec (Chapter 10.4).  These devices have a simple set of
> +features--a power meter that returns average power use over a configurable
> +interval, an optional capping mechanism, and a couple of trip points.
> +
> +Special Features
> +----------------
> +
> +The power[1-*]_is_battery knob indicates if the power supply is a battery.
> +Both power[1-*]_average_{min,max} must be set before the trip points will work.
> diff --git a/drivers/acpi/Kconfig b/drivers/acpi/Kconfig
> index 7ec7d88..8ac801d 100644
> --- a/drivers/acpi/Kconfig
> +++ b/drivers/acpi/Kconfig
> @@ -82,6 +82,18 @@ config ACPI_PROCFS_POWER
> 
>           Say N to delete power /proc/acpi/ directories that have moved to /sys/
> 
> +config ACPI_POWER_METER
> +       tristate "ACPI 4.0 power meter"
> +       depends on HWMON
> +       default m
> +       help
> +         This driver exposes ACPI 4.0 power meters as hardware monitoring
> +         devices.  Say Y (or M) if you have an Intel or AMD computer with
> +         a power meter.
> +
> +         To compile this driver as a module, choose M here:
> +         the module will be called power-meter.
> +
>  config ACPI_SYSFS_POWER
>         bool "Future power /sys interface"
>         select POWER_SUPPLY
> diff --git a/drivers/acpi/Makefile b/drivers/acpi/Makefile
> index 03a985b..82cd49d 100644
> --- a/drivers/acpi/Makefile
> +++ b/drivers/acpi/Makefile
> @@ -56,6 +56,7 @@ obj-$(CONFIG_ACPI_HOTPLUG_MEMORY) += acpi_memhotplug.o
>  obj-$(CONFIG_ACPI_BATTERY)     += battery.o
>  obj-$(CONFIG_ACPI_SBS)         += sbshc.o
>  obj-$(CONFIG_ACPI_SBS)         += sbs.o
> +obj-$(CONFIG_ACPI_POWER_METER) += power_meter.o
> 
>  # processor has its own "processor." module_param namespace
>  processor-y                    := processor_core.o processor_throttling.o
> diff --git a/drivers/acpi/power_meter.c b/drivers/acpi/power_meter.c
> new file mode 100644
> index 0000000..9418bbd
> --- /dev/null
> +++ b/drivers/acpi/power_meter.c
> @@ -0,0 +1,776 @@
> +/*
> + * A hwmon driver for ACPI 4.0 power meters
> + * Copyright (C) 2009 IBM
> + *
> + * Author: Darrick J. Wong <djwong@us.ibm.com>
> + *
> + * This program is free software; you can redistribute it and/or modify
> + * it under the terms of the GNU General Public License as published by
> + * the Free Software Foundation; either version 2 of the License, or
> + * (at your option) any later version.
> + *
> + * This program is distributed in the hope that 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
> + */
> +
> +#include <linux/module.h>
> +#include <linux/hwmon.h>
> +#include <linux/hwmon-sysfs.h>
> +#include <linux/jiffies.h>
> +#include <linux/mutex.h>
> +#include <linux/kdev_t.h>
> +#include <linux/sched.h>
> +#include <linux/time.h>
> +#include <acpi/acpi_drivers.h>
> +#include <acpi/acpi_bus.h>
> +
> +#define ACPI_POWER_METER_NAME          "power_meter"
> +ACPI_MODULE_NAME(ACPI_POWER_METER_NAME);
> +#define ACPI_POWER_METER_DEVICE_NAME   "Power Meter"
> +#define ACPI_POWER_METER_CLASS         "power_meter_resource"
> +
> +#define NUM_SENSORS                    14
> +
> +#define POWER_METER_CAN_MEASURE        (1 << 0)
> +#define POWER_METER_CAN_TRIP   (1 << 1)
> +#define POWER_METER_CAN_CAP    (1 << 2)
> +#define POWER_METER_CAN_NOTIFY (1 << 3)
> +#define POWER_METER_IS_BATTERY (1 << 8)
> +#define UNKNOWN_HYSTERESIS     0xFFFFFFFF
> +
> +#define METER_NOTIFY_CONFIG    0x80
> +#define METER_NOTIFY_TRIP      0x81
> +#define METER_NOTIFY_CAP       0x82
> +#define METER_NOTIFY_CAPPING   0x83
> +#define METER_NOTIFY_INTERVAL  0x84
> +
> +static struct acpi_device_id power_meter_ids[] = {
> +       {"ACPI000D", 0},
> +       {"", 0},
> +};
> +MODULE_DEVICE_TABLE(acpi, power_meter_ids);
> +
> +struct acpi_power_meter_capabilities {
> +       acpi_integer            flags;
> +       acpi_integer            units;
> +       acpi_integer            type;
> +       acpi_integer            accuracy;
> +       acpi_integer            sampling_time;
> +       acpi_integer            min_avg_interval;
> +       acpi_integer            max_avg_interval;
> +       acpi_integer            hysteresis;
> +       acpi_integer            configurable_cap;
> +       acpi_integer            min_cap;
> +       acpi_integer            max_cap;
> +};
> +
> +struct acpi_power_meter_resource {
> +       struct acpi_device      *acpi_dev;
> +       acpi_bus_id             name;
> +       struct mutex            lock;
> +       struct device           *hwmon_dev;
> +       struct acpi_power_meter_capabilities    caps;
> +       acpi_integer            power;
> +       acpi_integer            cap;
> +       acpi_integer            avg_interval;
> +       int                     sensors_valid;
> +       unsigned long           sensors_last_updated;
> +       struct sensor_device_attribute  sensors[NUM_SENSORS];
> +       int                     num_sensors;
> +       int                     trip[2];
> +};
> +
> +struct ro_sensor_template {
> +       char *label;
> +       ssize_t (*show)(struct device *dev,
> +                       struct device_attribute *devattr,
> +                       char *buf);
> +       int index;
> +};
> +
> +struct rw_sensor_template {
> +       char *label;
> +       ssize_t (*show)(struct device *dev,
> +                       struct device_attribute *devattr,
> +                       char *buf);
> +       ssize_t (*set)(struct device *dev,
> +                      struct device_attribute *devattr,
> +                      const char *buf, size_t count);
> +       int index;
> +};
> +
> +/* Averaging interval */
> +static int update_avg_interval(struct acpi_power_meter_resource *resource)
> +{
> +       unsigned long long data;
> +       acpi_status status;
> +
> +       status = acpi_evaluate_integer(resource->acpi_dev->handle, "_GAI",
> +                                      NULL, &data);
> +       if (ACPI_FAILURE(status)) {
> +               ACPI_EXCEPTION((AE_INFO, status, "Evaluating _GAI"));
> +               return -ENODEV;
> +       }
> +
> +       resource->avg_interval = data;
> +       return 0;
> +}
> +
> +static ssize_t show_avg_interval(struct device *dev,
> +                                struct device_attribute *devattr,
> +                                char *buf)
> +{
> +       struct acpi_device *acpi_dev = to_acpi_device(dev);
> +       struct acpi_power_meter_resource *resource = acpi_dev->driver_data;
> +
> +       mutex_lock(&resource->lock);
> +       update_avg_interval(resource);
> +       mutex_unlock(&resource->lock);
> +
> +       return sprintf(buf, "%llu\n", resource->avg_interval);
> +}
> +
> +static ssize_t set_avg_interval(struct device *dev,
> +                               struct device_attribute *devattr,
> +                               const char *buf, size_t count)
> +{
> +       struct acpi_device *acpi_dev = to_acpi_device(dev);
> +       struct acpi_power_meter_resource *resource = acpi_dev->driver_data;
> +       union acpi_object arg0 = { ACPI_TYPE_INTEGER };
> +       struct acpi_object_list args = { 1, &arg0 };
> +       int res;
> +       unsigned long temp;
> +       unsigned long long data;
> +       acpi_status status;
> +
> +       res = strict_strtoul(buf, 10, &temp);
> +       if (res)
> +               return res;
> +
> +       if (temp > resource->caps.max_avg_interval ||
> +           temp < resource->caps.min_avg_interval)
> +               return -EINVAL;
> +       arg0.integer.value = temp;
> +
> +       mutex_lock(&resource->lock);
> +       status = acpi_evaluate_integer(resource->acpi_dev->handle, "_PAI",
> +                                      &args, &data);
> +       if (!ACPI_FAILURE(status))
> +               resource->avg_interval = temp;
> +       mutex_unlock(&resource->lock);
> +
> +       if (ACPI_FAILURE(status)) {
> +               ACPI_EXCEPTION((AE_INFO, status, "Evaluating _PAI"));
> +               return -EINVAL;
> +       }
> +
> +       if (data)
> +               return -EINVAL;
> +
> +       return count;
> +}
> +
> +/* Cap functions */
> +static int update_cap(struct acpi_power_meter_resource *resource)
> +{
> +       unsigned long long data;
> +       acpi_status status;
> +
> +       status = acpi_evaluate_integer(resource->acpi_dev->handle, "_GHL",
> +                                      NULL, &data);
> +       if (ACPI_FAILURE(status)) {
> +               ACPI_EXCEPTION((AE_INFO, status, "Evaluating _GHL"));
> +               return -ENODEV;
> +       }
> +
> +       resource->cap = data;
> +       return 0;
> +}
> +
> +static ssize_t show_cap(struct device *dev,
> +                       struct device_attribute *devattr,
> +                       char *buf)
> +{
> +       struct acpi_device *acpi_dev = to_acpi_device(dev);
> +       struct acpi_power_meter_resource *resource = acpi_dev->driver_data;
> +
> +       mutex_lock(&resource->lock);
> +       update_cap(resource);
> +       mutex_unlock(&resource->lock);
> +
> +       return sprintf(buf, "%llu\n", resource->cap * 1000);
> +}
> +
> +static ssize_t set_cap(struct device *dev, struct device_attribute *devattr,
> +                      const char *buf, size_t count)
> +{
> +       struct acpi_device *acpi_dev = to_acpi_device(dev);
> +       struct acpi_power_meter_resource *resource = acpi_dev->driver_data;
> +       union acpi_object arg0 = { ACPI_TYPE_INTEGER };
> +       struct acpi_object_list args = { 1, &arg0 };
> +       int res;
> +       unsigned long temp;
> +       unsigned long long data;
> +       acpi_status status;
> +
> +       res = strict_strtoul(buf, 10, &temp);
> +       if (res)
> +               return res;
> +
> +       temp /= 1000;
> +       if (temp > resource->caps.max_cap || temp < resource->caps.min_cap)
> +               return -EINVAL;
> +       arg0.integer.value = temp;
> +
> +       mutex_lock(&resource->lock);
> +       status = acpi_evaluate_integer(resource->acpi_dev->handle, "_SHL",
> +                                      &args, &data);
> +       if (!ACPI_FAILURE(status))
> +               resource->cap = temp;
> +       mutex_unlock(&resource->lock);
> +
> +       if (ACPI_FAILURE(status)) {
> +               ACPI_EXCEPTION((AE_INFO, status, "Evaluating _SHL"));
> +               return -EINVAL;
> +       }
> +
> +       if (data)
> +               return -EINVAL;
> +
> +       return count;
> +}
> +
> +/* Power meter trip points */
> +static int set_acpi_trip(struct acpi_power_meter_resource *resource)
> +{
> +       union acpi_object arg_objs[] = {
> +               {ACPI_TYPE_INTEGER},
> +               {ACPI_TYPE_INTEGER}
> +       };
> +       struct acpi_object_list args = { 2, arg_objs };
> +       unsigned long long data;
> +       acpi_status status;
> +
> +       /* Both trip levels must be set */
> +       if (resource->trip[0] < 0 || resource->trip[1] < 0)
> +               return 0;
> +
> +       /* This driver stores min, max; ACPI wants max, min. */
> +       arg_objs[0].integer.value = resource->trip[1];
> +       arg_objs[1].integer.value = resource->trip[0];
> +
> +       status = acpi_evaluate_integer(resource->acpi_dev->handle, "_PTP",
> +                                      &args, &data);
> +       if (ACPI_FAILURE(status)) {
> +               ACPI_EXCEPTION((AE_INFO, status, "Evaluating _PTP"));
> +               return -EINVAL;
> +       }
> +
> +       return data;
> +}
> +
> +static ssize_t set_trip(struct device *dev, struct device_attribute *devattr,
> +                       const char *buf, size_t count)
> +{
> +       struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr);
> +       struct acpi_device *acpi_dev = to_acpi_device(dev);
> +       struct acpi_power_meter_resource *resource = acpi_dev->driver_data;
> +       int res;
> +       unsigned long temp;
> +
> +       res = strict_strtoul(buf, 10, &temp);
> +       if (res)
> +               return res;
> +
> +       temp /= 1000;
> +       if (temp < 0)
> +               return -EINVAL;
> +
> +       mutex_lock(&resource->lock);
> +       resource->trip[attr->index - 7] = temp;
> +       res = set_acpi_trip(resource);
> +       mutex_unlock(&resource->lock);
> +
> +       if (res)
> +               return res;
> +
> +       return count;
> +}
> +
> +/* Power meter */
> +static int update_meter(struct acpi_power_meter_resource *resource)
> +{
> +       unsigned long long data;
> +       acpi_status status;
> +       unsigned long local_jiffies = jiffies;
> +
> +       if (time_before(local_jiffies, resource->sensors_last_updated +
> +                       msecs_to_jiffies(resource->caps.sampling_time)) &&
> +                       resource->sensors_valid)
> +               return 0;
> +
> +       status = acpi_evaluate_integer(resource->acpi_dev->handle, "_PMM",
> +                                      NULL, &data);
> +       if (ACPI_FAILURE(status)) {
> +               ACPI_EXCEPTION((AE_INFO, status, "Evaluating _PMM"));
> +               return -ENODEV;
> +       }
> +
> +       resource->power = data;
> +       resource->sensors_valid = 1;
> +       resource->sensors_last_updated = jiffies;
> +       return 0;
> +}
> +
> +static ssize_t show_power(struct device *dev,
> +                         struct device_attribute *devattr,
> +                         char *buf)
> +{
> +       struct acpi_device *acpi_dev = to_acpi_device(dev);
> +       struct acpi_power_meter_resource *resource = acpi_dev->driver_data;
> +
> +       mutex_lock(&resource->lock);
> +       update_meter(resource);
> +       mutex_unlock(&resource->lock);
> +
> +       return sprintf(buf, "%llu\n", resource->power * 1000);
> +}
> +
> +/* Miscellaneous */
> +static ssize_t show_val(struct device *dev,
> +                       struct device_attribute *devattr,
> +                       char *buf)
> +{
> +       struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr);
> +       struct acpi_device *acpi_dev = to_acpi_device(dev);
> +       struct acpi_power_meter_resource *resource = acpi_dev->driver_data;
> +       acpi_integer val = 0;
> +
> +       switch (attr->index) {
> +       case 0:
> +               val = resource->caps.min_avg_interval;
> +               break;
> +       case 1:
> +               val = resource->caps.max_avg_interval;
> +               break;
> +       case 2:
> +               val = resource->caps.min_cap * 1000;
> +               break;
> +       case 3:
> +               val = resource->caps.max_cap * 1000;
> +               break;
> +       case 4:
> +               if (resource->caps.hysteresis = UNKNOWN_HYSTERESIS)
> +                       return sprintf(buf, "unknown\n");
> +
> +               val = resource->caps.hysteresis * 1000;
> +               break;
> +       case 5:
> +               if (resource->caps.flags & POWER_METER_IS_BATTERY)
> +                       val = 1;
> +               else
> +                       val = 0;
> +               break;
> +       case 6:
> +               if (resource->power > resource->cap)
> +                       val = 1;
> +               else
> +                       val = 0;
> +               break;
> +       case 7:
> +       case 8:
> +               if (resource->trip[attr->index - 7] < 0)
> +                       return sprintf(buf, "unknown\n");
> +
> +               val = resource->trip[attr->index - 7] * 1000;
> +               break;
> +       default:
> +               BUG();
> +       }
> +
> +       return sprintf(buf, "%llu\n", val);
> +}
> +
> +static ssize_t show_accuracy(struct device *dev,
> +                            struct device_attribute *devattr,
> +                            char *buf)
> +{
> +       struct acpi_device *acpi_dev = to_acpi_device(dev);
> +       struct acpi_power_meter_resource *resource = acpi_dev->driver_data;
> +       unsigned int acc = resource->caps.accuracy;
> +
> +       return sprintf(buf, "%u.%u%%\n", acc / 1000, acc % 1000);
> +}
> +
> +static ssize_t show_name(struct device *dev,
> +                        struct device_attribute *devattr,
> +                        char *buf)
> +{
> +       return sprintf(buf, "%s\n", ACPI_POWER_METER_NAME);
> +}
> +
> +/* Sensor descriptions.  If you add a sensor, update NUM_SENSORS above! */
> +static struct ro_sensor_template meter_ro_attrs[] = {
> +{"power1_average", show_power, 0},
> +{"power1_accuracy", show_accuracy, 0},
> +{"power1_average_interval_min", show_val, 0},
> +{"power1_average_interval_max", show_val, 1},
> +{"power1_is_battery", show_val, 5},
> +{NULL, NULL, 0},
> +};
> +
> +static struct rw_sensor_template meter_rw_attrs[] = {
> +{"power1_average_interval", show_avg_interval, set_avg_interval, 0},
> +{NULL, NULL, NULL, 0},
> +};
> +
> +static struct ro_sensor_template misc_cap_attrs[] = {
> +{"power1_cap_min", show_val, 2},
> +{"power1_cap_max", show_val, 3},
> +{"power1_cap_hyst", show_val, 4},
> +{"power1_alarm", show_val, 6},
> +{NULL, NULL, 0},
> +};
> +
> +static struct ro_sensor_template ro_cap_attrs[] = {
> +{"power1_cap", show_cap, 0},
> +{NULL, NULL, 0},
> +};
> +
> +static struct rw_sensor_template rw_cap_attrs[] = {
> +{"power1_cap", show_cap, set_cap, 0},
> +{NULL, NULL, NULL, 0},
> +};
> +
> +static struct rw_sensor_template trip_attrs[] = {
> +{"power1_average_min", show_val, set_trip, 7},
> +{"power1_average_max", show_val, set_trip, 8},
> +{NULL, NULL, NULL, 0},
> +};
> +
> +static struct ro_sensor_template misc_attrs[] = {
> +{"name", show_name, 0},
> +{NULL, NULL, 0},
> +};
> +
> +/* Registration and deregistration */
> +static int register_ro_attrs(struct acpi_power_meter_resource *resource,
> +                            struct ro_sensor_template *ro)
> +{
> +       struct device *dev = &resource->acpi_dev->dev;
> +       struct sensor_device_attribute *sensors > +               &resource->sensors[resource->num_sensors];
> +       int res;
> +
> +       while (ro->label) {
> +               sensors->dev_attr.attr.name = ro->label;
> +               sensors->dev_attr.attr.mode = S_IRUGO;
> +               sensors->dev_attr.show = ro->show;
> +               sensors->index = ro->index;
> +
> +               res = device_create_file(dev, &sensors->dev_attr);
> +               if (res) {
> +                       sensors->dev_attr.attr.name = NULL;
> +                       goto error;
> +               }
> +               sensors++;
> +               resource->num_sensors++;
> +               ro++;
> +       }
> +
> +error:
> +       return res;
> +}
> +
> +static int register_rw_attrs(struct acpi_power_meter_resource *resource,
> +                            struct rw_sensor_template *rw)
> +{
> +       struct device *dev = &resource->acpi_dev->dev;
> +       struct sensor_device_attribute *sensors > +               &resource->sensors[resource->num_sensors];
> +       int res;
> +
> +       while (rw->label) {
> +               sensors->dev_attr.attr.name = rw->label;
> +               sensors->dev_attr.attr.mode = S_IRUGO | S_IWUSR;
> +               sensors->dev_attr.show = rw->show;
> +               sensors->dev_attr.store = rw->set;
> +               sensors->index = rw->index;
> +
> +               res = device_create_file(dev, &sensors->dev_attr);
> +               if (res) {
> +                       sensors->dev_attr.attr.name = NULL;
> +                       goto error;
> +               }
> +               sensors++;
> +               resource->num_sensors++;
> +               rw++;
> +       }
> +
> +error:
> +       return res;
> +}
> +
> +static void remove_attrs(struct acpi_power_meter_resource *resource)
> +{
> +       int i;
> +
> +       for (i = 0; i < resource->num_sensors; i++) {
> +               if (!resource->sensors[i].dev_attr.attr.name)
> +                       continue;
> +               device_remove_file(&resource->acpi_dev->dev,
> +                                  &resource->sensors[i].dev_attr);
> +       }
> +
> +       resource->num_sensors = 0;
> +}
> +
> +static int setup_attrs(struct acpi_power_meter_resource *resource)
> +{
> +       int res = 0;
> +
> +       if (resource->caps.flags & POWER_METER_CAN_MEASURE) {
> +               res = register_ro_attrs(resource, meter_ro_attrs);
> +               if (res)
> +                       goto error;
> +               res = register_rw_attrs(resource, meter_rw_attrs);
> +               if (res)
> +                       goto error;
> +       }
> +
> +       if (resource->caps.flags & POWER_METER_CAN_CAP) {
> +               if (resource->caps.configurable_cap) {
> +                       res = register_rw_attrs(resource, rw_cap_attrs);
> +                       if (res)
> +                               goto error;
> +               } else {
> +                       res = register_ro_attrs(resource, ro_cap_attrs);
> +                       if (res)
> +                               goto error;
> +               }
> +               res = register_ro_attrs(resource, misc_cap_attrs);
> +               if (res)
> +                       goto error;
> +       }
> +
> +       if (resource->caps.flags & POWER_METER_CAN_TRIP) {
> +               res = register_rw_attrs(resource, trip_attrs);
> +               if (res)
> +                       goto error;
> +       }
> +
> +       res = register_ro_attrs(resource, misc_attrs);
> +       if (res)
> +               goto error;
> +
> +       return res;
> +error:
> +       remove_attrs(resource);
> +       return res;
> +}
> +
> +static int read_capabilities(struct acpi_power_meter_resource *resource)
> +{
> +       int res = 0;
> +       struct acpi_buffer buffer = { ACPI_ALLOCATE_BUFFER, NULL };
> +       struct acpi_buffer state = { 0, NULL };
> +       struct acpi_buffer format = { sizeof("NNNNNNNNNNN"), "NNNNNNNNNNN" };
> +       union acpi_object *pss;
> +       acpi_status status;
> +
> +       status = acpi_evaluate_object(resource->acpi_dev->handle, "_PMC", NULL,
> +                                     &buffer);
> +       if (ACPI_FAILURE(status)) {
> +               ACPI_EXCEPTION((AE_INFO, status, "Evaluating _PMC"));
> +               return -ENODEV;
> +       }
> +
> +       pss = buffer.pointer;
> +       if (!pss ||
> +           pss->type != ACPI_TYPE_PACKAGE ||
> +           pss->package.count != 14) {
> +               dev_err(&resource->acpi_dev->dev, PREFIX "Invalid _PMC data\n");
> +               res = -EFAULT;
> +               goto end;
> +       }
> +
> +       state.length = sizeof(struct acpi_power_meter_capabilities);
> +       state.pointer = &resource->caps;
> +
> +       status = acpi_extract_package(pss, &format, &state);
> +       if (ACPI_FAILURE(status)) {
> +               ACPI_EXCEPTION((AE_INFO, status, "Invalid data"));
> +               res = -EFAULT;
> +               goto end;
> +       }
> +
> +       if (resource->caps.units) {
> +               dev_err(&resource->acpi_dev->dev, PREFIX "Unknown unit %llu.\n",
> +                       resource->caps.units);
> +               res = -EINVAL;
> +               goto end;
> +       }
> +
> +       dev_info(&resource->acpi_dev->dev, "Found ACPI power meter.\n");
> +end:
> +       kfree(buffer.pointer);
> +       return res;
> +}
> +
> +/* Handle ACPI event notifications */
> +static void acpi_power_meter_notify(struct acpi_device *device, u32 event)
> +{
> +       struct acpi_power_meter_resource *resource;
> +       int res;
> +
> +       if (!device || !acpi_driver_data(device))
> +               return;
> +
> +       resource = acpi_driver_data(device);
> +
> +       mutex_lock(&resource->lock);
> +       switch (event) {
> +       case METER_NOTIFY_CONFIG:
> +               res = read_capabilities(resource);
> +               if (res)
> +                       break;
> +
> +               remove_attrs(resource);
> +               setup_attrs(resource);
> +               break;
> +       case METER_NOTIFY_TRIP:
> +               update_meter(resource);
> +               break;
> +       case METER_NOTIFY_CAP:
> +               update_cap(resource);
> +               break;
> +       case METER_NOTIFY_INTERVAL:
> +               update_avg_interval(resource);
> +               break;
> +       case METER_NOTIFY_CAPPING:
> +               dev_info(&device->dev, "Capping in progress.\n");
> +               break;
> +       default:
> +               BUG();
> +       }
> +       mutex_unlock(&resource->lock);
> +}
> +
> +static int acpi_power_meter_add(struct acpi_device *device)
> +{
> +       int res;
> +       struct acpi_power_meter_resource *resource;
> +
> +       if (!device)
> +               return -EINVAL;
> +
> +       resource = kzalloc(sizeof(struct acpi_power_meter_resource),
> +                          GFP_KERNEL);
> +       if (!resource)
> +               return -ENOMEM;
> +
> +       resource->sensors_valid = 0;
> +       resource->acpi_dev = device;
> +       mutex_init(&resource->lock);
> +       strcpy(acpi_device_name(device), ACPI_POWER_METER_DEVICE_NAME);
> +       strcpy(acpi_device_class(device), ACPI_POWER_METER_CLASS);
> +       device->driver_data = resource;
> +
> +       res = read_capabilities(resource);
> +       if (res)
> +               goto exit_free;
> +
> +       resource->trip[0] = resource->trip[1] = -1;
> +
> +       res = setup_attrs(resource);
> +       if (res)
> +               goto exit_free;
> +
> +       resource->hwmon_dev = hwmon_device_register(&device->dev);
> +       if (IS_ERR(resource->hwmon_dev)) {
> +               res = PTR_ERR(resource->hwmon_dev);
> +               goto exit_remove;
> +       }
> +
> +       res = 0;
> +       goto exit;
> +
> +exit_remove:
> +       remove_attrs(resource);
> +exit_free:
> +       kfree(resource);
> +exit:
> +       return res;
> +}
> +
> +static int acpi_power_meter_remove(struct acpi_device *device, int type)
> +{
> +       struct acpi_power_meter_resource *resource;
> +
> +       if (!device || !acpi_driver_data(device))
> +               return -EINVAL;
> +
> +       resource = acpi_driver_data(device);
> +       hwmon_device_unregister(resource->hwmon_dev);
> +
> +       remove_attrs(resource);
> +
> +       kfree(resource);
> +       return 0;
> +}
> +
> +static int acpi_power_meter_resume(struct acpi_device *device)
> +{
> +       struct acpi_power_meter_resource *resource;
> +
> +       if (!device || !acpi_driver_data(device))
> +               return -EINVAL;
> +
> +       resource = acpi_driver_data(device);
> +       read_capabilities(resource);
> +
> +       return 0;
> +}
> +
> +static struct acpi_driver acpi_power_meter_driver = {
> +       .name = "power_meter",
> +       .class = ACPI_POWER_METER_CLASS,
> +       .ids = power_meter_ids,
> +       .ops = {
> +               .add = acpi_power_meter_add,
> +               .remove = acpi_power_meter_remove,
> +               .resume = acpi_power_meter_resume,
> +               .notify = acpi_power_meter_notify,
> +               },
> +};
> +
> +/* Module init/exit routines */
> +
> +static int __init acpi_power_meter_init(void)
> +{
> +       int result;
> +
> +       result = acpi_bus_register_driver(&acpi_power_meter_driver);
> +       if (result < 0)
> +               return -ENODEV;
> +
> +       return 0;
> +}
> +
> +static void __exit acpi_power_meter_exit(void)
> +{
> +       acpi_bus_unregister_driver(&acpi_power_meter_driver);
> +}
> +
> +MODULE_AUTHOR("Darrick J. Wong <djwong@us.ibm.com>");
> +MODULE_DESCRIPTION("ACPI 4.0 power meter driver");
> +MODULE_LICENSE("GPL");
> +
> +module_init(acpi_power_meter_init);
> +module_exit(acpi_power_meter_exit);
> 
> --
> To unsubscribe from this list: send the line "unsubscribe linux-acpi" in
> the body of a message to majordomo@vger.kernel.org
> More majordomo info at  http://vger.kernel.org/majordomo-info.html


_______________________________________________
lm-sensors mailing list
lm-sensors@lm-sensors.org
http://lists.lm-sensors.org/mailman/listinfo/lm-sensors

^ permalink raw reply	[flat|nested] 38+ messages in thread

* Re: [PATCH 2/2] acpi_power_meter: hwmon driver for ACPI 4.0 power meters
  2009-07-25  0:43   ` [lm-sensors] [PATCH 2/2] acpi_power_meter: hwmon driver for ACPI Darrick J. Wong
@ 2009-07-27  6:45     ` Zhang Rui
  -1 siblings, 0 replies; 38+ messages in thread
From: Zhang Rui @ 2009-07-27  6:45 UTC (permalink / raw)
  To: Darrick J. Wong
  Cc: Len Brown, Andrew Morton, linux-kernel, lm-sensors, linux-acpi

Hi, Darrick,

great job. :)
comments in-line.

On Sat, 2009-07-25 at 08:43 +0800, Darrick J. Wong wrote:
> +/* Handle ACPI event notifications */
> +static void acpi_power_meter_notify(struct acpi_device *device, u32 event)
> +{
> +       struct acpi_power_meter_resource *resource;
> +       int res;
> +
> +       if (!device || !acpi_driver_data(device))
> +               return;
> +
> +       resource = acpi_driver_data(device);
> +
> +       mutex_lock(&resource->lock);
> +       switch (event) {
> +       case METER_NOTIFY_CONFIG:
> +               res = read_capabilities(resource);
> +               if (res)
> +                       break;
> +
> +               remove_attrs(resource);
> +               setup_attrs(resource);
> +               break;
> +       case METER_NOTIFY_TRIP:
> +               update_meter(resource);
> +               break;
> +       case METER_NOTIFY_CAP:
> +               update_cap(resource);
> +               break;
> +       case METER_NOTIFY_INTERVAL:
> +               update_avg_interval(resource);
> +               break;
> +       case METER_NOTIFY_CAPPING:
> +               dev_info(&device->dev, "Capping in progress.\n");
> +               break;
> +       default:
> +               BUG();
> +       }
> +       mutex_unlock(&resource->lock);
> +}

we should at least send a netlink event for an ACPI power meter
notification, shouldn't we?

> +
> +static int acpi_power_meter_add(struct acpi_device *device)
> +{
> +       int res;
> +       struct acpi_power_meter_resource *resource;
> +
> +       if (!device)
> +               return -EINVAL;
> +
> +       resource = kzalloc(sizeof(struct acpi_power_meter_resource),
> +                          GFP_KERNEL);
> +       if (!resource)
> +               return -ENOMEM;
> +
> +       resource->sensors_valid = 0;
> +       resource->acpi_dev = device;
> +       mutex_init(&resource->lock);
> +       strcpy(acpi_device_name(device), ACPI_POWER_METER_DEVICE_NAME);
> +       strcpy(acpi_device_class(device), ACPI_POWER_METER_CLASS);
> +       device->driver_data = resource;
> +
> +       res = read_capabilities(resource);
> +       if (res)
> +               goto exit_free;
> +
> +       resource->trip[0] = resource->trip[1] = -1;
> +
> +       res = setup_attrs(resource);
> +       if (res)
> +               goto exit_free;
> +
> +       resource->hwmon_dev = hwmon_device_register(&device->dev);
> +       if (IS_ERR(resource->hwmon_dev)) {
> +               res = PTR_ERR(resource->hwmon_dev);
> +               goto exit_remove;
> +       }
> +

we should create the hwmon sysfs I/F for the hwmon device,
i.e. add the hwmon attributes
under /sys/devices/LINUXSYS:00/.../ACPI000D:00/hwmon0/
rather than /sys/devices/LINUXSYS:00/.../ACPI000D:00/

> +       res = 0;
> +       goto exit;
> +
> +exit_remove:
> +       remove_attrs(resource);
> +exit_free:
> +       kfree(resource);
> +exit:
> +       return res;
> +}
> +
> +
> +static int __init acpi_power_meter_init(void)
> +{
> +       int result;
> +
	  if (acpi_disable)
		return -ENODEV;

> +       result = acpi_bus_register_driver(&acpi_power_meter_driver);
> +       if (result < 0)
> +               return -ENODEV;
> +
> +       return 0;
> +}
> +
> +static void __exit acpi_power_meter_exit(void)
> +{
> +       acpi_bus_unregister_driver(&acpi_power_meter_driver);
> +}
> +
> +MODULE_AUTHOR("Darrick J. Wong <djwong@us.ibm.com>");
> +MODULE_DESCRIPTION("ACPI 4.0 power meter driver");
> +MODULE_LICENSE("GPL");
> +
> +module_init(acpi_power_meter_init);
> +module_exit(acpi_power_meter_exit);
> 
plus, _PMD is not supported in this driver, right?
I agree with Yakui that we can create some ACPI device sysfs attributes
besides the hwmon ones. e.g. exporting devices measured by the current
ACPI power meter device to user space.

thanks,
rui

^ permalink raw reply	[flat|nested] 38+ messages in thread

* Re: [lm-sensors] [PATCH 2/2] acpi_power_meter: hwmon driver for
@ 2009-07-27  6:45     ` Zhang Rui
  0 siblings, 0 replies; 38+ messages in thread
From: Zhang Rui @ 2009-07-27  6:45 UTC (permalink / raw)
  To: Darrick J. Wong
  Cc: Len Brown, Andrew Morton, linux-kernel, lm-sensors, linux-acpi

Hi, Darrick,

great job. :)
comments in-line.

On Sat, 2009-07-25 at 08:43 +0800, Darrick J. Wong wrote:
> +/* Handle ACPI event notifications */
> +static void acpi_power_meter_notify(struct acpi_device *device, u32 event)
> +{
> +       struct acpi_power_meter_resource *resource;
> +       int res;
> +
> +       if (!device || !acpi_driver_data(device))
> +               return;
> +
> +       resource = acpi_driver_data(device);
> +
> +       mutex_lock(&resource->lock);
> +       switch (event) {
> +       case METER_NOTIFY_CONFIG:
> +               res = read_capabilities(resource);
> +               if (res)
> +                       break;
> +
> +               remove_attrs(resource);
> +               setup_attrs(resource);
> +               break;
> +       case METER_NOTIFY_TRIP:
> +               update_meter(resource);
> +               break;
> +       case METER_NOTIFY_CAP:
> +               update_cap(resource);
> +               break;
> +       case METER_NOTIFY_INTERVAL:
> +               update_avg_interval(resource);
> +               break;
> +       case METER_NOTIFY_CAPPING:
> +               dev_info(&device->dev, "Capping in progress.\n");
> +               break;
> +       default:
> +               BUG();
> +       }
> +       mutex_unlock(&resource->lock);
> +}

we should at least send a netlink event for an ACPI power meter
notification, shouldn't we?

> +
> +static int acpi_power_meter_add(struct acpi_device *device)
> +{
> +       int res;
> +       struct acpi_power_meter_resource *resource;
> +
> +       if (!device)
> +               return -EINVAL;
> +
> +       resource = kzalloc(sizeof(struct acpi_power_meter_resource),
> +                          GFP_KERNEL);
> +       if (!resource)
> +               return -ENOMEM;
> +
> +       resource->sensors_valid = 0;
> +       resource->acpi_dev = device;
> +       mutex_init(&resource->lock);
> +       strcpy(acpi_device_name(device), ACPI_POWER_METER_DEVICE_NAME);
> +       strcpy(acpi_device_class(device), ACPI_POWER_METER_CLASS);
> +       device->driver_data = resource;
> +
> +       res = read_capabilities(resource);
> +       if (res)
> +               goto exit_free;
> +
> +       resource->trip[0] = resource->trip[1] = -1;
> +
> +       res = setup_attrs(resource);
> +       if (res)
> +               goto exit_free;
> +
> +       resource->hwmon_dev = hwmon_device_register(&device->dev);
> +       if (IS_ERR(resource->hwmon_dev)) {
> +               res = PTR_ERR(resource->hwmon_dev);
> +               goto exit_remove;
> +       }
> +

we should create the hwmon sysfs I/F for the hwmon device,
i.e. add the hwmon attributes
under /sys/devices/LINUXSYS:00/.../ACPI000D:00/hwmon0/
rather than /sys/devices/LINUXSYS:00/.../ACPI000D:00/

> +       res = 0;
> +       goto exit;
> +
> +exit_remove:
> +       remove_attrs(resource);
> +exit_free:
> +       kfree(resource);
> +exit:
> +       return res;
> +}
> +
> +
> +static int __init acpi_power_meter_init(void)
> +{
> +       int result;
> +
	  if (acpi_disable)
		return -ENODEV;

> +       result = acpi_bus_register_driver(&acpi_power_meter_driver);
> +       if (result < 0)
> +               return -ENODEV;
> +
> +       return 0;
> +}
> +
> +static void __exit acpi_power_meter_exit(void)
> +{
> +       acpi_bus_unregister_driver(&acpi_power_meter_driver);
> +}
> +
> +MODULE_AUTHOR("Darrick J. Wong <djwong@us.ibm.com>");
> +MODULE_DESCRIPTION("ACPI 4.0 power meter driver");
> +MODULE_LICENSE("GPL");
> +
> +module_init(acpi_power_meter_init);
> +module_exit(acpi_power_meter_exit);
> 
plus, _PMD is not supported in this driver, right?
I agree with Yakui that we can create some ACPI device sysfs attributes
besides the hwmon ones. e.g. exporting devices measured by the current
ACPI power meter device to user space.

thanks,
rui


_______________________________________________
lm-sensors mailing list
lm-sensors@lm-sensors.org
http://lists.lm-sensors.org/mailman/listinfo/lm-sensors

^ permalink raw reply	[flat|nested] 38+ messages in thread

* Re: [PATCH 2/2] acpi_power_meter: hwmon driver for ACPI 4.0 power meters
  2009-07-25  0:43   ` [lm-sensors] [PATCH 2/2] acpi_power_meter: hwmon driver for ACPI Darrick J. Wong
@ 2009-07-28  1:25     ` Len Brown
  -1 siblings, 0 replies; 38+ messages in thread
From: Len Brown @ 2009-07-28  1:25 UTC (permalink / raw)
  To: Darrick J. Wong
  Cc: Andrew Morton, linux-kernel, lm-sensors, linux-acpi, Zhang Rui

Hi Darrick,
On the system you are planning to run this on...
Are the power meters in the processor, or elsewhere?

If they are the ones in the processor, I'd rather that
Linux use a native model-specific driver to access those
registers and not use ACPI.

thanks,
Len Brown, Intel Open Source Technology Center

^ permalink raw reply	[flat|nested] 38+ messages in thread

* Re: [lm-sensors] [PATCH 2/2] acpi_power_meter: hwmon driver for
@ 2009-07-28  1:25     ` Len Brown
  0 siblings, 0 replies; 38+ messages in thread
From: Len Brown @ 2009-07-28  1:25 UTC (permalink / raw)
  To: Darrick J. Wong
  Cc: Andrew Morton, linux-kernel, lm-sensors, linux-acpi, Zhang Rui

Hi Darrick,
On the system you are planning to run this on...
Are the power meters in the processor, or elsewhere?

If they are the ones in the processor, I'd rather that
Linux use a native model-specific driver to access those
registers and not use ACPI.

thanks,
Len Brown, Intel Open Source Technology Center

_______________________________________________
lm-sensors mailing list
lm-sensors@lm-sensors.org
http://lists.lm-sensors.org/mailman/listinfo/lm-sensors

^ permalink raw reply	[flat|nested] 38+ messages in thread

* Re: [PATCH 2/2] acpi_power_meter: hwmon driver for ACPI 4.0 power meters
  2009-07-27  1:44     ` [lm-sensors] [PATCH 2/2] acpi_power_meter: hwmon driver for ykzhao
@ 2009-08-03 20:48       ` Darrick J. Wong
  -1 siblings, 0 replies; 38+ messages in thread
From: Darrick J. Wong @ 2009-08-03 20:48 UTC (permalink / raw)
  To: ykzhao
  Cc: Len Brown, Andrew Morton, linux-kernel, lm-sensors, linux-acpi,
	Zhang, Rui

On Mon, Jul 27, 2009 at 09:44:27AM +0800, ykzhao wrote:
> On Sat, 2009-07-25 at 08:43 +0800, Darrick J. Wong wrote:
> > This driver exposes ACPI 4.0 compliant power meters as hardware monitoring
> > devices.
> It seems OK to me. But it seems that some attributes can't be obtained
> by using the hwmon sys I/F.
>    a. Can we add a sys I/F that can exports the Power meter capability?
> display all the content of _PMC as what we have done in battery

Are you talking about all 17 fields of the _PMC return value?  Or specifically
the feature bit field "Supported Capabilities"?  The feature bits control
whether or not the corresponding sysfs attributes even show up, and the other
fields (except the string data) are all there.  I suppose it would be useful to
display the OEM data too...

>    b. Add a sys I/F that displays the name of device list measured by
> the power meter if there exists the _PMD object.

As soon as I figure out how to do that. :)

Sorry for the slow response as I was mountain climbing = no Internet.

--D

^ permalink raw reply	[flat|nested] 38+ messages in thread

* Re: [lm-sensors] [PATCH 2/2] acpi_power_meter: hwmon driver for
@ 2009-08-03 20:48       ` Darrick J. Wong
  0 siblings, 0 replies; 38+ messages in thread
From: Darrick J. Wong @ 2009-08-03 20:48 UTC (permalink / raw)
  To: ykzhao
  Cc: Len Brown, Andrew Morton, linux-kernel, lm-sensors, linux-acpi,
	Zhang, Rui

On Mon, Jul 27, 2009 at 09:44:27AM +0800, ykzhao wrote:
> On Sat, 2009-07-25 at 08:43 +0800, Darrick J. Wong wrote:
> > This driver exposes ACPI 4.0 compliant power meters as hardware monitoring
> > devices.
> It seems OK to me. But it seems that some attributes can't be obtained
> by using the hwmon sys I/F.
>    a. Can we add a sys I/F that can exports the Power meter capability?
> display all the content of _PMC as what we have done in battery

Are you talking about all 17 fields of the _PMC return value?  Or specifically
the feature bit field "Supported Capabilities"?  The feature bits control
whether or not the corresponding sysfs attributes even show up, and the other
fields (except the string data) are all there.  I suppose it would be useful to
display the OEM data too...

>    b. Add a sys I/F that displays the name of device list measured by
> the power meter if there exists the _PMD object.

As soon as I figure out how to do that. :)

Sorry for the slow response as I was mountain climbing = no Internet.

--D

_______________________________________________
lm-sensors mailing list
lm-sensors@lm-sensors.org
http://lists.lm-sensors.org/mailman/listinfo/lm-sensors

^ permalink raw reply	[flat|nested] 38+ messages in thread

* Re: [PATCH 2/2] acpi_power_meter: hwmon driver for ACPI 4.0 power meters
  2009-07-27  6:45     ` [lm-sensors] [PATCH 2/2] acpi_power_meter: hwmon driver for Zhang Rui
  (?)
@ 2009-08-03 20:52       ` Darrick J. Wong
  -1 siblings, 0 replies; 38+ messages in thread
From: Darrick J. Wong @ 2009-08-03 20:52 UTC (permalink / raw)
  To: Zhang Rui; +Cc: Len Brown, Andrew Morton, linux-kernel, lm-sensors, linux-acpi

On Mon, Jul 27, 2009 at 02:45:55PM +0800, Zhang Rui wrote:
> Hi, Darrick,
> 
> great job. :)

Thanks.

> we should at least send a netlink event for an ACPI power meter
> notification, shouldn't we?

Yes, an event would be useful at the very least for when the system starts
capping, though unfortunately there doesn't seem to be a Notify event for when
capping ends.

> we should create the hwmon sysfs I/F for the hwmon device,
> i.e. add the hwmon attributes
> under /sys/devices/LINUXSYS:00/.../ACPI000D:00/hwmon0/
> rather than /sys/devices/LINUXSYS:00/.../ACPI000D:00/

Hee, this is a tricky matter.  The other hwmon drivers create sysfs attributes
under the sysfs object, not in a separate "hwmonX" object, and as I recall
libsensors reads symlinks under the device object to figure out which bus the
sensor device lives on.  If it can't figure that out, it ignores the hwmon
device.

> 	  if (acpi_disable)
> 		return -ENODEV;

Noted.

> plus, _PMD is not supported in this driver, right?
> I agree with Yakui that we can create some ACPI device sysfs attributes
> besides the hwmon ones. e.g. exporting devices measured by the current
> ACPI power meter device to user space.

I agree.

--D
--
To unsubscribe from this list: send the line "unsubscribe linux-acpi" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html

^ permalink raw reply	[flat|nested] 38+ messages in thread

* Re: [PATCH 2/2] acpi_power_meter: hwmon driver for ACPI 4.0 power meters
@ 2009-08-03 20:52       ` Darrick J. Wong
  0 siblings, 0 replies; 38+ messages in thread
From: Darrick J. Wong @ 2009-08-03 20:52 UTC (permalink / raw)
  To: Zhang Rui; +Cc: Len Brown, Andrew Morton, linux-kernel, lm-sensors, linux-acpi

On Mon, Jul 27, 2009 at 02:45:55PM +0800, Zhang Rui wrote:
> Hi, Darrick,
> 
> great job. :)

Thanks.

> we should at least send a netlink event for an ACPI power meter
> notification, shouldn't we?

Yes, an event would be useful at the very least for when the system starts
capping, though unfortunately there doesn't seem to be a Notify event for when
capping ends.

> we should create the hwmon sysfs I/F for the hwmon device,
> i.e. add the hwmon attributes
> under /sys/devices/LINUXSYS:00/.../ACPI000D:00/hwmon0/
> rather than /sys/devices/LINUXSYS:00/.../ACPI000D:00/

Hee, this is a tricky matter.  The other hwmon drivers create sysfs attributes
under the sysfs object, not in a separate "hwmonX" object, and as I recall
libsensors reads symlinks under the device object to figure out which bus the
sensor device lives on.  If it can't figure that out, it ignores the hwmon
device.

> 	  if (acpi_disable)
> 		return -ENODEV;

Noted.

> plus, _PMD is not supported in this driver, right?
> I agree with Yakui that we can create some ACPI device sysfs attributes
> besides the hwmon ones. e.g. exporting devices measured by the current
> ACPI power meter device to user space.

I agree.

--D

^ permalink raw reply	[flat|nested] 38+ messages in thread

* Re: [lm-sensors] [PATCH 2/2] acpi_power_meter: hwmon driver for
@ 2009-08-03 20:52       ` Darrick J. Wong
  0 siblings, 0 replies; 38+ messages in thread
From: Darrick J. Wong @ 2009-08-03 20:52 UTC (permalink / raw)
  To: Zhang Rui; +Cc: Len Brown, Andrew Morton, linux-kernel, lm-sensors, linux-acpi

On Mon, Jul 27, 2009 at 02:45:55PM +0800, Zhang Rui wrote:
> Hi, Darrick,
> 
> great job. :)

Thanks.

> we should at least send a netlink event for an ACPI power meter
> notification, shouldn't we?

Yes, an event would be useful at the very least for when the system starts
capping, though unfortunately there doesn't seem to be a Notify event for when
capping ends.

> we should create the hwmon sysfs I/F for the hwmon device,
> i.e. add the hwmon attributes
> under /sys/devices/LINUXSYS:00/.../ACPI000D:00/hwmon0/
> rather than /sys/devices/LINUXSYS:00/.../ACPI000D:00/

Hee, this is a tricky matter.  The other hwmon drivers create sysfs attributes
under the sysfs object, not in a separate "hwmonX" object, and as I recall
libsensors reads symlinks under the device object to figure out which bus the
sensor device lives on.  If it can't figure that out, it ignores the hwmon
device.

> 	  if (acpi_disable)
> 		return -ENODEV;

Noted.

> plus, _PMD is not supported in this driver, right?
> I agree with Yakui that we can create some ACPI device sysfs attributes
> besides the hwmon ones. e.g. exporting devices measured by the current
> ACPI power meter device to user space.

I agree.

--D

_______________________________________________
lm-sensors mailing list
lm-sensors@lm-sensors.org
http://lists.lm-sensors.org/mailman/listinfo/lm-sensors

^ permalink raw reply	[flat|nested] 38+ messages in thread

* Re: [PATCH 2/2] acpi_power_meter: hwmon driver for ACPI 4.0 power meters
  2009-07-28  1:25     ` [lm-sensors] [PATCH 2/2] acpi_power_meter: hwmon driver for Len Brown
@ 2009-08-03 20:58       ` Darrick J. Wong
  -1 siblings, 0 replies; 38+ messages in thread
From: Darrick J. Wong @ 2009-08-03 20:58 UTC (permalink / raw)
  To: Len Brown; +Cc: Andrew Morton, linux-kernel, lm-sensors, linux-acpi, Zhang Rui

On Mon, Jul 27, 2009 at 09:25:54PM -0400, Len Brown wrote:
> Hi Darrick,
> On the system you are planning to run this on...
> Are the power meters in the processor, or elsewhere?
> 
> If they are the ones in the processor, I'd rather that
> Linux use a native model-specific driver to access those
> registers and not use ACPI.

They're not in the processor.  Right now we're simulating the meters in kvm
until we get real hardware, though it could be a mechanism to report per-VM
power use.

--D

^ permalink raw reply	[flat|nested] 38+ messages in thread

* Re: [lm-sensors] [PATCH 2/2] acpi_power_meter: hwmon driver for
@ 2009-08-03 20:58       ` Darrick J. Wong
  0 siblings, 0 replies; 38+ messages in thread
From: Darrick J. Wong @ 2009-08-03 20:58 UTC (permalink / raw)
  To: Len Brown; +Cc: Andrew Morton, linux-kernel, lm-sensors, linux-acpi, Zhang Rui

On Mon, Jul 27, 2009 at 09:25:54PM -0400, Len Brown wrote:
> Hi Darrick,
> On the system you are planning to run this on...
> Are the power meters in the processor, or elsewhere?
> 
> If they are the ones in the processor, I'd rather that
> Linux use a native model-specific driver to access those
> registers and not use ACPI.

They're not in the processor.  Right now we're simulating the meters in kvm
until we get real hardware, though it could be a mechanism to report per-VM
power use.

--D

_______________________________________________
lm-sensors mailing list
lm-sensors@lm-sensors.org
http://lists.lm-sensors.org/mailman/listinfo/lm-sensors

^ permalink raw reply	[flat|nested] 38+ messages in thread

* [PATCH v2] acpi_power_meter: hwmon driver for ACPI 4.0 power meters
  2009-08-03 20:58       ` [lm-sensors] [PATCH 2/2] acpi_power_meter: hwmon driver for Darrick J. Wong
@ 2009-08-06 20:42         ` Darrick J. Wong
  -1 siblings, 0 replies; 38+ messages in thread
From: Darrick J. Wong @ 2009-08-06 20:42 UTC (permalink / raw)
  To: Len Brown; +Cc: linux-acpi, Andrew Morton, Zhang Rui, linux-kernel, lm-sensors

This driver exposes ACPI 4.0 compliant power meters as hardware monitoring
devices.  This second revision of the driver also exports the ACPI string
info as sysfs attributes, a list of the devices that the meter measures,
and will send ACPI notifications over the ACPI netlink socket.

Signed-off-by: Darrick J. Wong <djwong@us.ibm.com>
---

 Documentation/hwmon/acpi_power_meter |   29 +
 drivers/acpi/Kconfig                 |   12 
 drivers/acpi/Makefile                |    1 
 drivers/acpi/power_meter.c           |  968 ++++++++++++++++++++++++++++++++++
 4 files changed, 1010 insertions(+), 0 deletions(-)
 create mode 100644 Documentation/hwmon/acpi_power_meter
 create mode 100644 drivers/acpi/power_meter.c


diff --git a/Documentation/hwmon/acpi_power_meter b/Documentation/hwmon/acpi_power_meter
new file mode 100644
index 0000000..92f5f9c
--- /dev/null
+++ b/Documentation/hwmon/acpi_power_meter
@@ -0,0 +1,29 @@
+Kernel driver power_meter
+=========================
+
+This driver talks to ACPI 4.0 power meters.
+
+Supported systems:
+  * Any recent system with ACPI 4.0.
+    Prefix: 'power_meter'
+    Datasheet: http://acpi.info/, section 10.4.
+
+Author: Darrick J. Wong
+
+Description
+-----------
+
+This driver implements sensor reading support for the power meters exposed in
+the ACPI 4.0 spec (Chapter 10.4).  These devices have a simple set of
+features--a power meter that returns average power use over a configurable
+interval, an optional capping mechanism, and a couple of trip points.
+
+Special Features
+----------------
+
+The power[1-*]_is_battery knob indicates if the power supply is a battery.
+Both power[1-*]_average_{min,max} must be set before the trip points will work.
+The power[1-*]_{model_number, serial_number, oem_info} fields display arbitrary
+strings that ACPI provides with the meter.
+The measures/ directory contains symlinks to the devices that this meter
+measures.
diff --git a/drivers/acpi/Kconfig b/drivers/acpi/Kconfig
index 7ec7d88..8ac801d 100644
--- a/drivers/acpi/Kconfig
+++ b/drivers/acpi/Kconfig
@@ -82,6 +82,18 @@ config ACPI_PROCFS_POWER
 
 	  Say N to delete power /proc/acpi/ directories that have moved to /sys/
 
+config ACPI_POWER_METER
+	tristate "ACPI 4.0 power meter"
+	depends on HWMON
+	default m
+	help
+	  This driver exposes ACPI 4.0 power meters as hardware monitoring
+	  devices.  Say Y (or M) if you have an Intel or AMD computer with
+	  a power meter.
+
+	  To compile this driver as a module, choose M here:
+	  the module will be called power-meter.
+
 config ACPI_SYSFS_POWER
 	bool "Future power /sys interface"
 	select POWER_SUPPLY
diff --git a/drivers/acpi/Makefile b/drivers/acpi/Makefile
index 03a985b..82cd49d 100644
--- a/drivers/acpi/Makefile
+++ b/drivers/acpi/Makefile
@@ -56,6 +56,7 @@ obj-$(CONFIG_ACPI_HOTPLUG_MEMORY) += acpi_memhotplug.o
 obj-$(CONFIG_ACPI_BATTERY)	+= battery.o
 obj-$(CONFIG_ACPI_SBS)		+= sbshc.o
 obj-$(CONFIG_ACPI_SBS)		+= sbs.o
+obj-$(CONFIG_ACPI_POWER_METER)	+= power_meter.o
 
 # processor has its own "processor." module_param namespace
 processor-y			:= processor_core.o processor_throttling.o
diff --git a/drivers/acpi/power_meter.c b/drivers/acpi/power_meter.c
new file mode 100644
index 0000000..3f48dec
--- /dev/null
+++ b/drivers/acpi/power_meter.c
@@ -0,0 +1,968 @@
+/*
+ * A hwmon driver for ACPI 4.0 power meters
+ * Copyright (C) 2009 IBM
+ *
+ * Author: Darrick J. Wong <djwong@us.ibm.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+
+#include <linux/module.h>
+#include <linux/hwmon.h>
+#include <linux/hwmon-sysfs.h>
+#include <linux/jiffies.h>
+#include <linux/mutex.h>
+#include <linux/kdev_t.h>
+#include <linux/sched.h>
+#include <linux/time.h>
+#include <acpi/acpi_drivers.h>
+#include <acpi/acpi_bus.h>
+
+#define ACPI_POWER_METER_NAME		"power_meter"
+ACPI_MODULE_NAME(ACPI_POWER_METER_NAME);
+#define ACPI_POWER_METER_DEVICE_NAME	"Power Meter"
+#define ACPI_POWER_METER_CLASS		"power_meter_resource"
+
+#define NUM_SENSORS			17
+
+#define POWER_METER_CAN_MEASURE	(1 << 0)
+#define POWER_METER_CAN_TRIP	(1 << 1)
+#define POWER_METER_CAN_CAP	(1 << 2)
+#define POWER_METER_CAN_NOTIFY	(1 << 3)
+#define POWER_METER_IS_BATTERY	(1 << 8)
+#define UNKNOWN_HYSTERESIS	0xFFFFFFFF
+
+#define METER_NOTIFY_CONFIG	0x80
+#define METER_NOTIFY_TRIP	0x81
+#define METER_NOTIFY_CAP	0x82
+#define METER_NOTIFY_CAPPING	0x83
+#define METER_NOTIFY_INTERVAL	0x84
+
+static struct acpi_device_id power_meter_ids[] = {
+	{"ACPI000D", 0},
+	{"", 0},
+};
+MODULE_DEVICE_TABLE(acpi, power_meter_ids);
+
+struct acpi_power_meter_capabilities {
+	acpi_integer		flags;
+	acpi_integer		units;
+	acpi_integer		type;
+	acpi_integer		accuracy;
+	acpi_integer		sampling_time;
+	acpi_integer		min_avg_interval;
+	acpi_integer		max_avg_interval;
+	acpi_integer		hysteresis;
+	acpi_integer		configurable_cap;
+	acpi_integer		min_cap;
+	acpi_integer		max_cap;
+};
+
+struct acpi_power_meter_resource {
+	struct acpi_device	*acpi_dev;
+	acpi_bus_id		name;
+	struct mutex		lock;
+	struct device		*hwmon_dev;
+	struct acpi_power_meter_capabilities	caps;
+	acpi_string		model_number;
+	acpi_string		serial_number;
+	acpi_string		oem_info;
+	acpi_integer		power;
+	acpi_integer		cap;
+	acpi_integer		avg_interval;
+	int			sensors_valid;
+	unsigned long		sensors_last_updated;
+	struct sensor_device_attribute	sensors[NUM_SENSORS];
+	int			num_sensors;
+	int			trip[2];
+	int			num_domain_devices;
+	struct acpi_device	**domain_devices;
+	struct kobject		*holders_dir;
+};
+
+struct ro_sensor_template {
+	char *label;
+	ssize_t (*show)(struct device *dev,
+			struct device_attribute *devattr,
+			char *buf);
+	int index;
+};
+
+struct rw_sensor_template {
+	char *label;
+	ssize_t (*show)(struct device *dev,
+			struct device_attribute *devattr,
+			char *buf);
+	ssize_t (*set)(struct device *dev,
+		       struct device_attribute *devattr,
+		       const char *buf, size_t count);
+	int index;
+};
+
+/* Averaging interval */
+static int update_avg_interval(struct acpi_power_meter_resource *resource)
+{
+	unsigned long long data;
+	acpi_status status;
+
+	status = acpi_evaluate_integer(resource->acpi_dev->handle, "_GAI",
+				       NULL, &data);
+	if (ACPI_FAILURE(status)) {
+		ACPI_EXCEPTION((AE_INFO, status, "Evaluating _GAI"));
+		return -ENODEV;
+	}
+
+	resource->avg_interval = data;
+	return 0;
+}
+
+static ssize_t show_avg_interval(struct device *dev,
+				 struct device_attribute *devattr,
+				 char *buf)
+{
+	struct acpi_device *acpi_dev = to_acpi_device(dev);
+	struct acpi_power_meter_resource *resource = acpi_dev->driver_data;
+
+	mutex_lock(&resource->lock);
+	update_avg_interval(resource);
+	mutex_unlock(&resource->lock);
+
+	return sprintf(buf, "%llu\n", resource->avg_interval);
+}
+
+static ssize_t set_avg_interval(struct device *dev,
+				struct device_attribute *devattr,
+				const char *buf, size_t count)
+{
+	struct acpi_device *acpi_dev = to_acpi_device(dev);
+	struct acpi_power_meter_resource *resource = acpi_dev->driver_data;
+	union acpi_object arg0 = { ACPI_TYPE_INTEGER };
+	struct acpi_object_list args = { 1, &arg0 };
+	int res;
+	unsigned long temp;
+	unsigned long long data;
+	acpi_status status;
+
+	res = strict_strtoul(buf, 10, &temp);
+	if (res)
+		return res;
+
+	if (temp > resource->caps.max_avg_interval ||
+	    temp < resource->caps.min_avg_interval)
+		return -EINVAL;
+	arg0.integer.value = temp;
+
+	mutex_lock(&resource->lock);
+	status = acpi_evaluate_integer(resource->acpi_dev->handle, "_PAI",
+				       &args, &data);
+	if (!ACPI_FAILURE(status))
+		resource->avg_interval = temp;
+	mutex_unlock(&resource->lock);
+
+	if (ACPI_FAILURE(status)) {
+		ACPI_EXCEPTION((AE_INFO, status, "Evaluating _PAI"));
+		return -EINVAL;
+	}
+
+	if (data)
+		return -EINVAL;
+
+	return count;
+}
+
+/* Cap functions */
+static int update_cap(struct acpi_power_meter_resource *resource)
+{
+	unsigned long long data;
+	acpi_status status;
+
+	status = acpi_evaluate_integer(resource->acpi_dev->handle, "_GHL",
+				       NULL, &data);
+	if (ACPI_FAILURE(status)) {
+		ACPI_EXCEPTION((AE_INFO, status, "Evaluating _GHL"));
+		return -ENODEV;
+	}
+
+	resource->cap = data;
+	return 0;
+}
+
+static ssize_t show_cap(struct device *dev,
+			struct device_attribute *devattr,
+			char *buf)
+{
+	struct acpi_device *acpi_dev = to_acpi_device(dev);
+	struct acpi_power_meter_resource *resource = acpi_dev->driver_data;
+
+	mutex_lock(&resource->lock);
+	update_cap(resource);
+	mutex_unlock(&resource->lock);
+
+	return sprintf(buf, "%llu\n", resource->cap * 1000);
+}
+
+static ssize_t set_cap(struct device *dev, struct device_attribute *devattr,
+		       const char *buf, size_t count)
+{
+	struct acpi_device *acpi_dev = to_acpi_device(dev);
+	struct acpi_power_meter_resource *resource = acpi_dev->driver_data;
+	union acpi_object arg0 = { ACPI_TYPE_INTEGER };
+	struct acpi_object_list args = { 1, &arg0 };
+	int res;
+	unsigned long temp;
+	unsigned long long data;
+	acpi_status status;
+
+	res = strict_strtoul(buf, 10, &temp);
+	if (res)
+		return res;
+
+	temp /= 1000;
+	if (temp > resource->caps.max_cap || temp < resource->caps.min_cap)
+		return -EINVAL;
+	arg0.integer.value = temp;
+
+	mutex_lock(&resource->lock);
+	status = acpi_evaluate_integer(resource->acpi_dev->handle, "_SHL",
+				       &args, &data);
+	if (!ACPI_FAILURE(status))
+		resource->cap = temp;
+	mutex_unlock(&resource->lock);
+
+	if (ACPI_FAILURE(status)) {
+		ACPI_EXCEPTION((AE_INFO, status, "Evaluating _SHL"));
+		return -EINVAL;
+	}
+
+	if (data)
+		return -EINVAL;
+
+	return count;
+}
+
+/* Power meter trip points */
+static int set_acpi_trip(struct acpi_power_meter_resource *resource)
+{
+	union acpi_object arg_objs[] = {
+		{ACPI_TYPE_INTEGER},
+		{ACPI_TYPE_INTEGER}
+	};
+	struct acpi_object_list args = { 2, arg_objs };
+	unsigned long long data;
+	acpi_status status;
+
+	/* Both trip levels must be set */
+	if (resource->trip[0] < 0 || resource->trip[1] < 0)
+		return 0;
+
+	/* This driver stores min, max; ACPI wants max, min. */
+	arg_objs[0].integer.value = resource->trip[1];
+	arg_objs[1].integer.value = resource->trip[0];
+
+	status = acpi_evaluate_integer(resource->acpi_dev->handle, "_PTP",
+				       &args, &data);
+	if (ACPI_FAILURE(status)) {
+		ACPI_EXCEPTION((AE_INFO, status, "Evaluating _PTP"));
+		return -EINVAL;
+	}
+
+	return data;
+}
+
+static ssize_t set_trip(struct device *dev, struct device_attribute *devattr,
+			const char *buf, size_t count)
+{
+	struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr);
+	struct acpi_device *acpi_dev = to_acpi_device(dev);
+	struct acpi_power_meter_resource *resource = acpi_dev->driver_data;
+	int res;
+	unsigned long temp;
+
+	res = strict_strtoul(buf, 10, &temp);
+	if (res)
+		return res;
+
+	temp /= 1000;
+	if (temp < 0)
+		return -EINVAL;
+
+	mutex_lock(&resource->lock);
+	resource->trip[attr->index - 7] = temp;
+	res = set_acpi_trip(resource);
+	mutex_unlock(&resource->lock);
+
+	if (res)
+		return res;
+
+	return count;
+}
+
+/* Power meter */
+static int update_meter(struct acpi_power_meter_resource *resource)
+{
+	unsigned long long data;
+	acpi_status status;
+	unsigned long local_jiffies = jiffies;
+
+	if (time_before(local_jiffies, resource->sensors_last_updated +
+			msecs_to_jiffies(resource->caps.sampling_time)) &&
+			resource->sensors_valid)
+		return 0;
+
+	status = acpi_evaluate_integer(resource->acpi_dev->handle, "_PMM",
+				       NULL, &data);
+	if (ACPI_FAILURE(status)) {
+		ACPI_EXCEPTION((AE_INFO, status, "Evaluating _PMM"));
+		return -ENODEV;
+	}
+
+	resource->power = data;
+	resource->sensors_valid = 1;
+	resource->sensors_last_updated = jiffies;
+	return 0;
+}
+
+static ssize_t show_power(struct device *dev,
+			  struct device_attribute *devattr,
+			  char *buf)
+{
+	struct acpi_device *acpi_dev = to_acpi_device(dev);
+	struct acpi_power_meter_resource *resource = acpi_dev->driver_data;
+
+	mutex_lock(&resource->lock);
+	update_meter(resource);
+	mutex_unlock(&resource->lock);
+
+	return sprintf(buf, "%llu\n", resource->power * 1000);
+}
+
+/* Miscellaneous */
+static ssize_t show_str(struct device *dev,
+			struct device_attribute *devattr,
+			char *buf)
+{
+	struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr);
+	struct acpi_device *acpi_dev = to_acpi_device(dev);
+	struct acpi_power_meter_resource *resource = acpi_dev->driver_data;
+	acpi_string val;
+
+	switch (attr->index) {
+	case 0:
+		val = resource->model_number;
+		break;
+	case 1:
+		val = resource->serial_number;
+		break;
+	case 2:
+		val = resource->oem_info;
+		break;
+	default:
+		BUG();
+	}
+
+	return sprintf(buf, "%s\n", val);
+}
+
+static ssize_t show_val(struct device *dev,
+			struct device_attribute *devattr,
+			char *buf)
+{
+	struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr);
+	struct acpi_device *acpi_dev = to_acpi_device(dev);
+	struct acpi_power_meter_resource *resource = acpi_dev->driver_data;
+	acpi_integer val = 0;
+
+	switch (attr->index) {
+	case 0:
+		val = resource->caps.min_avg_interval;
+		break;
+	case 1:
+		val = resource->caps.max_avg_interval;
+		break;
+	case 2:
+		val = resource->caps.min_cap * 1000;
+		break;
+	case 3:
+		val = resource->caps.max_cap * 1000;
+		break;
+	case 4:
+		if (resource->caps.hysteresis == UNKNOWN_HYSTERESIS)
+			return sprintf(buf, "unknown\n");
+
+		val = resource->caps.hysteresis * 1000;
+		break;
+	case 5:
+		if (resource->caps.flags & POWER_METER_IS_BATTERY)
+			val = 1;
+		else
+			val = 0;
+		break;
+	case 6:
+		if (resource->power > resource->cap)
+			val = 1;
+		else
+			val = 0;
+		break;
+	case 7:
+	case 8:
+		if (resource->trip[attr->index - 7] < 0)
+			return sprintf(buf, "unknown\n");
+
+		val = resource->trip[attr->index - 7] * 1000;
+		break;
+	default:
+		BUG();
+	}
+
+	return sprintf(buf, "%llu\n", val);
+}
+
+static ssize_t show_accuracy(struct device *dev,
+			     struct device_attribute *devattr,
+			     char *buf)
+{
+	struct acpi_device *acpi_dev = to_acpi_device(dev);
+	struct acpi_power_meter_resource *resource = acpi_dev->driver_data;
+	unsigned int acc = resource->caps.accuracy;
+
+	return sprintf(buf, "%u.%u%%\n", acc / 1000, acc % 1000);
+}
+
+static ssize_t show_name(struct device *dev,
+			 struct device_attribute *devattr,
+			 char *buf)
+{
+	return sprintf(buf, "%s\n", ACPI_POWER_METER_NAME);
+}
+
+/* Sensor descriptions.  If you add a sensor, update NUM_SENSORS above! */
+static struct ro_sensor_template meter_ro_attrs[] = {
+{"power1_average", show_power, 0},
+{"power1_accuracy", show_accuracy, 0},
+{"power1_average_interval_min", show_val, 0},
+{"power1_average_interval_max", show_val, 1},
+{"power1_is_battery", show_val, 5},
+{NULL, NULL, 0},
+};
+
+static struct rw_sensor_template meter_rw_attrs[] = {
+{"power1_average_interval", show_avg_interval, set_avg_interval, 0},
+{NULL, NULL, NULL, 0},
+};
+
+static struct ro_sensor_template misc_cap_attrs[] = {
+{"power1_cap_min", show_val, 2},
+{"power1_cap_max", show_val, 3},
+{"power1_cap_hyst", show_val, 4},
+{"power1_alarm", show_val, 6},
+{NULL, NULL, 0},
+};
+
+static struct ro_sensor_template ro_cap_attrs[] = {
+{"power1_cap", show_cap, 0},
+{NULL, NULL, 0},
+};
+
+static struct rw_sensor_template rw_cap_attrs[] = {
+{"power1_cap", show_cap, set_cap, 0},
+{NULL, NULL, NULL, 0},
+};
+
+static struct rw_sensor_template trip_attrs[] = {
+{"power1_average_min", show_val, set_trip, 7},
+{"power1_average_max", show_val, set_trip, 8},
+{NULL, NULL, NULL, 0},
+};
+
+static struct ro_sensor_template misc_attrs[] = {
+{"name", show_name, 0},
+{"power1_model_number", show_str, 0},
+{"power1_oem_info", show_str, 2},
+{"power1_serial_number", show_str, 1},
+{NULL, NULL, 0},
+};
+
+/* Read power domain data */
+static void remove_domain_devices(struct acpi_power_meter_resource *resource)
+{
+	int i;
+
+	if (!resource->num_domain_devices)
+		return;
+
+	for (i = 0; i < resource->num_domain_devices; i++) {
+		struct acpi_device *obj = resource->domain_devices[i];
+		if (!obj)
+			continue;
+
+		sysfs_remove_link(resource->holders_dir,
+				  kobject_name(&obj->dev.kobj));
+		put_device(&obj->dev);
+	}
+
+	kfree(resource->domain_devices);
+	kobject_put(resource->holders_dir);
+}
+
+static int read_domain_devices(struct acpi_power_meter_resource *resource)
+{
+	int res = 0;
+	int i;
+	struct acpi_buffer buffer = { ACPI_ALLOCATE_BUFFER, NULL };
+	union acpi_object *pss;
+	acpi_status status;
+
+	status = acpi_evaluate_object(resource->acpi_dev->handle, "_PMD", NULL,
+				      &buffer);
+	if (ACPI_FAILURE(status)) {
+		ACPI_EXCEPTION((AE_INFO, status, "Evaluating _PMD"));
+		return -ENODEV;
+	}
+
+	pss = buffer.pointer;
+	if (!pss ||
+	    pss->type != ACPI_TYPE_PACKAGE) {
+		dev_err(&resource->acpi_dev->dev, PREFIX "Invalid _PMD data\n");
+		res = -EFAULT;
+		goto end;
+	}
+
+	if (!pss->package.count)
+		goto end;
+
+	resource->domain_devices = kzalloc(sizeof(struct acpi_device *) *
+					   pss->package.count, GFP_KERNEL);
+	if (!resource->domain_devices) {
+		res = -ENOMEM;
+		goto end;
+	}
+
+	resource->holders_dir = kobject_create_and_add("measures",
+					&resource->acpi_dev->dev.kobj);
+	if (!resource->holders_dir) {
+		res = -ENOMEM;
+		goto exit_free;
+	}
+
+	resource->num_domain_devices = pss->package.count;
+
+	for (i = 0; i < pss->package.count; i++) {
+		struct acpi_device *obj;
+		union acpi_object *element = &(pss->package.elements[i]);
+
+		/* Refuse non-references */
+		if (element->type != ACPI_TYPE_LOCAL_REFERENCE)
+			continue;
+
+		/* Create a symlink to domain objects */
+		resource->domain_devices[i] = NULL;
+		status = acpi_bus_get_device(element->reference.handle,
+					     &resource->domain_devices[i]);
+		if (ACPI_FAILURE(status))
+			continue;
+
+		obj = resource->domain_devices[i];
+		get_device(&obj->dev);
+
+		res = sysfs_create_link(resource->holders_dir, &obj->dev.kobj,
+				      kobject_name(&obj->dev.kobj));
+		if (res) {
+			put_device(&obj->dev);
+			resource->domain_devices[i] = NULL;
+		}
+	}
+
+	res = 0;
+	goto end;
+
+exit_free:
+	kfree(resource->domain_devices);
+end:
+	kfree(buffer.pointer);
+	return res;
+}
+
+/* Registration and deregistration */
+static int register_ro_attrs(struct acpi_power_meter_resource *resource,
+			     struct ro_sensor_template *ro)
+{
+	struct device *dev = &resource->acpi_dev->dev;
+	struct sensor_device_attribute *sensors =
+		&resource->sensors[resource->num_sensors];
+	int res;
+
+	while (ro->label) {
+		sensors->dev_attr.attr.name = ro->label;
+		sensors->dev_attr.attr.mode = S_IRUGO;
+		sensors->dev_attr.show = ro->show;
+		sensors->index = ro->index;
+
+		res = device_create_file(dev, &sensors->dev_attr);
+		if (res) {
+			sensors->dev_attr.attr.name = NULL;
+			goto error;
+		}
+		sensors++;
+		resource->num_sensors++;
+		ro++;
+	}
+
+error:
+	return res;
+}
+
+static int register_rw_attrs(struct acpi_power_meter_resource *resource,
+			     struct rw_sensor_template *rw)
+{
+	struct device *dev = &resource->acpi_dev->dev;
+	struct sensor_device_attribute *sensors =
+		&resource->sensors[resource->num_sensors];
+	int res;
+
+	while (rw->label) {
+		sensors->dev_attr.attr.name = rw->label;
+		sensors->dev_attr.attr.mode = S_IRUGO | S_IWUSR;
+		sensors->dev_attr.show = rw->show;
+		sensors->dev_attr.store = rw->set;
+		sensors->index = rw->index;
+
+		res = device_create_file(dev, &sensors->dev_attr);
+		if (res) {
+			sensors->dev_attr.attr.name = NULL;
+			goto error;
+		}
+		sensors++;
+		resource->num_sensors++;
+		rw++;
+	}
+
+error:
+	return res;
+}
+
+static void remove_attrs(struct acpi_power_meter_resource *resource)
+{
+	int i;
+
+	for (i = 0; i < resource->num_sensors; i++) {
+		if (!resource->sensors[i].dev_attr.attr.name)
+			continue;
+		device_remove_file(&resource->acpi_dev->dev,
+				   &resource->sensors[i].dev_attr);
+	}
+
+	remove_domain_devices(resource);
+
+	resource->num_sensors = 0;
+}
+
+static int setup_attrs(struct acpi_power_meter_resource *resource)
+{
+	int res = 0;
+
+	res = read_domain_devices(resource);
+	if (res)
+		return res;
+
+	if (resource->caps.flags & POWER_METER_CAN_MEASURE) {
+		res = register_ro_attrs(resource, meter_ro_attrs);
+		if (res)
+			goto error;
+		res = register_rw_attrs(resource, meter_rw_attrs);
+		if (res)
+			goto error;
+	}
+
+	if (resource->caps.flags & POWER_METER_CAN_CAP) {
+		if (resource->caps.configurable_cap) {
+			res = register_rw_attrs(resource, rw_cap_attrs);
+			if (res)
+				goto error;
+		} else {
+			res = register_ro_attrs(resource, ro_cap_attrs);
+			if (res)
+				goto error;
+		}
+		res = register_ro_attrs(resource, misc_cap_attrs);
+		if (res)
+			goto error;
+	}
+
+	if (resource->caps.flags & POWER_METER_CAN_TRIP) {
+		res = register_rw_attrs(resource, trip_attrs);
+		if (res)
+			goto error;
+	}
+
+	res = register_ro_attrs(resource, misc_attrs);
+	if (res)
+		goto error;
+
+	return res;
+error:
+	remove_domain_devices(resource);
+	remove_attrs(resource);
+	return res;
+}
+
+static void free_capabilities(struct acpi_power_meter_resource *resource)
+{
+	acpi_string *str;
+	int i;
+
+	str = &resource->model_number;
+	for (i = 0; i < 3; i++, str++)
+		kfree(*str);
+}
+
+static int read_capabilities(struct acpi_power_meter_resource *resource)
+{
+	int res = 0;
+	int i;
+	struct acpi_buffer buffer = { ACPI_ALLOCATE_BUFFER, NULL };
+	struct acpi_buffer state = { 0, NULL };
+	struct acpi_buffer format = { sizeof("NNNNNNNNNNN"), "NNNNNNNNNNN" };
+	union acpi_object *pss;
+	acpi_string *str;
+	acpi_status status;
+
+	status = acpi_evaluate_object(resource->acpi_dev->handle, "_PMC", NULL,
+				      &buffer);
+	if (ACPI_FAILURE(status)) {
+		ACPI_EXCEPTION((AE_INFO, status, "Evaluating _PMC"));
+		return -ENODEV;
+	}
+
+	pss = buffer.pointer;
+	if (!pss ||
+	    pss->type != ACPI_TYPE_PACKAGE ||
+	    pss->package.count != 14) {
+		dev_err(&resource->acpi_dev->dev, PREFIX "Invalid _PMC data\n");
+		res = -EFAULT;
+		goto end;
+	}
+
+	/* Grab all the integer data at once */
+	state.length = sizeof(struct acpi_power_meter_capabilities);
+	state.pointer = &resource->caps;
+
+	status = acpi_extract_package(pss, &format, &state);
+	if (ACPI_FAILURE(status)) {
+		ACPI_EXCEPTION((AE_INFO, status, "Invalid data"));
+		res = -EFAULT;
+		goto end;
+	}
+
+	if (resource->caps.units) {
+		dev_err(&resource->acpi_dev->dev, PREFIX "Unknown unit %llu.\n",
+			resource->caps.units);
+		res = -EINVAL;
+		goto end;
+	}
+
+	/* Grab the string data */
+	str = &resource->model_number;
+
+	for (i = 11; i < 14; i++) {
+		union acpi_object *element = &(pss->package.elements[i]);
+
+		if (element->type != ACPI_TYPE_STRING) {
+			res = -EINVAL;
+			goto error;
+		}
+
+		*str = kzalloc(sizeof(u8) * (element->string.length + 1),
+			       GFP_KERNEL);
+		if (!*str) {
+			res = -ENOMEM;
+			goto error;
+		}
+
+		strncpy(*str, element->string.pointer, element->string.length);
+		str++;
+	}
+
+	dev_info(&resource->acpi_dev->dev, "Found ACPI power meter.\n");
+	goto end;
+error:
+	str = &resource->model_number;
+	for (i = 0; i < 3; i++, str++)
+		kfree(*str);
+end:
+	kfree(buffer.pointer);
+	return res;
+}
+
+/* Handle ACPI event notifications */
+static void acpi_power_meter_notify(struct acpi_device *device, u32 event)
+{
+	struct acpi_power_meter_resource *resource;
+	int res;
+
+	if (!device || !acpi_driver_data(device))
+		return;
+
+	resource = acpi_driver_data(device);
+
+	mutex_lock(&resource->lock);
+	switch (event) {
+	case METER_NOTIFY_CONFIG:
+		free_capabilities(resource);
+		res = read_capabilities(resource);
+		if (res)
+			break;
+
+		remove_attrs(resource);
+		setup_attrs(resource);
+		break;
+	case METER_NOTIFY_TRIP:
+		update_meter(resource);
+		break;
+	case METER_NOTIFY_CAP:
+		update_cap(resource);
+		break;
+	case METER_NOTIFY_INTERVAL:
+		update_avg_interval(resource);
+		break;
+	case METER_NOTIFY_CAPPING:
+		dev_info(&device->dev, "Capping in progress.\n");
+		break;
+	default:
+		BUG();
+	}
+	mutex_unlock(&resource->lock);
+
+	acpi_bus_generate_netlink_event(ACPI_POWER_METER_CLASS,
+					dev_name(&device->dev), event, 0);
+}
+
+static int acpi_power_meter_add(struct acpi_device *device)
+{
+	int res;
+	struct acpi_power_meter_resource *resource;
+
+	if (!device)
+		return -EINVAL;
+
+	resource = kzalloc(sizeof(struct acpi_power_meter_resource),
+			   GFP_KERNEL);
+	if (!resource)
+		return -ENOMEM;
+
+	resource->sensors_valid = 0;
+	resource->acpi_dev = device;
+	mutex_init(&resource->lock);
+	strcpy(acpi_device_name(device), ACPI_POWER_METER_DEVICE_NAME);
+	strcpy(acpi_device_class(device), ACPI_POWER_METER_CLASS);
+	device->driver_data = resource;
+
+	free_capabilities(resource);
+	res = read_capabilities(resource);
+	if (res)
+		goto exit_free;
+
+	resource->trip[0] = resource->trip[1] = -1;
+
+	res = setup_attrs(resource);
+	if (res)
+		goto exit_free;
+
+	resource->hwmon_dev = hwmon_device_register(&device->dev);
+	if (IS_ERR(resource->hwmon_dev)) {
+		res = PTR_ERR(resource->hwmon_dev);
+		goto exit_remove;
+	}
+
+	res = 0;
+	goto exit;
+
+exit_remove:
+	remove_attrs(resource);
+exit_free:
+	kfree(resource);
+exit:
+	return res;
+}
+
+static int acpi_power_meter_remove(struct acpi_device *device, int type)
+{
+	struct acpi_power_meter_resource *resource;
+
+	if (!device || !acpi_driver_data(device))
+		return -EINVAL;
+
+	resource = acpi_driver_data(device);
+	hwmon_device_unregister(resource->hwmon_dev);
+
+	free_capabilities(resource);
+	remove_attrs(resource);
+
+	kfree(resource);
+	return 0;
+}
+
+static int acpi_power_meter_resume(struct acpi_device *device)
+{
+	struct acpi_power_meter_resource *resource;
+
+	if (!device || !acpi_driver_data(device))
+		return -EINVAL;
+
+	resource = acpi_driver_data(device);
+	free_capabilities(resource);
+	read_capabilities(resource);
+
+	return 0;
+}
+
+static struct acpi_driver acpi_power_meter_driver = {
+	.name = "power_meter",
+	.class = ACPI_POWER_METER_CLASS,
+	.ids = power_meter_ids,
+	.ops = {
+		.add = acpi_power_meter_add,
+		.remove = acpi_power_meter_remove,
+		.resume = acpi_power_meter_resume,
+		.notify = acpi_power_meter_notify,
+		},
+};
+
+/* Module init/exit routines */
+
+static int __init acpi_power_meter_init(void)
+{
+	int result;
+
+	if (acpi_disabled)
+		return -ENODEV;
+
+	result = acpi_bus_register_driver(&acpi_power_meter_driver);
+	if (result < 0)
+		return -ENODEV;
+
+	return 0;
+}
+
+static void __exit acpi_power_meter_exit(void)
+{
+	acpi_bus_unregister_driver(&acpi_power_meter_driver);
+}
+
+MODULE_AUTHOR("Darrick J. Wong <djwong@us.ibm.com>");
+MODULE_DESCRIPTION("ACPI 4.0 power meter driver");
+MODULE_LICENSE("GPL");
+
+module_init(acpi_power_meter_init);
+module_exit(acpi_power_meter_exit);

^ permalink raw reply related	[flat|nested] 38+ messages in thread

* [lm-sensors] [PATCH v2] acpi_power_meter: hwmon driver for ACPI 4.0
@ 2009-08-06 20:42         ` Darrick J. Wong
  0 siblings, 0 replies; 38+ messages in thread
From: Darrick J. Wong @ 2009-08-06 20:42 UTC (permalink / raw)
  To: Len Brown; +Cc: linux-acpi, Andrew Morton, Zhang Rui, linux-kernel, lm-sensors

This driver exposes ACPI 4.0 compliant power meters as hardware monitoring
devices.  This second revision of the driver also exports the ACPI string
info as sysfs attributes, a list of the devices that the meter measures,
and will send ACPI notifications over the ACPI netlink socket.

Signed-off-by: Darrick J. Wong <djwong@us.ibm.com>
---

 Documentation/hwmon/acpi_power_meter |   29 +
 drivers/acpi/Kconfig                 |   12 
 drivers/acpi/Makefile                |    1 
 drivers/acpi/power_meter.c           |  968 ++++++++++++++++++++++++++++++++++
 4 files changed, 1010 insertions(+), 0 deletions(-)
 create mode 100644 Documentation/hwmon/acpi_power_meter
 create mode 100644 drivers/acpi/power_meter.c


diff --git a/Documentation/hwmon/acpi_power_meter b/Documentation/hwmon/acpi_power_meter
new file mode 100644
index 0000000..92f5f9c
--- /dev/null
+++ b/Documentation/hwmon/acpi_power_meter
@@ -0,0 +1,29 @@
+Kernel driver power_meter
+============+
+This driver talks to ACPI 4.0 power meters.
+
+Supported systems:
+  * Any recent system with ACPI 4.0.
+    Prefix: 'power_meter'
+    Datasheet: http://acpi.info/, section 10.4.
+
+Author: Darrick J. Wong
+
+Description
+-----------
+
+This driver implements sensor reading support for the power meters exposed in
+the ACPI 4.0 spec (Chapter 10.4).  These devices have a simple set of
+features--a power meter that returns average power use over a configurable
+interval, an optional capping mechanism, and a couple of trip points.
+
+Special Features
+----------------
+
+The power[1-*]_is_battery knob indicates if the power supply is a battery.
+Both power[1-*]_average_{min,max} must be set before the trip points will work.
+The power[1-*]_{model_number, serial_number, oem_info} fields display arbitrary
+strings that ACPI provides with the meter.
+The measures/ directory contains symlinks to the devices that this meter
+measures.
diff --git a/drivers/acpi/Kconfig b/drivers/acpi/Kconfig
index 7ec7d88..8ac801d 100644
--- a/drivers/acpi/Kconfig
+++ b/drivers/acpi/Kconfig
@@ -82,6 +82,18 @@ config ACPI_PROCFS_POWER
 
 	  Say N to delete power /proc/acpi/ directories that have moved to /sys/
 
+config ACPI_POWER_METER
+	tristate "ACPI 4.0 power meter"
+	depends on HWMON
+	default m
+	help
+	  This driver exposes ACPI 4.0 power meters as hardware monitoring
+	  devices.  Say Y (or M) if you have an Intel or AMD computer with
+	  a power meter.
+
+	  To compile this driver as a module, choose M here:
+	  the module will be called power-meter.
+
 config ACPI_SYSFS_POWER
 	bool "Future power /sys interface"
 	select POWER_SUPPLY
diff --git a/drivers/acpi/Makefile b/drivers/acpi/Makefile
index 03a985b..82cd49d 100644
--- a/drivers/acpi/Makefile
+++ b/drivers/acpi/Makefile
@@ -56,6 +56,7 @@ obj-$(CONFIG_ACPI_HOTPLUG_MEMORY) += acpi_memhotplug.o
 obj-$(CONFIG_ACPI_BATTERY)	+= battery.o
 obj-$(CONFIG_ACPI_SBS)		+= sbshc.o
 obj-$(CONFIG_ACPI_SBS)		+= sbs.o
+obj-$(CONFIG_ACPI_POWER_METER)	+= power_meter.o
 
 # processor has its own "processor." module_param namespace
 processor-y			:= processor_core.o processor_throttling.o
diff --git a/drivers/acpi/power_meter.c b/drivers/acpi/power_meter.c
new file mode 100644
index 0000000..3f48dec
--- /dev/null
+++ b/drivers/acpi/power_meter.c
@@ -0,0 +1,968 @@
+/*
+ * A hwmon driver for ACPI 4.0 power meters
+ * Copyright (C) 2009 IBM
+ *
+ * Author: Darrick J. Wong <djwong@us.ibm.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+
+#include <linux/module.h>
+#include <linux/hwmon.h>
+#include <linux/hwmon-sysfs.h>
+#include <linux/jiffies.h>
+#include <linux/mutex.h>
+#include <linux/kdev_t.h>
+#include <linux/sched.h>
+#include <linux/time.h>
+#include <acpi/acpi_drivers.h>
+#include <acpi/acpi_bus.h>
+
+#define ACPI_POWER_METER_NAME		"power_meter"
+ACPI_MODULE_NAME(ACPI_POWER_METER_NAME);
+#define ACPI_POWER_METER_DEVICE_NAME	"Power Meter"
+#define ACPI_POWER_METER_CLASS		"power_meter_resource"
+
+#define NUM_SENSORS			17
+
+#define POWER_METER_CAN_MEASURE	(1 << 0)
+#define POWER_METER_CAN_TRIP	(1 << 1)
+#define POWER_METER_CAN_CAP	(1 << 2)
+#define POWER_METER_CAN_NOTIFY	(1 << 3)
+#define POWER_METER_IS_BATTERY	(1 << 8)
+#define UNKNOWN_HYSTERESIS	0xFFFFFFFF
+
+#define METER_NOTIFY_CONFIG	0x80
+#define METER_NOTIFY_TRIP	0x81
+#define METER_NOTIFY_CAP	0x82
+#define METER_NOTIFY_CAPPING	0x83
+#define METER_NOTIFY_INTERVAL	0x84
+
+static struct acpi_device_id power_meter_ids[] = {
+	{"ACPI000D", 0},
+	{"", 0},
+};
+MODULE_DEVICE_TABLE(acpi, power_meter_ids);
+
+struct acpi_power_meter_capabilities {
+	acpi_integer		flags;
+	acpi_integer		units;
+	acpi_integer		type;
+	acpi_integer		accuracy;
+	acpi_integer		sampling_time;
+	acpi_integer		min_avg_interval;
+	acpi_integer		max_avg_interval;
+	acpi_integer		hysteresis;
+	acpi_integer		configurable_cap;
+	acpi_integer		min_cap;
+	acpi_integer		max_cap;
+};
+
+struct acpi_power_meter_resource {
+	struct acpi_device	*acpi_dev;
+	acpi_bus_id		name;
+	struct mutex		lock;
+	struct device		*hwmon_dev;
+	struct acpi_power_meter_capabilities	caps;
+	acpi_string		model_number;
+	acpi_string		serial_number;
+	acpi_string		oem_info;
+	acpi_integer		power;
+	acpi_integer		cap;
+	acpi_integer		avg_interval;
+	int			sensors_valid;
+	unsigned long		sensors_last_updated;
+	struct sensor_device_attribute	sensors[NUM_SENSORS];
+	int			num_sensors;
+	int			trip[2];
+	int			num_domain_devices;
+	struct acpi_device	**domain_devices;
+	struct kobject		*holders_dir;
+};
+
+struct ro_sensor_template {
+	char *label;
+	ssize_t (*show)(struct device *dev,
+			struct device_attribute *devattr,
+			char *buf);
+	int index;
+};
+
+struct rw_sensor_template {
+	char *label;
+	ssize_t (*show)(struct device *dev,
+			struct device_attribute *devattr,
+			char *buf);
+	ssize_t (*set)(struct device *dev,
+		       struct device_attribute *devattr,
+		       const char *buf, size_t count);
+	int index;
+};
+
+/* Averaging interval */
+static int update_avg_interval(struct acpi_power_meter_resource *resource)
+{
+	unsigned long long data;
+	acpi_status status;
+
+	status = acpi_evaluate_integer(resource->acpi_dev->handle, "_GAI",
+				       NULL, &data);
+	if (ACPI_FAILURE(status)) {
+		ACPI_EXCEPTION((AE_INFO, status, "Evaluating _GAI"));
+		return -ENODEV;
+	}
+
+	resource->avg_interval = data;
+	return 0;
+}
+
+static ssize_t show_avg_interval(struct device *dev,
+				 struct device_attribute *devattr,
+				 char *buf)
+{
+	struct acpi_device *acpi_dev = to_acpi_device(dev);
+	struct acpi_power_meter_resource *resource = acpi_dev->driver_data;
+
+	mutex_lock(&resource->lock);
+	update_avg_interval(resource);
+	mutex_unlock(&resource->lock);
+
+	return sprintf(buf, "%llu\n", resource->avg_interval);
+}
+
+static ssize_t set_avg_interval(struct device *dev,
+				struct device_attribute *devattr,
+				const char *buf, size_t count)
+{
+	struct acpi_device *acpi_dev = to_acpi_device(dev);
+	struct acpi_power_meter_resource *resource = acpi_dev->driver_data;
+	union acpi_object arg0 = { ACPI_TYPE_INTEGER };
+	struct acpi_object_list args = { 1, &arg0 };
+	int res;
+	unsigned long temp;
+	unsigned long long data;
+	acpi_status status;
+
+	res = strict_strtoul(buf, 10, &temp);
+	if (res)
+		return res;
+
+	if (temp > resource->caps.max_avg_interval ||
+	    temp < resource->caps.min_avg_interval)
+		return -EINVAL;
+	arg0.integer.value = temp;
+
+	mutex_lock(&resource->lock);
+	status = acpi_evaluate_integer(resource->acpi_dev->handle, "_PAI",
+				       &args, &data);
+	if (!ACPI_FAILURE(status))
+		resource->avg_interval = temp;
+	mutex_unlock(&resource->lock);
+
+	if (ACPI_FAILURE(status)) {
+		ACPI_EXCEPTION((AE_INFO, status, "Evaluating _PAI"));
+		return -EINVAL;
+	}
+
+	if (data)
+		return -EINVAL;
+
+	return count;
+}
+
+/* Cap functions */
+static int update_cap(struct acpi_power_meter_resource *resource)
+{
+	unsigned long long data;
+	acpi_status status;
+
+	status = acpi_evaluate_integer(resource->acpi_dev->handle, "_GHL",
+				       NULL, &data);
+	if (ACPI_FAILURE(status)) {
+		ACPI_EXCEPTION((AE_INFO, status, "Evaluating _GHL"));
+		return -ENODEV;
+	}
+
+	resource->cap = data;
+	return 0;
+}
+
+static ssize_t show_cap(struct device *dev,
+			struct device_attribute *devattr,
+			char *buf)
+{
+	struct acpi_device *acpi_dev = to_acpi_device(dev);
+	struct acpi_power_meter_resource *resource = acpi_dev->driver_data;
+
+	mutex_lock(&resource->lock);
+	update_cap(resource);
+	mutex_unlock(&resource->lock);
+
+	return sprintf(buf, "%llu\n", resource->cap * 1000);
+}
+
+static ssize_t set_cap(struct device *dev, struct device_attribute *devattr,
+		       const char *buf, size_t count)
+{
+	struct acpi_device *acpi_dev = to_acpi_device(dev);
+	struct acpi_power_meter_resource *resource = acpi_dev->driver_data;
+	union acpi_object arg0 = { ACPI_TYPE_INTEGER };
+	struct acpi_object_list args = { 1, &arg0 };
+	int res;
+	unsigned long temp;
+	unsigned long long data;
+	acpi_status status;
+
+	res = strict_strtoul(buf, 10, &temp);
+	if (res)
+		return res;
+
+	temp /= 1000;
+	if (temp > resource->caps.max_cap || temp < resource->caps.min_cap)
+		return -EINVAL;
+	arg0.integer.value = temp;
+
+	mutex_lock(&resource->lock);
+	status = acpi_evaluate_integer(resource->acpi_dev->handle, "_SHL",
+				       &args, &data);
+	if (!ACPI_FAILURE(status))
+		resource->cap = temp;
+	mutex_unlock(&resource->lock);
+
+	if (ACPI_FAILURE(status)) {
+		ACPI_EXCEPTION((AE_INFO, status, "Evaluating _SHL"));
+		return -EINVAL;
+	}
+
+	if (data)
+		return -EINVAL;
+
+	return count;
+}
+
+/* Power meter trip points */
+static int set_acpi_trip(struct acpi_power_meter_resource *resource)
+{
+	union acpi_object arg_objs[] = {
+		{ACPI_TYPE_INTEGER},
+		{ACPI_TYPE_INTEGER}
+	};
+	struct acpi_object_list args = { 2, arg_objs };
+	unsigned long long data;
+	acpi_status status;
+
+	/* Both trip levels must be set */
+	if (resource->trip[0] < 0 || resource->trip[1] < 0)
+		return 0;
+
+	/* This driver stores min, max; ACPI wants max, min. */
+	arg_objs[0].integer.value = resource->trip[1];
+	arg_objs[1].integer.value = resource->trip[0];
+
+	status = acpi_evaluate_integer(resource->acpi_dev->handle, "_PTP",
+				       &args, &data);
+	if (ACPI_FAILURE(status)) {
+		ACPI_EXCEPTION((AE_INFO, status, "Evaluating _PTP"));
+		return -EINVAL;
+	}
+
+	return data;
+}
+
+static ssize_t set_trip(struct device *dev, struct device_attribute *devattr,
+			const char *buf, size_t count)
+{
+	struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr);
+	struct acpi_device *acpi_dev = to_acpi_device(dev);
+	struct acpi_power_meter_resource *resource = acpi_dev->driver_data;
+	int res;
+	unsigned long temp;
+
+	res = strict_strtoul(buf, 10, &temp);
+	if (res)
+		return res;
+
+	temp /= 1000;
+	if (temp < 0)
+		return -EINVAL;
+
+	mutex_lock(&resource->lock);
+	resource->trip[attr->index - 7] = temp;
+	res = set_acpi_trip(resource);
+	mutex_unlock(&resource->lock);
+
+	if (res)
+		return res;
+
+	return count;
+}
+
+/* Power meter */
+static int update_meter(struct acpi_power_meter_resource *resource)
+{
+	unsigned long long data;
+	acpi_status status;
+	unsigned long local_jiffies = jiffies;
+
+	if (time_before(local_jiffies, resource->sensors_last_updated +
+			msecs_to_jiffies(resource->caps.sampling_time)) &&
+			resource->sensors_valid)
+		return 0;
+
+	status = acpi_evaluate_integer(resource->acpi_dev->handle, "_PMM",
+				       NULL, &data);
+	if (ACPI_FAILURE(status)) {
+		ACPI_EXCEPTION((AE_INFO, status, "Evaluating _PMM"));
+		return -ENODEV;
+	}
+
+	resource->power = data;
+	resource->sensors_valid = 1;
+	resource->sensors_last_updated = jiffies;
+	return 0;
+}
+
+static ssize_t show_power(struct device *dev,
+			  struct device_attribute *devattr,
+			  char *buf)
+{
+	struct acpi_device *acpi_dev = to_acpi_device(dev);
+	struct acpi_power_meter_resource *resource = acpi_dev->driver_data;
+
+	mutex_lock(&resource->lock);
+	update_meter(resource);
+	mutex_unlock(&resource->lock);
+
+	return sprintf(buf, "%llu\n", resource->power * 1000);
+}
+
+/* Miscellaneous */
+static ssize_t show_str(struct device *dev,
+			struct device_attribute *devattr,
+			char *buf)
+{
+	struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr);
+	struct acpi_device *acpi_dev = to_acpi_device(dev);
+	struct acpi_power_meter_resource *resource = acpi_dev->driver_data;
+	acpi_string val;
+
+	switch (attr->index) {
+	case 0:
+		val = resource->model_number;
+		break;
+	case 1:
+		val = resource->serial_number;
+		break;
+	case 2:
+		val = resource->oem_info;
+		break;
+	default:
+		BUG();
+	}
+
+	return sprintf(buf, "%s\n", val);
+}
+
+static ssize_t show_val(struct device *dev,
+			struct device_attribute *devattr,
+			char *buf)
+{
+	struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr);
+	struct acpi_device *acpi_dev = to_acpi_device(dev);
+	struct acpi_power_meter_resource *resource = acpi_dev->driver_data;
+	acpi_integer val = 0;
+
+	switch (attr->index) {
+	case 0:
+		val = resource->caps.min_avg_interval;
+		break;
+	case 1:
+		val = resource->caps.max_avg_interval;
+		break;
+	case 2:
+		val = resource->caps.min_cap * 1000;
+		break;
+	case 3:
+		val = resource->caps.max_cap * 1000;
+		break;
+	case 4:
+		if (resource->caps.hysteresis = UNKNOWN_HYSTERESIS)
+			return sprintf(buf, "unknown\n");
+
+		val = resource->caps.hysteresis * 1000;
+		break;
+	case 5:
+		if (resource->caps.flags & POWER_METER_IS_BATTERY)
+			val = 1;
+		else
+			val = 0;
+		break;
+	case 6:
+		if (resource->power > resource->cap)
+			val = 1;
+		else
+			val = 0;
+		break;
+	case 7:
+	case 8:
+		if (resource->trip[attr->index - 7] < 0)
+			return sprintf(buf, "unknown\n");
+
+		val = resource->trip[attr->index - 7] * 1000;
+		break;
+	default:
+		BUG();
+	}
+
+	return sprintf(buf, "%llu\n", val);
+}
+
+static ssize_t show_accuracy(struct device *dev,
+			     struct device_attribute *devattr,
+			     char *buf)
+{
+	struct acpi_device *acpi_dev = to_acpi_device(dev);
+	struct acpi_power_meter_resource *resource = acpi_dev->driver_data;
+	unsigned int acc = resource->caps.accuracy;
+
+	return sprintf(buf, "%u.%u%%\n", acc / 1000, acc % 1000);
+}
+
+static ssize_t show_name(struct device *dev,
+			 struct device_attribute *devattr,
+			 char *buf)
+{
+	return sprintf(buf, "%s\n", ACPI_POWER_METER_NAME);
+}
+
+/* Sensor descriptions.  If you add a sensor, update NUM_SENSORS above! */
+static struct ro_sensor_template meter_ro_attrs[] = {
+{"power1_average", show_power, 0},
+{"power1_accuracy", show_accuracy, 0},
+{"power1_average_interval_min", show_val, 0},
+{"power1_average_interval_max", show_val, 1},
+{"power1_is_battery", show_val, 5},
+{NULL, NULL, 0},
+};
+
+static struct rw_sensor_template meter_rw_attrs[] = {
+{"power1_average_interval", show_avg_interval, set_avg_interval, 0},
+{NULL, NULL, NULL, 0},
+};
+
+static struct ro_sensor_template misc_cap_attrs[] = {
+{"power1_cap_min", show_val, 2},
+{"power1_cap_max", show_val, 3},
+{"power1_cap_hyst", show_val, 4},
+{"power1_alarm", show_val, 6},
+{NULL, NULL, 0},
+};
+
+static struct ro_sensor_template ro_cap_attrs[] = {
+{"power1_cap", show_cap, 0},
+{NULL, NULL, 0},
+};
+
+static struct rw_sensor_template rw_cap_attrs[] = {
+{"power1_cap", show_cap, set_cap, 0},
+{NULL, NULL, NULL, 0},
+};
+
+static struct rw_sensor_template trip_attrs[] = {
+{"power1_average_min", show_val, set_trip, 7},
+{"power1_average_max", show_val, set_trip, 8},
+{NULL, NULL, NULL, 0},
+};
+
+static struct ro_sensor_template misc_attrs[] = {
+{"name", show_name, 0},
+{"power1_model_number", show_str, 0},
+{"power1_oem_info", show_str, 2},
+{"power1_serial_number", show_str, 1},
+{NULL, NULL, 0},
+};
+
+/* Read power domain data */
+static void remove_domain_devices(struct acpi_power_meter_resource *resource)
+{
+	int i;
+
+	if (!resource->num_domain_devices)
+		return;
+
+	for (i = 0; i < resource->num_domain_devices; i++) {
+		struct acpi_device *obj = resource->domain_devices[i];
+		if (!obj)
+			continue;
+
+		sysfs_remove_link(resource->holders_dir,
+				  kobject_name(&obj->dev.kobj));
+		put_device(&obj->dev);
+	}
+
+	kfree(resource->domain_devices);
+	kobject_put(resource->holders_dir);
+}
+
+static int read_domain_devices(struct acpi_power_meter_resource *resource)
+{
+	int res = 0;
+	int i;
+	struct acpi_buffer buffer = { ACPI_ALLOCATE_BUFFER, NULL };
+	union acpi_object *pss;
+	acpi_status status;
+
+	status = acpi_evaluate_object(resource->acpi_dev->handle, "_PMD", NULL,
+				      &buffer);
+	if (ACPI_FAILURE(status)) {
+		ACPI_EXCEPTION((AE_INFO, status, "Evaluating _PMD"));
+		return -ENODEV;
+	}
+
+	pss = buffer.pointer;
+	if (!pss ||
+	    pss->type != ACPI_TYPE_PACKAGE) {
+		dev_err(&resource->acpi_dev->dev, PREFIX "Invalid _PMD data\n");
+		res = -EFAULT;
+		goto end;
+	}
+
+	if (!pss->package.count)
+		goto end;
+
+	resource->domain_devices = kzalloc(sizeof(struct acpi_device *) *
+					   pss->package.count, GFP_KERNEL);
+	if (!resource->domain_devices) {
+		res = -ENOMEM;
+		goto end;
+	}
+
+	resource->holders_dir = kobject_create_and_add("measures",
+					&resource->acpi_dev->dev.kobj);
+	if (!resource->holders_dir) {
+		res = -ENOMEM;
+		goto exit_free;
+	}
+
+	resource->num_domain_devices = pss->package.count;
+
+	for (i = 0; i < pss->package.count; i++) {
+		struct acpi_device *obj;
+		union acpi_object *element = &(pss->package.elements[i]);
+
+		/* Refuse non-references */
+		if (element->type != ACPI_TYPE_LOCAL_REFERENCE)
+			continue;
+
+		/* Create a symlink to domain objects */
+		resource->domain_devices[i] = NULL;
+		status = acpi_bus_get_device(element->reference.handle,
+					     &resource->domain_devices[i]);
+		if (ACPI_FAILURE(status))
+			continue;
+
+		obj = resource->domain_devices[i];
+		get_device(&obj->dev);
+
+		res = sysfs_create_link(resource->holders_dir, &obj->dev.kobj,
+				      kobject_name(&obj->dev.kobj));
+		if (res) {
+			put_device(&obj->dev);
+			resource->domain_devices[i] = NULL;
+		}
+	}
+
+	res = 0;
+	goto end;
+
+exit_free:
+	kfree(resource->domain_devices);
+end:
+	kfree(buffer.pointer);
+	return res;
+}
+
+/* Registration and deregistration */
+static int register_ro_attrs(struct acpi_power_meter_resource *resource,
+			     struct ro_sensor_template *ro)
+{
+	struct device *dev = &resource->acpi_dev->dev;
+	struct sensor_device_attribute *sensors +		&resource->sensors[resource->num_sensors];
+	int res;
+
+	while (ro->label) {
+		sensors->dev_attr.attr.name = ro->label;
+		sensors->dev_attr.attr.mode = S_IRUGO;
+		sensors->dev_attr.show = ro->show;
+		sensors->index = ro->index;
+
+		res = device_create_file(dev, &sensors->dev_attr);
+		if (res) {
+			sensors->dev_attr.attr.name = NULL;
+			goto error;
+		}
+		sensors++;
+		resource->num_sensors++;
+		ro++;
+	}
+
+error:
+	return res;
+}
+
+static int register_rw_attrs(struct acpi_power_meter_resource *resource,
+			     struct rw_sensor_template *rw)
+{
+	struct device *dev = &resource->acpi_dev->dev;
+	struct sensor_device_attribute *sensors +		&resource->sensors[resource->num_sensors];
+	int res;
+
+	while (rw->label) {
+		sensors->dev_attr.attr.name = rw->label;
+		sensors->dev_attr.attr.mode = S_IRUGO | S_IWUSR;
+		sensors->dev_attr.show = rw->show;
+		sensors->dev_attr.store = rw->set;
+		sensors->index = rw->index;
+
+		res = device_create_file(dev, &sensors->dev_attr);
+		if (res) {
+			sensors->dev_attr.attr.name = NULL;
+			goto error;
+		}
+		sensors++;
+		resource->num_sensors++;
+		rw++;
+	}
+
+error:
+	return res;
+}
+
+static void remove_attrs(struct acpi_power_meter_resource *resource)
+{
+	int i;
+
+	for (i = 0; i < resource->num_sensors; i++) {
+		if (!resource->sensors[i].dev_attr.attr.name)
+			continue;
+		device_remove_file(&resource->acpi_dev->dev,
+				   &resource->sensors[i].dev_attr);
+	}
+
+	remove_domain_devices(resource);
+
+	resource->num_sensors = 0;
+}
+
+static int setup_attrs(struct acpi_power_meter_resource *resource)
+{
+	int res = 0;
+
+	res = read_domain_devices(resource);
+	if (res)
+		return res;
+
+	if (resource->caps.flags & POWER_METER_CAN_MEASURE) {
+		res = register_ro_attrs(resource, meter_ro_attrs);
+		if (res)
+			goto error;
+		res = register_rw_attrs(resource, meter_rw_attrs);
+		if (res)
+			goto error;
+	}
+
+	if (resource->caps.flags & POWER_METER_CAN_CAP) {
+		if (resource->caps.configurable_cap) {
+			res = register_rw_attrs(resource, rw_cap_attrs);
+			if (res)
+				goto error;
+		} else {
+			res = register_ro_attrs(resource, ro_cap_attrs);
+			if (res)
+				goto error;
+		}
+		res = register_ro_attrs(resource, misc_cap_attrs);
+		if (res)
+			goto error;
+	}
+
+	if (resource->caps.flags & POWER_METER_CAN_TRIP) {
+		res = register_rw_attrs(resource, trip_attrs);
+		if (res)
+			goto error;
+	}
+
+	res = register_ro_attrs(resource, misc_attrs);
+	if (res)
+		goto error;
+
+	return res;
+error:
+	remove_domain_devices(resource);
+	remove_attrs(resource);
+	return res;
+}
+
+static void free_capabilities(struct acpi_power_meter_resource *resource)
+{
+	acpi_string *str;
+	int i;
+
+	str = &resource->model_number;
+	for (i = 0; i < 3; i++, str++)
+		kfree(*str);
+}
+
+static int read_capabilities(struct acpi_power_meter_resource *resource)
+{
+	int res = 0;
+	int i;
+	struct acpi_buffer buffer = { ACPI_ALLOCATE_BUFFER, NULL };
+	struct acpi_buffer state = { 0, NULL };
+	struct acpi_buffer format = { sizeof("NNNNNNNNNNN"), "NNNNNNNNNNN" };
+	union acpi_object *pss;
+	acpi_string *str;
+	acpi_status status;
+
+	status = acpi_evaluate_object(resource->acpi_dev->handle, "_PMC", NULL,
+				      &buffer);
+	if (ACPI_FAILURE(status)) {
+		ACPI_EXCEPTION((AE_INFO, status, "Evaluating _PMC"));
+		return -ENODEV;
+	}
+
+	pss = buffer.pointer;
+	if (!pss ||
+	    pss->type != ACPI_TYPE_PACKAGE ||
+	    pss->package.count != 14) {
+		dev_err(&resource->acpi_dev->dev, PREFIX "Invalid _PMC data\n");
+		res = -EFAULT;
+		goto end;
+	}
+
+	/* Grab all the integer data at once */
+	state.length = sizeof(struct acpi_power_meter_capabilities);
+	state.pointer = &resource->caps;
+
+	status = acpi_extract_package(pss, &format, &state);
+	if (ACPI_FAILURE(status)) {
+		ACPI_EXCEPTION((AE_INFO, status, "Invalid data"));
+		res = -EFAULT;
+		goto end;
+	}
+
+	if (resource->caps.units) {
+		dev_err(&resource->acpi_dev->dev, PREFIX "Unknown unit %llu.\n",
+			resource->caps.units);
+		res = -EINVAL;
+		goto end;
+	}
+
+	/* Grab the string data */
+	str = &resource->model_number;
+
+	for (i = 11; i < 14; i++) {
+		union acpi_object *element = &(pss->package.elements[i]);
+
+		if (element->type != ACPI_TYPE_STRING) {
+			res = -EINVAL;
+			goto error;
+		}
+
+		*str = kzalloc(sizeof(u8) * (element->string.length + 1),
+			       GFP_KERNEL);
+		if (!*str) {
+			res = -ENOMEM;
+			goto error;
+		}
+
+		strncpy(*str, element->string.pointer, element->string.length);
+		str++;
+	}
+
+	dev_info(&resource->acpi_dev->dev, "Found ACPI power meter.\n");
+	goto end;
+error:
+	str = &resource->model_number;
+	for (i = 0; i < 3; i++, str++)
+		kfree(*str);
+end:
+	kfree(buffer.pointer);
+	return res;
+}
+
+/* Handle ACPI event notifications */
+static void acpi_power_meter_notify(struct acpi_device *device, u32 event)
+{
+	struct acpi_power_meter_resource *resource;
+	int res;
+
+	if (!device || !acpi_driver_data(device))
+		return;
+
+	resource = acpi_driver_data(device);
+
+	mutex_lock(&resource->lock);
+	switch (event) {
+	case METER_NOTIFY_CONFIG:
+		free_capabilities(resource);
+		res = read_capabilities(resource);
+		if (res)
+			break;
+
+		remove_attrs(resource);
+		setup_attrs(resource);
+		break;
+	case METER_NOTIFY_TRIP:
+		update_meter(resource);
+		break;
+	case METER_NOTIFY_CAP:
+		update_cap(resource);
+		break;
+	case METER_NOTIFY_INTERVAL:
+		update_avg_interval(resource);
+		break;
+	case METER_NOTIFY_CAPPING:
+		dev_info(&device->dev, "Capping in progress.\n");
+		break;
+	default:
+		BUG();
+	}
+	mutex_unlock(&resource->lock);
+
+	acpi_bus_generate_netlink_event(ACPI_POWER_METER_CLASS,
+					dev_name(&device->dev), event, 0);
+}
+
+static int acpi_power_meter_add(struct acpi_device *device)
+{
+	int res;
+	struct acpi_power_meter_resource *resource;
+
+	if (!device)
+		return -EINVAL;
+
+	resource = kzalloc(sizeof(struct acpi_power_meter_resource),
+			   GFP_KERNEL);
+	if (!resource)
+		return -ENOMEM;
+
+	resource->sensors_valid = 0;
+	resource->acpi_dev = device;
+	mutex_init(&resource->lock);
+	strcpy(acpi_device_name(device), ACPI_POWER_METER_DEVICE_NAME);
+	strcpy(acpi_device_class(device), ACPI_POWER_METER_CLASS);
+	device->driver_data = resource;
+
+	free_capabilities(resource);
+	res = read_capabilities(resource);
+	if (res)
+		goto exit_free;
+
+	resource->trip[0] = resource->trip[1] = -1;
+
+	res = setup_attrs(resource);
+	if (res)
+		goto exit_free;
+
+	resource->hwmon_dev = hwmon_device_register(&device->dev);
+	if (IS_ERR(resource->hwmon_dev)) {
+		res = PTR_ERR(resource->hwmon_dev);
+		goto exit_remove;
+	}
+
+	res = 0;
+	goto exit;
+
+exit_remove:
+	remove_attrs(resource);
+exit_free:
+	kfree(resource);
+exit:
+	return res;
+}
+
+static int acpi_power_meter_remove(struct acpi_device *device, int type)
+{
+	struct acpi_power_meter_resource *resource;
+
+	if (!device || !acpi_driver_data(device))
+		return -EINVAL;
+
+	resource = acpi_driver_data(device);
+	hwmon_device_unregister(resource->hwmon_dev);
+
+	free_capabilities(resource);
+	remove_attrs(resource);
+
+	kfree(resource);
+	return 0;
+}
+
+static int acpi_power_meter_resume(struct acpi_device *device)
+{
+	struct acpi_power_meter_resource *resource;
+
+	if (!device || !acpi_driver_data(device))
+		return -EINVAL;
+
+	resource = acpi_driver_data(device);
+	free_capabilities(resource);
+	read_capabilities(resource);
+
+	return 0;
+}
+
+static struct acpi_driver acpi_power_meter_driver = {
+	.name = "power_meter",
+	.class = ACPI_POWER_METER_CLASS,
+	.ids = power_meter_ids,
+	.ops = {
+		.add = acpi_power_meter_add,
+		.remove = acpi_power_meter_remove,
+		.resume = acpi_power_meter_resume,
+		.notify = acpi_power_meter_notify,
+		},
+};
+
+/* Module init/exit routines */
+
+static int __init acpi_power_meter_init(void)
+{
+	int result;
+
+	if (acpi_disabled)
+		return -ENODEV;
+
+	result = acpi_bus_register_driver(&acpi_power_meter_driver);
+	if (result < 0)
+		return -ENODEV;
+
+	return 0;
+}
+
+static void __exit acpi_power_meter_exit(void)
+{
+	acpi_bus_unregister_driver(&acpi_power_meter_driver);
+}
+
+MODULE_AUTHOR("Darrick J. Wong <djwong@us.ibm.com>");
+MODULE_DESCRIPTION("ACPI 4.0 power meter driver");
+MODULE_LICENSE("GPL");
+
+module_init(acpi_power_meter_init);
+module_exit(acpi_power_meter_exit);

_______________________________________________
lm-sensors mailing list
lm-sensors@lm-sensors.org
http://lists.lm-sensors.org/mailman/listinfo/lm-sensors

^ permalink raw reply related	[flat|nested] 38+ messages in thread

* Re: [PATCH 1/2] hwmon: Enhance the sysfs API for power meters.
  2009-07-25  0:43   ` [lm-sensors] [PATCH 1/2] hwmon: Enhance the sysfs API for power Darrick J. Wong
@ 2009-08-07 18:09     ` Pavel Machek
  -1 siblings, 0 replies; 38+ messages in thread
From: Pavel Machek @ 2009-08-07 18:09 UTC (permalink / raw)
  To: Darrick J. Wong
  Cc: Len Brown, Andrew Morton, linux-kernel, lm-sensors, linux-acpi,
	Zhang Rui


> @@ -365,6 +373,16 @@ power[1-*]_average_lowest	Historical average minimum power use
>  				Unit: microWatt
>  				RO
>  
> +power[1-*]_average_max		A notification is sent when power use
> +				rises above this value.
> +				Unit: microWatt
> +				RW
> +

How is the notification sent?

> +power[1-*]_average_min		A notification is sent when power use
> +				sinks below this value.
> +				Unit: microWatt
> +				RW

And what is this good for? Will it wake from sleep? Will it wake from
C3?

...seems like good way to prevent deep sleep states.

> +power[1-*]_cap			If power use rises above this limit, the
> +				system should take action to reduce
> power use.

System as in 'hw'? Or who? And how?
								Pavel
-- 
(english) http://www.livejournal.com/~pavelmachek
(cesky, pictures) http://atrey.karlin.mff.cuni.cz/~pavel/picture/horses/blog.html

^ permalink raw reply	[flat|nested] 38+ messages in thread

* Re: [lm-sensors] [PATCH 1/2] hwmon: Enhance the sysfs API for power
@ 2009-08-07 18:09     ` Pavel Machek
  0 siblings, 0 replies; 38+ messages in thread
From: Pavel Machek @ 2009-08-07 18:09 UTC (permalink / raw)
  To: Darrick J. Wong
  Cc: Len Brown, Andrew Morton, linux-kernel, lm-sensors, linux-acpi,
	Zhang Rui


> @@ -365,6 +373,16 @@ power[1-*]_average_lowest	Historical average minimum power use
>  				Unit: microWatt
>  				RO
>  
> +power[1-*]_average_max		A notification is sent when power use
> +				rises above this value.
> +				Unit: microWatt
> +				RW
> +

How is the notification sent?

> +power[1-*]_average_min		A notification is sent when power use
> +				sinks below this value.
> +				Unit: microWatt
> +				RW

And what is this good for? Will it wake from sleep? Will it wake from
C3?

...seems like good way to prevent deep sleep states.

> +power[1-*]_cap			If power use rises above this limit, the
> +				system should take action to reduce
> power use.

System as in 'hw'? Or who? And how?
								Pavel
-- 
(english) http://www.livejournal.com/~pavelmachek
(cesky, pictures) http://atrey.karlin.mff.cuni.cz/~pavel/picture/horses/blog.html

_______________________________________________
lm-sensors mailing list
lm-sensors@lm-sensors.org
http://lists.lm-sensors.org/mailman/listinfo/lm-sensors

^ permalink raw reply	[flat|nested] 38+ messages in thread

* Re: [PATCH 1/2] hwmon: Enhance the sysfs API for power meters.
  2009-08-07 18:09     ` [lm-sensors] [PATCH 1/2] hwmon: Enhance the sysfs API for power Pavel Machek
@ 2009-08-08 17:40       ` Darrick J. Wong
  -1 siblings, 0 replies; 38+ messages in thread
From: Darrick J. Wong @ 2009-08-08 17:40 UTC (permalink / raw)
  To: Pavel Machek
  Cc: Len Brown, Andrew Morton, linux-kernel, lm-sensors, linux-acpi,
	Zhang Rui

On Fri, Aug 07, 2009 at 08:09:06PM +0200, Pavel Machek wrote:
> 
> > @@ -365,6 +373,16 @@ power[1-*]_average_lowest	Historical average minimum power use
> >  				Unit: microWatt
> >  				RO
> >  
> > +power[1-*]_average_max		A notification is sent when power use
> > +				rises above this value.
> > +				Unit: microWatt
> > +				RW
> > +
> 
> How is the notification sent?

ACPI Notify is sent to the kernel, which passes it to the ACPI netlink socket.

> > +power[1-*]_average_min		A notification is sent when power use
> > +				sinks below this value.
> > +				Unit: microWatt
> > +				RW
> 
> And what is this good for? Will it wake from sleep? Will it wake from
> C3?

All it really does is generates an ACPI Notify event, which is a hint to the OS
that it could re-read the power meter use.
> 
> ...seems like good way to prevent deep sleep states.
> 
> > +power[1-*]_cap			If power use rises above this limit, the
> > +				system should take action to reduce
> > power use.
> 
> System as in 'hw'? Or who? And how?

The ACPI spec is silent on this matter.  It's not clear if the OS is supposed
to monitor and take action on its own when power > cap, or if the hardware/BIOS
will take action, or possibly both...?

--D

^ permalink raw reply	[flat|nested] 38+ messages in thread

* Re: [lm-sensors] [PATCH 1/2] hwmon: Enhance the sysfs API for power
@ 2009-08-08 17:40       ` Darrick J. Wong
  0 siblings, 0 replies; 38+ messages in thread
From: Darrick J. Wong @ 2009-08-08 17:40 UTC (permalink / raw)
  To: Pavel Machek
  Cc: Len Brown, Andrew Morton, linux-kernel, lm-sensors, linux-acpi,
	Zhang Rui

On Fri, Aug 07, 2009 at 08:09:06PM +0200, Pavel Machek wrote:
> 
> > @@ -365,6 +373,16 @@ power[1-*]_average_lowest	Historical average minimum power use
> >  				Unit: microWatt
> >  				RO
> >  
> > +power[1-*]_average_max		A notification is sent when power use
> > +				rises above this value.
> > +				Unit: microWatt
> > +				RW
> > +
> 
> How is the notification sent?

ACPI Notify is sent to the kernel, which passes it to the ACPI netlink socket.

> > +power[1-*]_average_min		A notification is sent when power use
> > +				sinks below this value.
> > +				Unit: microWatt
> > +				RW
> 
> And what is this good for? Will it wake from sleep? Will it wake from
> C3?

All it really does is generates an ACPI Notify event, which is a hint to the OS
that it could re-read the power meter use.
> 
> ...seems like good way to prevent deep sleep states.
> 
> > +power[1-*]_cap			If power use rises above this limit, the
> > +				system should take action to reduce
> > power use.
> 
> System as in 'hw'? Or who? And how?

The ACPI spec is silent on this matter.  It's not clear if the OS is supposed
to monitor and take action on its own when power > cap, or if the hardware/BIOS
will take action, or possibly both...?

--D

_______________________________________________
lm-sensors mailing list
lm-sensors@lm-sensors.org
http://lists.lm-sensors.org/mailman/listinfo/lm-sensors

^ permalink raw reply	[flat|nested] 38+ messages in thread

* Re: [PATCH 1/2] hwmon: Enhance the sysfs API for power meters.
  2009-08-08 17:40       ` [lm-sensors] [PATCH 1/2] hwmon: Enhance the sysfs API for power Darrick J. Wong
@ 2009-08-08 21:48         ` Pavel Machek
  -1 siblings, 0 replies; 38+ messages in thread
From: Pavel Machek @ 2009-08-08 21:48 UTC (permalink / raw)
  To: Darrick J. Wong
  Cc: Len Brown, Andrew Morton, linux-kernel, lm-sensors, linux-acpi,
	Zhang Rui

On Sat 2009-08-08 10:40:20, Darrick J. Wong wrote:
> On Fri, Aug 07, 2009 at 08:09:06PM +0200, Pavel Machek wrote:
> > 
> > > @@ -365,6 +373,16 @@ power[1-*]_average_lowest	Historical average minimum power use
> > >  				Unit: microWatt
> > >  				RO
> > >  
> > > +power[1-*]_average_max		A notification is sent when power use
> > > +				rises above this value.
> > > +				Unit: microWatt
> > > +				RW
> > > +
> > 
> > How is the notification sent?
> 
> ACPI Notify is sent to the kernel, which passes it to the ACPI
> netlink socket.

Uhuh. This is hwmon documentation AFAICT; so a) it should be
documented here, and b) interface should be generic so that it works
without ACPI, too.

> > > +power[1-*]_average_min		A notification is sent when power use
> > > +				sinks below this value.
> > > +				Unit: microWatt
> > > +				RW
> > 
> > And what is this good for? Will it wake from sleep? Will it wake from
> > C3?
> 
> All it really does is generates an ACPI Notify event, which is a hint to the OS
> that it could re-read the power meter use.

So it is one-shot? Document that.

> > ...seems like good way to prevent deep sleep states.
> > 
> > > +power[1-*]_cap			If power use rises above this limit, the
> > > +				system should take action to reduce
> > > power use.
> > 
> > System as in 'hw'? Or who? And how?
> 
> The ACPI spec is silent on this matter.  It's not clear if the OS is supposed
> to monitor and take action on its own when power > cap, or if the hardware/BIOS
> will take action, or possibly both...?

Having user<->kernel interface that is well... uh... undocumented
makes little sense.
									Pavel
-- 
(english) http://www.livejournal.com/~pavelmachek
(cesky, pictures) http://atrey.karlin.mff.cuni.cz/~pavel/picture/horses/blog.html

^ permalink raw reply	[flat|nested] 38+ messages in thread

* Re: [lm-sensors] [PATCH 1/2] hwmon: Enhance the sysfs API for power
@ 2009-08-08 21:48         ` Pavel Machek
  0 siblings, 0 replies; 38+ messages in thread
From: Pavel Machek @ 2009-08-08 21:48 UTC (permalink / raw)
  To: Darrick J. Wong
  Cc: Len Brown, Andrew Morton, linux-kernel, lm-sensors, linux-acpi,
	Zhang Rui

On Sat 2009-08-08 10:40:20, Darrick J. Wong wrote:
> On Fri, Aug 07, 2009 at 08:09:06PM +0200, Pavel Machek wrote:
> > 
> > > @@ -365,6 +373,16 @@ power[1-*]_average_lowest	Historical average minimum power use
> > >  				Unit: microWatt
> > >  				RO
> > >  
> > > +power[1-*]_average_max		A notification is sent when power use
> > > +				rises above this value.
> > > +				Unit: microWatt
> > > +				RW
> > > +
> > 
> > How is the notification sent?
> 
> ACPI Notify is sent to the kernel, which passes it to the ACPI
> netlink socket.

Uhuh. This is hwmon documentation AFAICT; so a) it should be
documented here, and b) interface should be generic so that it works
without ACPI, too.

> > > +power[1-*]_average_min		A notification is sent when power use
> > > +				sinks below this value.
> > > +				Unit: microWatt
> > > +				RW
> > 
> > And what is this good for? Will it wake from sleep? Will it wake from
> > C3?
> 
> All it really does is generates an ACPI Notify event, which is a hint to the OS
> that it could re-read the power meter use.

So it is one-shot? Document that.

> > ...seems like good way to prevent deep sleep states.
> > 
> > > +power[1-*]_cap			If power use rises above this limit, the
> > > +				system should take action to reduce
> > > power use.
> > 
> > System as in 'hw'? Or who? And how?
> 
> The ACPI spec is silent on this matter.  It's not clear if the OS is supposed
> to monitor and take action on its own when power > cap, or if the hardware/BIOS
> will take action, or possibly both...?

Having user<->kernel interface that is well... uh... undocumented
makes little sense.
									Pavel
-- 
(english) http://www.livejournal.com/~pavelmachek
(cesky, pictures) http://atrey.karlin.mff.cuni.cz/~pavel/picture/horses/blog.html

_______________________________________________
lm-sensors mailing list
lm-sensors@lm-sensors.org
http://lists.lm-sensors.org/mailman/listinfo/lm-sensors

^ permalink raw reply	[flat|nested] 38+ messages in thread

* Re: [PATCH 1/2] hwmon: Enhance the sysfs API for power meters.
  2009-08-08 21:48         ` [lm-sensors] [PATCH 1/2] hwmon: Enhance the sysfs API for power Pavel Machek
@ 2009-08-10 21:12           ` Darrick J. Wong
  -1 siblings, 0 replies; 38+ messages in thread
From: Darrick J. Wong @ 2009-08-10 21:12 UTC (permalink / raw)
  To: Pavel Machek
  Cc: Len Brown, Andrew Morton, linux-kernel, lm-sensors, linux-acpi,
	Zhang Rui

On Sat, Aug 08, 2009 at 11:48:30PM +0200, Pavel Machek wrote:
> 
> Uhuh. This is hwmon documentation AFAICT; so a) it should be
> documented here, and b) interface should be generic so that it works
> without ACPI, too.

Perhaps it is time to set up a netlink interface for all the hwmon drivers?
>From what I can tell, some of the chips can send interrupts when alarms go off,
though I don't see much of an effort by the drivers to catch those interrupts.
For sure there's no netlink socket that one can read for that sort of thing; a
far as I can tell, client programs are expected to bang on sysfs periodicially
to figure out when things have gone bad.

<shrug> If there's sufficient interest in building a netlink-like notification
system to replace (or augment) polling, I'll put something together.

> > All it really does is generates an ACPI Notify event, which is a hint to the OS
> > that it could re-read the power meter use.
> 
> So it is one-shot? Document that.

<nod>

> > The ACPI spec is silent on this matter.  It's not clear if the OS is supposed
> > to monitor and take action on its own when power > cap, or if the hardware/BIOS
> > will take action, or possibly both...?
> 
> Having user<->kernel interface that is well... uh... undocumented
> makes little sense.

So it goes with ACPI.  Given that things like Node Manager and Active Energy
Manager claim to be able to control power use, I would guess that it is
generally likely that a power monitoring system would take action on its own to
cut power use, and not necessarily bothering to tell the OS exactly what it's
doing.

--D

^ permalink raw reply	[flat|nested] 38+ messages in thread

* Re: [lm-sensors] [PATCH 1/2] hwmon: Enhance the sysfs API for power
@ 2009-08-10 21:12           ` Darrick J. Wong
  0 siblings, 0 replies; 38+ messages in thread
From: Darrick J. Wong @ 2009-08-10 21:12 UTC (permalink / raw)
  To: Pavel Machek
  Cc: Len Brown, Andrew Morton, linux-kernel, lm-sensors, linux-acpi,
	Zhang Rui

On Sat, Aug 08, 2009 at 11:48:30PM +0200, Pavel Machek wrote:
> 
> Uhuh. This is hwmon documentation AFAICT; so a) it should be
> documented here, and b) interface should be generic so that it works
> without ACPI, too.

Perhaps it is time to set up a netlink interface for all the hwmon drivers?
From what I can tell, some of the chips can send interrupts when alarms go off,
though I don't see much of an effort by the drivers to catch those interrupts.
For sure there's no netlink socket that one can read for that sort of thing; a
far as I can tell, client programs are expected to bang on sysfs periodicially
to figure out when things have gone bad.

<shrug> If there's sufficient interest in building a netlink-like notification
system to replace (or augment) polling, I'll put something together.

> > All it really does is generates an ACPI Notify event, which is a hint to the OS
> > that it could re-read the power meter use.
> 
> So it is one-shot? Document that.

<nod>

> > The ACPI spec is silent on this matter.  It's not clear if the OS is supposed
> > to monitor and take action on its own when power > cap, or if the hardware/BIOS
> > will take action, or possibly both...?
> 
> Having user<->kernel interface that is well... uh... undocumented
> makes little sense.

So it goes with ACPI.  Given that things like Node Manager and Active Energy
Manager claim to be able to control power use, I would guess that it is
generally likely that a power monitoring system would take action on its own to
cut power use, and not necessarily bothering to tell the OS exactly what it's
doing.

--D

_______________________________________________
lm-sensors mailing list
lm-sensors@lm-sensors.org
http://lists.lm-sensors.org/mailman/listinfo/lm-sensors

^ permalink raw reply	[flat|nested] 38+ messages in thread

* Re: [PATCH 1/2] hwmon: Enhance the sysfs API for power meters.
  2009-08-10 21:12           ` [lm-sensors] [PATCH 1/2] hwmon: Enhance the sysfs API for power Darrick J. Wong
@ 2009-08-12 11:56             ` Pavel Machek
  -1 siblings, 0 replies; 38+ messages in thread
From: Pavel Machek @ 2009-08-12 11:56 UTC (permalink / raw)
  To: Darrick J. Wong
  Cc: Len Brown, Andrew Morton, linux-kernel, lm-sensors, linux-acpi,
	Zhang Rui

On Mon 2009-08-10 14:12:11, Darrick J. Wong wrote:
> On Sat, Aug 08, 2009 at 11:48:30PM +0200, Pavel Machek wrote:
> > 
> > Uhuh. This is hwmon documentation AFAICT; so a) it should be
> > documented here, and b) interface should be generic so that it works
> > without ACPI, too.
> 
> Perhaps it is time to set up a netlink interface for all the hwmon drivers?
> >From what I can tell, some of the chips can send interrupts when alarms go off,
> though I don't see much of an effort by the drivers to catch those interrupts.
> For sure there's no netlink socket that one can read for that sort of thing; a
> far as I can tell, client programs are expected to bang on sysfs periodicially
> to figure out when things have gone bad.

Is netlink right interface for that? Perhaps /sys files should be
pollable?

> > > The ACPI spec is silent on this matter.  It's not clear if the OS is supposed
> > > to monitor and take action on its own when power > cap, or if the hardware/BIOS
> > > will take action, or possibly both...?
> > 
> > Having user<->kernel interface that is well... uh... undocumented
> > makes little sense.
> 
> So it goes with ACPI.  Given that things like Node Manager and Active Energy
> Manager claim to be able to control power use, I would guess that it is
> generally likely that a power monitoring system would take action on its own to
> cut power use, and not necessarily bothering to tell the OS exactly what it's
> doing.

I guess this needs to be clarified/documented properly because it
should go anywhere near mainline.
							Pavel
-- 
(english) http://www.livejournal.com/~pavelmachek
(cesky, pictures) http://atrey.karlin.mff.cuni.cz/~pavel/picture/horses/blog.html

^ permalink raw reply	[flat|nested] 38+ messages in thread

* Re: [lm-sensors] [PATCH 1/2] hwmon: Enhance the sysfs API for power
@ 2009-08-12 11:56             ` Pavel Machek
  0 siblings, 0 replies; 38+ messages in thread
From: Pavel Machek @ 2009-08-12 11:56 UTC (permalink / raw)
  To: Darrick J. Wong
  Cc: Len Brown, Andrew Morton, linux-kernel, lm-sensors, linux-acpi,
	Zhang Rui

On Mon 2009-08-10 14:12:11, Darrick J. Wong wrote:
> On Sat, Aug 08, 2009 at 11:48:30PM +0200, Pavel Machek wrote:
> > 
> > Uhuh. This is hwmon documentation AFAICT; so a) it should be
> > documented here, and b) interface should be generic so that it works
> > without ACPI, too.
> 
> Perhaps it is time to set up a netlink interface for all the hwmon drivers?
> >From what I can tell, some of the chips can send interrupts when alarms go off,
> though I don't see much of an effort by the drivers to catch those interrupts.
> For sure there's no netlink socket that one can read for that sort of thing; a
> far as I can tell, client programs are expected to bang on sysfs periodicially
> to figure out when things have gone bad.

Is netlink right interface for that? Perhaps /sys files should be
pollable?

> > > The ACPI spec is silent on this matter.  It's not clear if the OS is supposed
> > > to monitor and take action on its own when power > cap, or if the hardware/BIOS
> > > will take action, or possibly both...?
> > 
> > Having user<->kernel interface that is well... uh... undocumented
> > makes little sense.
> 
> So it goes with ACPI.  Given that things like Node Manager and Active Energy
> Manager claim to be able to control power use, I would guess that it is
> generally likely that a power monitoring system would take action on its own to
> cut power use, and not necessarily bothering to tell the OS exactly what it's
> doing.

I guess this needs to be clarified/documented properly because it
should go anywhere near mainline.
							Pavel
-- 
(english) http://www.livejournal.com/~pavelmachek
(cesky, pictures) http://atrey.karlin.mff.cuni.cz/~pavel/picture/horses/blog.html

_______________________________________________
lm-sensors mailing list
lm-sensors@lm-sensors.org
http://lists.lm-sensors.org/mailman/listinfo/lm-sensors

^ permalink raw reply	[flat|nested] 38+ messages in thread

* Re: [PATCH v2] acpi_power_meter: hwmon driver for ACPI 4.0 power meters
  2009-08-06 20:42         ` [lm-sensors] [PATCH v2] acpi_power_meter: hwmon driver for ACPI 4.0 Darrick J. Wong
@ 2009-08-17 22:05           ` Andrew Morton
  -1 siblings, 0 replies; 38+ messages in thread
From: Andrew Morton @ 2009-08-17 22:05 UTC (permalink / raw)
  To: djwong; +Cc: lenb, linux-acpi, rui.zhang, linux-kernel, lm-sensors

On Thu, 6 Aug 2009 13:42:57 -0700
"Darrick J. Wong" <djwong@us.ibm.com> wrote:

> This driver exposes ACPI 4.0 compliant power meters as hardware monitoring
> devices.  This second revision of the driver also exports the ACPI string
> info as sysfs attributes, a list of the devices that the meter measures,
> and will send ACPI notifications over the ACPI netlink socket.
> 
>
> ...
>
> +static ssize_t set_avg_interval(struct device *dev,
> +				struct device_attribute *devattr,
> +				const char *buf, size_t count)
> +{
> +	struct acpi_device *acpi_dev = to_acpi_device(dev);
> +	struct acpi_power_meter_resource *resource = acpi_dev->driver_data;
> +	union acpi_object arg0 = { ACPI_TYPE_INTEGER };
> +	struct acpi_object_list args = { 1, &arg0 };
> +	int res;
> +	unsigned long temp;
> +	unsigned long long data;
> +	acpi_status status;
> +
> +	res = strict_strtoul(buf, 10, &temp);
> +	if (res)
> +		return res;
> +
> +	if (temp > resource->caps.max_avg_interval ||
> +	    temp < resource->caps.min_avg_interval)
> +		return -EINVAL;
> +	arg0.integer.value = temp;
> +
> +	mutex_lock(&resource->lock);
> +	status = acpi_evaluate_integer(resource->acpi_dev->handle, "_PAI",
> +				       &args, &data);
> +	if (!ACPI_FAILURE(status))
> +		resource->avg_interval = temp;
> +	mutex_unlock(&resource->lock);
> +
> +	if (ACPI_FAILURE(status)) {
> +		ACPI_EXCEPTION((AE_INFO, status, "Evaluating _PAI"));
> +		return -EINVAL;
> +	}
> +
> +	if (data)
> +		return -EINVAL;

I find this test of `data' inexplicable.  Is it just me, or do we need
a comment here?

> +	return count;
> +}
> +
>
> ...
>
> +static ssize_t set_cap(struct device *dev, struct device_attribute *devattr,
> +		       const char *buf, size_t count)
> +{
> +	struct acpi_device *acpi_dev = to_acpi_device(dev);
> +	struct acpi_power_meter_resource *resource = acpi_dev->driver_data;
> +	union acpi_object arg0 = { ACPI_TYPE_INTEGER };
> +	struct acpi_object_list args = { 1, &arg0 };
> +	int res;
> +	unsigned long temp;
> +	unsigned long long data;
> +	acpi_status status;
> +
> +	res = strict_strtoul(buf, 10, &temp);
> +	if (res)
> +		return res;
> +
> +	temp /= 1000;
> +	if (temp > resource->caps.max_cap || temp < resource->caps.min_cap)
> +		return -EINVAL;
> +	arg0.integer.value = temp;
> +
> +	mutex_lock(&resource->lock);
> +	status = acpi_evaluate_integer(resource->acpi_dev->handle, "_SHL",
> +				       &args, &data);
> +	if (!ACPI_FAILURE(status))
> +		resource->cap = temp;
> +	mutex_unlock(&resource->lock);
> +
> +	if (ACPI_FAILURE(status)) {
> +		ACPI_EXCEPTION((AE_INFO, status, "Evaluating _SHL"));
> +		return -EINVAL;
> +	}
> +
> +	if (data)
> +		return -EINVAL;

ditto.

> +	return count;
> +}
> +


^ permalink raw reply	[flat|nested] 38+ messages in thread

* Re: [lm-sensors] [PATCH v2] acpi_power_meter: hwmon driver for ACPI
@ 2009-08-17 22:05           ` Andrew Morton
  0 siblings, 0 replies; 38+ messages in thread
From: Andrew Morton @ 2009-08-17 22:05 UTC (permalink / raw)
  To: djwong; +Cc: lenb, linux-acpi, rui.zhang, linux-kernel, lm-sensors

On Thu, 6 Aug 2009 13:42:57 -0700
"Darrick J. Wong" <djwong@us.ibm.com> wrote:

> This driver exposes ACPI 4.0 compliant power meters as hardware monitoring
> devices.  This second revision of the driver also exports the ACPI string
> info as sysfs attributes, a list of the devices that the meter measures,
> and will send ACPI notifications over the ACPI netlink socket.
> 
>
> ...
>
> +static ssize_t set_avg_interval(struct device *dev,
> +				struct device_attribute *devattr,
> +				const char *buf, size_t count)
> +{
> +	struct acpi_device *acpi_dev = to_acpi_device(dev);
> +	struct acpi_power_meter_resource *resource = acpi_dev->driver_data;
> +	union acpi_object arg0 = { ACPI_TYPE_INTEGER };
> +	struct acpi_object_list args = { 1, &arg0 };
> +	int res;
> +	unsigned long temp;
> +	unsigned long long data;
> +	acpi_status status;
> +
> +	res = strict_strtoul(buf, 10, &temp);
> +	if (res)
> +		return res;
> +
> +	if (temp > resource->caps.max_avg_interval ||
> +	    temp < resource->caps.min_avg_interval)
> +		return -EINVAL;
> +	arg0.integer.value = temp;
> +
> +	mutex_lock(&resource->lock);
> +	status = acpi_evaluate_integer(resource->acpi_dev->handle, "_PAI",
> +				       &args, &data);
> +	if (!ACPI_FAILURE(status))
> +		resource->avg_interval = temp;
> +	mutex_unlock(&resource->lock);
> +
> +	if (ACPI_FAILURE(status)) {
> +		ACPI_EXCEPTION((AE_INFO, status, "Evaluating _PAI"));
> +		return -EINVAL;
> +	}
> +
> +	if (data)
> +		return -EINVAL;

I find this test of `data' inexplicable.  Is it just me, or do we need
a comment here?

> +	return count;
> +}
> +
>
> ...
>
> +static ssize_t set_cap(struct device *dev, struct device_attribute *devattr,
> +		       const char *buf, size_t count)
> +{
> +	struct acpi_device *acpi_dev = to_acpi_device(dev);
> +	struct acpi_power_meter_resource *resource = acpi_dev->driver_data;
> +	union acpi_object arg0 = { ACPI_TYPE_INTEGER };
> +	struct acpi_object_list args = { 1, &arg0 };
> +	int res;
> +	unsigned long temp;
> +	unsigned long long data;
> +	acpi_status status;
> +
> +	res = strict_strtoul(buf, 10, &temp);
> +	if (res)
> +		return res;
> +
> +	temp /= 1000;
> +	if (temp > resource->caps.max_cap || temp < resource->caps.min_cap)
> +		return -EINVAL;
> +	arg0.integer.value = temp;
> +
> +	mutex_lock(&resource->lock);
> +	status = acpi_evaluate_integer(resource->acpi_dev->handle, "_SHL",
> +				       &args, &data);
> +	if (!ACPI_FAILURE(status))
> +		resource->cap = temp;
> +	mutex_unlock(&resource->lock);
> +
> +	if (ACPI_FAILURE(status)) {
> +		ACPI_EXCEPTION((AE_INFO, status, "Evaluating _SHL"));
> +		return -EINVAL;
> +	}
> +
> +	if (data)
> +		return -EINVAL;

ditto.

> +	return count;
> +}
> +


_______________________________________________
lm-sensors mailing list
lm-sensors@lm-sensors.org
http://lists.lm-sensors.org/mailman/listinfo/lm-sensors

^ permalink raw reply	[flat|nested] 38+ messages in thread

* Re: [PATCH v2] acpi_power_meter: hwmon driver for ACPI 4.0 power meters
  2009-08-17 22:05           ` [lm-sensors] [PATCH v2] acpi_power_meter: hwmon driver for ACPI Andrew Morton
@ 2009-08-18 16:24             ` Darrick J. Wong
  -1 siblings, 0 replies; 38+ messages in thread
From: Darrick J. Wong @ 2009-08-18 16:24 UTC (permalink / raw)
  To: Andrew Morton; +Cc: lenb, linux-acpi, rui.zhang, linux-kernel, lm-sensors

On Mon, Aug 17, 2009 at 03:05:58PM -0700, Andrew Morton wrote:
> > +	if (ACPI_FAILURE(status)) {
> > +		ACPI_EXCEPTION((AE_INFO, status, "Evaluating _PAI"));
> > +		return -EINVAL;
> > +	}
> > +
> > +	if (data)
> > +		return -EINVAL;
> 
> I find this test of `data' inexplicable.  Is it just me, or do we need
> a comment here?

ACPI spec says that the _PAI method passes back 0 on success.  I guess I'll
reroll the patch with a comment stating that.

> > +	if (ACPI_FAILURE(status)) {
> > +		ACPI_EXCEPTION((AE_INFO, status, "Evaluating _SHL"));
> > +		return -EINVAL;
> > +	}
> > +
> > +	if (data)
> > +		return -EINVAL;
> 
> ditto.

Ditto.

--D

^ permalink raw reply	[flat|nested] 38+ messages in thread

* Re: [lm-sensors] [PATCH v2] acpi_power_meter: hwmon driver for ACPI
@ 2009-08-18 16:24             ` Darrick J. Wong
  0 siblings, 0 replies; 38+ messages in thread
From: Darrick J. Wong @ 2009-08-18 16:24 UTC (permalink / raw)
  To: Andrew Morton; +Cc: lenb, linux-acpi, rui.zhang, linux-kernel, lm-sensors

On Mon, Aug 17, 2009 at 03:05:58PM -0700, Andrew Morton wrote:
> > +	if (ACPI_FAILURE(status)) {
> > +		ACPI_EXCEPTION((AE_INFO, status, "Evaluating _PAI"));
> > +		return -EINVAL;
> > +	}
> > +
> > +	if (data)
> > +		return -EINVAL;
> 
> I find this test of `data' inexplicable.  Is it just me, or do we need
> a comment here?

ACPI spec says that the _PAI method passes back 0 on success.  I guess I'll
reroll the patch with a comment stating that.

> > +	if (ACPI_FAILURE(status)) {
> > +		ACPI_EXCEPTION((AE_INFO, status, "Evaluating _SHL"));
> > +		return -EINVAL;
> > +	}
> > +
> > +	if (data)
> > +		return -EINVAL;
> 
> ditto.

Ditto.

--D

_______________________________________________
lm-sensors mailing list
lm-sensors@lm-sensors.org
http://lists.lm-sensors.org/mailman/listinfo/lm-sensors

^ permalink raw reply	[flat|nested] 38+ messages in thread

* Re: [PATCH 1/2] hwmon: Enhance the sysfs API for power meters.
  2009-08-12 11:56             ` [lm-sensors] [PATCH 1/2] hwmon: Enhance the sysfs API for power Pavel Machek
@ 2009-08-18 23:32               ` Darrick J. Wong
  -1 siblings, 0 replies; 38+ messages in thread
From: Darrick J. Wong @ 2009-08-18 23:32 UTC (permalink / raw)
  To: Pavel Machek
  Cc: Len Brown, Andrew Morton, linux-kernel, lm-sensors, linux-acpi,
	Zhang Rui

On Wed, Aug 12, 2009 at 01:56:54PM +0200, Pavel Machek wrote:

> Is netlink right interface for that? Perhaps /sys files should be
> pollable?

Since the ACPI meter receives ACPI events, it seems logical to me that these
events should be passed through to ACPI clients.  It also seems logical that
hwmon clients shouldn't have to know about ACPI netlink sockets either, so
I'll combine them both and see what popular consensus thinks of that. :)

Therefore, I updated the patch to make the appropriate sysfs files pollable,
and the hwmon sysfs documentation to state that they can be polled.

> > So it goes with ACPI.  Given that things like Node Manager and Active Energy
> > Manager claim to be able to control power use, I would guess that it is
> > generally likely that a power monitoring system would take action on its own to
> > cut power use, and not necessarily bothering to tell the OS exactly what it's
> > doing.
> 
> I guess this needs to be clarified/documented properly because it
> should go anywhere near mainline.

Ok, I'll clarify the documentation to say that those two scenarios are
possible, and to contact the machine vendor for details.

--D

^ permalink raw reply	[flat|nested] 38+ messages in thread

* Re: [lm-sensors] [PATCH 1/2] hwmon: Enhance the sysfs API for power
@ 2009-08-18 23:32               ` Darrick J. Wong
  0 siblings, 0 replies; 38+ messages in thread
From: Darrick J. Wong @ 2009-08-18 23:32 UTC (permalink / raw)
  To: Pavel Machek
  Cc: Len Brown, Andrew Morton, linux-kernel, lm-sensors, linux-acpi,
	Zhang Rui

On Wed, Aug 12, 2009 at 01:56:54PM +0200, Pavel Machek wrote:

> Is netlink right interface for that? Perhaps /sys files should be
> pollable?

Since the ACPI meter receives ACPI events, it seems logical to me that these
events should be passed through to ACPI clients.  It also seems logical that
hwmon clients shouldn't have to know about ACPI netlink sockets either, so
I'll combine them both and see what popular consensus thinks of that. :)

Therefore, I updated the patch to make the appropriate sysfs files pollable,
and the hwmon sysfs documentation to state that they can be polled.

> > So it goes with ACPI.  Given that things like Node Manager and Active Energy
> > Manager claim to be able to control power use, I would guess that it is
> > generally likely that a power monitoring system would take action on its own to
> > cut power use, and not necessarily bothering to tell the OS exactly what it's
> > doing.
> 
> I guess this needs to be clarified/documented properly because it
> should go anywhere near mainline.

Ok, I'll clarify the documentation to say that those two scenarios are
possible, and to contact the machine vendor for details.

--D

_______________________________________________
lm-sensors mailing list
lm-sensors@lm-sensors.org
http://lists.lm-sensors.org/mailman/listinfo/lm-sensors

^ permalink raw reply	[flat|nested] 38+ messages in thread

* [PATCH 0/2] ACPI 4.0 power meter
@ 2009-08-18 23:47 Darrick J. Wong
  0 siblings, 0 replies; 38+ messages in thread
From: Darrick J. Wong @ 2009-08-18 23:47 UTC (permalink / raw)
  To: Darrick J. Wong, Len Brown, Andrew Morton
  Cc: linux-kernel, lm-sensors, linux-acpi, Zhang Rui, Pavel Machek

Hi all,

This is a two-part patchset.  The first patch extends the hwmon sysfs interface
to include power capping features and extra data about the accuracy and
internal operation of the power meter.  The second patch implements a hwmon
driver for ACPI 4.0 compliant power meters that uses the expanded sysfs
interface.  I don't have an example DSDT from any actual piece of hardware, but
I can send some sample ASL code that I've been using to outfit kvm with a power
meter if anyone requests it.

This latest revision updates the driver itself to send sysfs poll notifications
upon receipt of ACPI events and updates the sysfs hwmon documentation to
reflect that.

--D

^ permalink raw reply	[flat|nested] 38+ messages in thread

end of thread, other threads:[~2009-08-18 23:37 UTC | newest]

Thread overview: 38+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2009-07-25  0:43 [PATCH 0/2] ACPI 4.0 power meter Darrick J. Wong
2009-07-25  0:43 ` [lm-sensors] " Darrick J. Wong
2009-07-25  0:43 ` [PATCH 1/2] hwmon: Enhance the sysfs API for power meters Darrick J. Wong
2009-07-25  0:43   ` [lm-sensors] [PATCH 1/2] hwmon: Enhance the sysfs API for power Darrick J. Wong
2009-08-07 18:09   ` [PATCH 1/2] hwmon: Enhance the sysfs API for power meters Pavel Machek
2009-08-07 18:09     ` [lm-sensors] [PATCH 1/2] hwmon: Enhance the sysfs API for power Pavel Machek
2009-08-08 17:40     ` [PATCH 1/2] hwmon: Enhance the sysfs API for power meters Darrick J. Wong
2009-08-08 17:40       ` [lm-sensors] [PATCH 1/2] hwmon: Enhance the sysfs API for power Darrick J. Wong
2009-08-08 21:48       ` [PATCH 1/2] hwmon: Enhance the sysfs API for power meters Pavel Machek
2009-08-08 21:48         ` [lm-sensors] [PATCH 1/2] hwmon: Enhance the sysfs API for power Pavel Machek
2009-08-10 21:12         ` [PATCH 1/2] hwmon: Enhance the sysfs API for power meters Darrick J. Wong
2009-08-10 21:12           ` [lm-sensors] [PATCH 1/2] hwmon: Enhance the sysfs API for power Darrick J. Wong
2009-08-12 11:56           ` [PATCH 1/2] hwmon: Enhance the sysfs API for power meters Pavel Machek
2009-08-12 11:56             ` [lm-sensors] [PATCH 1/2] hwmon: Enhance the sysfs API for power Pavel Machek
2009-08-18 23:32             ` [PATCH 1/2] hwmon: Enhance the sysfs API for power meters Darrick J. Wong
2009-08-18 23:32               ` [lm-sensors] [PATCH 1/2] hwmon: Enhance the sysfs API for power Darrick J. Wong
2009-07-25  0:43 ` [PATCH 2/2] acpi_power_meter: hwmon driver for ACPI 4.0 power meters Darrick J. Wong
2009-07-25  0:43   ` [lm-sensors] [PATCH 2/2] acpi_power_meter: hwmon driver for ACPI Darrick J. Wong
2009-07-27  1:44   ` [PATCH 2/2] acpi_power_meter: hwmon driver for ACPI 4.0 power meters ykzhao
2009-07-27  1:44     ` [lm-sensors] [PATCH 2/2] acpi_power_meter: hwmon driver for ykzhao
2009-08-03 20:48     ` [PATCH 2/2] acpi_power_meter: hwmon driver for ACPI 4.0 power meters Darrick J. Wong
2009-08-03 20:48       ` [lm-sensors] [PATCH 2/2] acpi_power_meter: hwmon driver for Darrick J. Wong
2009-07-27  6:45   ` [PATCH 2/2] acpi_power_meter: hwmon driver for ACPI 4.0 power meters Zhang Rui
2009-07-27  6:45     ` [lm-sensors] [PATCH 2/2] acpi_power_meter: hwmon driver for Zhang Rui
2009-08-03 20:52     ` [PATCH 2/2] acpi_power_meter: hwmon driver for ACPI 4.0 power meters Darrick J. Wong
2009-08-03 20:52       ` [lm-sensors] [PATCH 2/2] acpi_power_meter: hwmon driver for Darrick J. Wong
2009-08-03 20:52       ` [PATCH 2/2] acpi_power_meter: hwmon driver for ACPI 4.0 power meters Darrick J. Wong
2009-07-28  1:25   ` Len Brown
2009-07-28  1:25     ` [lm-sensors] [PATCH 2/2] acpi_power_meter: hwmon driver for Len Brown
2009-08-03 20:58     ` [PATCH 2/2] acpi_power_meter: hwmon driver for ACPI 4.0 power meters Darrick J. Wong
2009-08-03 20:58       ` [lm-sensors] [PATCH 2/2] acpi_power_meter: hwmon driver for Darrick J. Wong
2009-08-06 20:42       ` [PATCH v2] acpi_power_meter: hwmon driver for ACPI 4.0 power meters Darrick J. Wong
2009-08-06 20:42         ` [lm-sensors] [PATCH v2] acpi_power_meter: hwmon driver for ACPI 4.0 Darrick J. Wong
2009-08-17 22:05         ` [PATCH v2] acpi_power_meter: hwmon driver for ACPI 4.0 power meters Andrew Morton
2009-08-17 22:05           ` [lm-sensors] [PATCH v2] acpi_power_meter: hwmon driver for ACPI Andrew Morton
2009-08-18 16:24           ` [PATCH v2] acpi_power_meter: hwmon driver for ACPI 4.0 power meters Darrick J. Wong
2009-08-18 16:24             ` [lm-sensors] [PATCH v2] acpi_power_meter: hwmon driver for ACPI Darrick J. Wong
2009-08-18 23:47 [PATCH 0/2] ACPI 4.0 power meter Darrick J. Wong

This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.