LKML Archive on lore.kernel.org
help / color / mirror / Atom feed
* [PATCH 2/2] ibmaem: New driver for power/energy meters in IBM System X hardware
@ 2008-03-28 21:38 Darrick J. Wong
  2008-04-01  3:05 ` Anthony Liguori
  0 siblings, 1 reply; 4+ messages in thread
From: Darrick J. Wong @ 2008-03-28 21:38 UTC (permalink / raw)
  To: Mark M. Hoffman; +Cc: linux-kernel, lm-sensors

New driver to read energy, power, and temperature meters in various
IBM System X hardware.

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

 Documentation/hwmon/ibmaem |   37 +
 drivers/hwmon/Kconfig      |   14 +
 drivers/hwmon/Makefile     |    1 
 drivers/hwmon/ibmaem.c     | 1151 ++++++++++++++++++++++++++++++++++++++++++++
 4 files changed, 1203 insertions(+), 0 deletions(-)

diff --git a/Documentation/hwmon/ibmaem b/Documentation/hwmon/ibmaem
new file mode 100644
index 0000000..238e87d
--- /dev/null
+++ b/Documentation/hwmon/ibmaem
@@ -0,0 +1,37 @@
+Kernel driver ibmaem
+======================
+
+Supported systems:
+  * Any recent IBM System X server with Active Energy Manager support.
+    This includes the x3350, x3550, x3650, x3655, x3755, x3850 M2,
+    x3950 M2, and certain HS2x/LS2x/QS2x blades.  The IPMI host interface
+    driver ("ipmi-si") needs to be loaded for this driver to do anything.
+    Prefix: 'ibmaem'
+    Datasheet: Not available
+
+Author: Darrick J. Wong
+
+Description
+-----------
+
+This driver implements sensor reading support for the energy and power
+meters available on various IBM System X hardware through the BMC.  All
+sensor banks will be exported as platform devices; this driver can talk
+to both v1 and v2 interfaces.  This driver is completely separate from the
+older ibmpex driver.
+
+The v1 AEM interface has a simple set of features to monitor energy use.
+There is a register that displays an estimate of raw energy consumption
+since the last BMC reset, and a power sensor that returns average power
+use over a configurable interval.
+
+The v2 AEM interface is a bit more sophisticated, being able to present
+a wider range of energy and power use registers, the power cap as
+set by the AEM software, and temperature sensors.
+
+Special Features
+----------------
+
+The "power_cap" value displays the current system power cap, as set by
+the Active Energy Manager software.  Setting the power cap from the host
+is not currently supported.
diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig
index 4dc76bc..00ff533 100644
--- a/drivers/hwmon/Kconfig
+++ b/drivers/hwmon/Kconfig
@@ -330,6 +330,20 @@ config SENSORS_CORETEMP
 	  sensor inside your CPU. Supported all are all known variants
 	  of Intel Core family.
 
+config SENSORS_IBMAEM
+	tristate "IBM Active Energy Manager temperature/power sensors and control"
+	select IPMI_SI
+	depends on IPMI_HANDLER
+	help
+	  If you say yes here you get support for the temperature and
+	  power sensors and capping hardware in various IBM System X
+	  servers that support Active Energy Manager.  This includes
+	  the x3350, x3550, x3650, x3655, x3755, x3850 M2, x3950 M2,
+	  and certain HS2x/LS2x/QS2x blades.
+
+	  This driver can also be built as a module.  If so, the module
+	  will be called ibmaem.
+
 config SENSORS_IBMPEX
 	tristate "IBM PowerExecutive temperature/power sensors"
 	select IPMI_SI
diff --git a/drivers/hwmon/Makefile b/drivers/hwmon/Makefile
index 3bdb05a..d098677 100644
--- a/drivers/hwmon/Makefile
+++ b/drivers/hwmon/Makefile
@@ -41,6 +41,7 @@ obj-$(CONFIG_SENSORS_GL518SM)	+= gl518sm.o
 obj-$(CONFIG_SENSORS_GL520SM)	+= gl520sm.o
 obj-$(CONFIG_SENSORS_HDAPS)	+= hdaps.o
 obj-$(CONFIG_SENSORS_I5K_AMB)	+= i5k_amb.o
+obj-$(CONFIG_SENSORS_IBMAEM)	+= ibmaem.o
 obj-$(CONFIG_SENSORS_IBMPEX)	+= ibmpex.o
 obj-$(CONFIG_SENSORS_IT87)	+= it87.o
 obj-$(CONFIG_SENSORS_K8TEMP)	+= k8temp.o
diff --git a/drivers/hwmon/ibmaem.c b/drivers/hwmon/ibmaem.c
new file mode 100644
index 0000000..477eadd
--- /dev/null
+++ b/drivers/hwmon/ibmaem.c
@@ -0,0 +1,1151 @@
+/*
+ * A hwmon driver for the IBM Active Energy Manager temperature/power sensors
+ * and capping functionality.
+ * Copyright (C) 2008 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/ipmi.h>
+#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/spinlock.h>
+#include <linux/idr.h>
+#include <linux/sched.h>
+#include <linux/platform_device.h>
+
+#define REFRESH_INTERVAL	(HZ)
+#define DRVNAME			"aem"
+
+#define AEM_NETFN		0x2E
+
+#define AEM_FIND_FW_CMD		0x80
+#define AEM_ELEMENT_CMD		0x81
+#define AEM_FW_INSTANCE_CMD	0x82
+
+#define AEM_READ_ELEMENT_CFG	0x80
+#define AEM_READ_BUFFER		0x81
+#define AEM_READ_REGISTER	0x82
+#define AEM_WRITE_REGISTER	0x83
+#define AEM_SET_REG_MASK	0x84
+#define AEM_CLEAR_REG_MASK	0x85
+#define AEM_READ_ELEMENT_CFG2	0x86
+
+#define AEM_CONTROL_ELEMENT	0
+#define AEM_ENERGY_ELEMENT	1
+#define AEM_CLOCK_ELEMENT	4
+#define AEM_POWER_CAP_ELEMENT	7
+#define AEM_EXHAUST_ELEMENT	9
+#define AEM_POWER_ELEMENT	10
+
+#define AEM_MODULE_TYPE_ID	0x0001
+
+#define AEM2_NUM_ENERGY_REGS	2
+#define AEM2_NUM_PCAP_REGS	6
+#define AEM2_NUM_TEMP_REGS	2
+#define AEM2_NUM_SENSORS	14
+
+#define AEM1_NUM_ENERGY_REGS	1
+#define AEM1_NUM_SENSORS	3
+
+#define POWER_CAP		0
+#define POWER_CAP_MAX_HOTPLUG	1
+#define POWER_CAP_MAX		2
+#define	POWER_CAP_MIN_WARNING	3
+#define POWER_CAP_MIN		4
+#define	POWER_AUX		5
+
+#define AEM_DEFAULT_POWER_INTERVAL 1000
+#define AEM_MIN_POWER_INTERVAL	200
+
+static DEFINE_IDR(aem1_idr);
+static DEFINE_SPINLOCK(aem1_idr_lock);
+static DEFINE_IDR(aem2_idr);
+static DEFINE_SPINLOCK(aem2_idr_lock);
+
+struct aem_ipmi_data;
+struct aem1_data;
+struct aem2_data;
+
+static void aem_register_bmc(int iface, struct device *dev);
+static void aem_bmc_gone(int iface);
+static void aem_msg_handler(struct ipmi_recv_msg *msg, void *user_msg_data);
+static void aem_init_aem1(struct aem_ipmi_data *probe);
+static void aem1_delete(struct aem1_data *data);
+static void aem_init_aem2(struct aem_ipmi_data *probe);
+static void aem2_delete(struct aem2_data *data);
+static void aem1_remove_sensors(struct aem1_data *data);
+static int aem1_find_sensors(struct aem1_data *data);
+static void aem2_remove_sensors(struct aem2_data *data);
+static int aem2_find_sensors(struct aem2_data *data);
+
+static struct device_driver aem_driver = {
+	.name = DRVNAME,
+	.bus = &platform_bus_type,
+};
+
+struct aem_ipmi_data {
+	struct completion	read_complete;
+	struct ipmi_addr	address;
+	ipmi_user_t		user;
+	int			interface;
+
+	struct kernel_ipmi_msg	tx_message;
+	long			tx_msgid;
+
+	void			*rx_msg_data;
+	unsigned short		rx_msg_len;
+	unsigned char		rx_result;
+	int			rx_recv_type;
+
+	struct device		*bmc_device;
+};
+
+struct aem_fw_data {
+	struct device		*hwmon_dev;
+	struct platform_device	*pdev;
+	struct mutex		lock;
+	char			valid;
+	unsigned long		last_updated;	/* In jiffies */
+	u8			ver_major;
+	u8			ver_minor;
+	u8			module_handle;
+	int			id;
+	struct aem_ipmi_data	ipmi;
+};
+
+struct aem_ro_sensor_template {
+	char *label;
+	ssize_t (*show)(struct device *dev,
+			struct device_attribute *devattr,
+			char *buf);
+	int index;
+};
+
+struct aem_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;
+};
+
+struct aem1_data {
+	struct aem_fw_data	fw;
+	struct list_head	list;
+
+	/*
+	 * Available sensors:
+	 * Energy meter
+	 * Power meter
+	 */
+	struct sensor_device_attribute	sensors[AEM1_NUM_SENSORS];
+
+	/* energy use */
+	u64			energy[AEM1_NUM_ENERGY_REGS];
+
+	int			power_period[AEM1_NUM_ENERGY_REGS];
+};
+
+struct aem2_data {
+	struct aem_fw_data	fw;
+	struct list_head	list;
+
+	/*
+	 * Available sensors:
+	 * Two energy meters
+	 * Two power meters
+	 * Two temperature sensors
+	 * Six powercap registers
+	 */
+	struct sensor_device_attribute	sensors[AEM2_NUM_SENSORS];
+
+	/* energy use */
+	u64			energy[AEM2_NUM_ENERGY_REGS];
+
+	/* power caps */
+	u16			pcap[AEM2_NUM_PCAP_REGS];
+
+	/* exhaust temperature */
+	u8			temp[AEM2_NUM_TEMP_REGS];
+
+	/* power sampling interval */
+	int			power_period[AEM2_NUM_ENERGY_REGS];
+};
+
+/* Data structures returned by the AEM firmware */
+struct aem_iana_id {
+	u8			bytes[3];
+};
+static struct aem_iana_id system_x_id = {
+	.bytes = {0x4D, 0x4F, 0x00}
+};
+
+/* These are used to find AEM1 instances */
+struct aem_find_firmware_req {
+	struct aem_iana_id	id;
+	u8			rsvd;
+	u16			index;
+	u16			module_type_id;
+} __attribute__ ((packed));
+
+struct aem_find_firmware_resp {
+	struct aem_iana_id	id;
+	u8			num_instances;
+} __attribute__ ((packed));
+
+/* These are used to find AEM2 instances */
+struct aem_find_instance_req {
+	struct aem_iana_id	id;
+	u8			instance_number;
+	u16			module_type_id;
+} __attribute__ ((packed));
+
+struct aem_find_instance_resp {
+	struct aem_iana_id	id;
+	u8			num_instances;
+	u8			major;
+	u8			minor;
+	u8			module_handle;
+	u16			record_id;
+} __attribute__ ((packed));
+
+/* These are used to query sensors */
+struct aem_read_sensor_req {
+	struct aem_iana_id	id;
+	u8			module_handle;
+	u8			element;
+	u8			subcommand;
+	u8			reg;
+	u8			rx_buf_size;
+} __attribute__ ((packed));
+
+struct aem_read_sensor_resp {
+	struct aem_iana_id	id;
+	u8			bytes[0];
+} __attribute__ ((packed));
+
+/* Data structures to talk to the IPMI layer */
+struct aem_driver_data {
+	struct list_head	aem1_devices;
+	struct list_head	aem2_devices;
+	struct ipmi_smi_watcher	bmc_events;
+	struct ipmi_user_hndl	ipmi_hndlrs;
+};
+
+static struct aem_driver_data driver_data = {
+	.aem1_devices = LIST_HEAD_INIT(driver_data.aem1_devices),
+	.aem2_devices = LIST_HEAD_INIT(driver_data.aem2_devices),
+	.bmc_events = {
+		.owner = THIS_MODULE,
+		.new_smi = aem_register_bmc,
+		.smi_gone = aem_bmc_gone,
+	},
+	.ipmi_hndlrs = {
+		.ipmi_recv_hndl = aem_msg_handler,
+	},
+};
+
+/* Functions to talk to the IPMI layer */
+
+/* Initialize IPMI address, message buffers and user data */
+static int aem_init_ipmi_data(struct aem_ipmi_data *data,
+				 int iface, struct device *bmc)
+{
+	int err;
+
+	init_completion(&data->read_complete);
+	data->bmc_device = bmc;
+
+	/* Initialize IPMI address */
+	data->address.addr_type = IPMI_SYSTEM_INTERFACE_ADDR_TYPE;
+	data->address.channel = IPMI_BMC_CHANNEL;
+	data->address.data[0] = 0;
+	data->interface = iface;
+
+	/* Initialize message buffers */
+	data->tx_msgid = 0;
+	data->tx_message.netfn = AEM_NETFN;
+
+	/* Create IPMI messaging interface user */
+	err = ipmi_create_user(data->interface, &driver_data.ipmi_hndlrs,
+			       data, &data->user);
+	if (err < 0) {
+		dev_err(bmc, "Unable to register user with IPMI "
+			"interface %d\n", data->interface);
+		return -EACCES;
+	}
+
+	return 0;
+}
+
+/* Send an IPMI command */
+static int aem_send_message(struct aem_ipmi_data *data)
+{
+	int err;
+
+	err = ipmi_validate_addr(&data->address, sizeof(data->address));
+	if (err)
+		goto out;
+
+	data->tx_msgid++;
+	err = ipmi_request_settime(data->user, &data->address, data->tx_msgid,
+				   &data->tx_message, data, 0, 0, 0);
+	if (err)
+		goto out1;
+
+	return 0;
+out1:
+	dev_err(data->bmc_device, "request_settime=%x\n", err);
+	return err;
+out:
+	dev_err(data->bmc_device, "validate_addr=%x\n", err);
+	return err;
+}
+
+/* Probe a BMC for AEM firmware instances */
+static void aem_register_bmc(int iface, struct device *dev)
+{
+	struct aem_ipmi_data probe;
+
+	if (aem_init_ipmi_data(&probe, iface, dev))
+		return;
+
+	aem_init_aem1(&probe);
+	aem_init_aem2(&probe);
+
+	ipmi_destroy_user(probe.user);
+}
+
+/* Handle BMC deletion */
+static void aem_bmc_gone(int iface)
+{
+	struct aem1_data *p1, *next1;
+	struct aem2_data *p2, *next2;
+
+	list_for_each_entry_safe(p1, next1, &driver_data.aem1_devices, list) {
+		if (p1->fw.ipmi.interface == iface)
+			aem1_delete(p1);
+	}
+
+	list_for_each_entry_safe(p2, next2, &driver_data.aem2_devices, list) {
+		if (p2->fw.ipmi.interface == iface)
+			aem2_delete(p2);
+	}
+}
+
+/* Dispatch IPMI messages to callers */
+static void aem_msg_handler(struct ipmi_recv_msg *msg, void *user_msg_data)
+{
+	unsigned short rx_len;
+	struct aem_ipmi_data *data = (struct aem_ipmi_data *)user_msg_data;
+
+	if (msg->msgid != data->tx_msgid) {
+		dev_err(data->bmc_device, "Mismatch between received msgid "
+			"(%02x) and transmitted msgid (%02x)!\n",
+			(int)msg->msgid,
+			(int)data->tx_msgid);
+		ipmi_free_recv_msg(msg);
+		return;
+	}
+
+	data->rx_recv_type = msg->recv_type;
+	if (msg->msg.data_len > 0)
+		data->rx_result = msg->msg.data[0];
+	else
+		data->rx_result = IPMI_UNKNOWN_ERR_COMPLETION_CODE;
+
+	if (msg->msg.data_len > 1) {
+		rx_len = msg->msg.data_len - 1;
+		if (data->rx_msg_len < rx_len)
+			rx_len = data->rx_msg_len;
+		data->rx_msg_len = rx_len;
+		memcpy(data->rx_msg_data, msg->msg.data + 1, data->rx_msg_len);
+	} else
+		data->rx_msg_len = 0;
+
+	ipmi_free_recv_msg(msg);
+	complete(&data->read_complete);
+}
+
+/* ID functions */
+
+/* Obtain an id */
+#define AEM_IDR_GET(type) \
+static int type##_idr_get(int *id) \
+{ \
+	int i, err; \
+\
+again: \
+	if (unlikely(idr_pre_get(&type##_idr, GFP_KERNEL) == 0)) \
+		return -ENOMEM; \
+\
+	spin_lock(&type##_idr_lock); \
+	err = idr_get_new(&type##_idr, NULL, &i); \
+	spin_unlock(&type##_idr_lock); \
+\
+	if (unlikely(err == -EAGAIN)) \
+		goto again; \
+	else if (unlikely(err)) \
+		return err; \
+\
+	*id = i & MAX_ID_MASK; \
+	return 0; \
+}
+
+/* Release an object ID */
+#define AEM_IDR_PUT(type) \
+static void type##_idr_put(int id) \
+{ \
+	spin_lock(&type##_idr_lock); \
+	idr_remove(&type##_idr, id); \
+	spin_unlock(&type##_idr_lock); \
+}
+
+/* Probe functions for AEM1 devices */
+
+/* Get an object ID for AEM1 device */
+AEM_IDR_GET(aem1)
+
+/* Release an object ID for an AEM1 device */
+AEM_IDR_PUT(aem1)
+
+/* Retrieve version and module handle for an AEM1 instance */
+static int aem_find_aem1_count(struct aem_ipmi_data *data)
+{
+	struct aem_find_firmware_req	ff_req;
+	struct aem_find_firmware_resp	ff_resp;
+
+	ff_req.id = system_x_id;
+	ff_req.index = 0;
+	ff_req.module_type_id = cpu_to_be16(AEM_MODULE_TYPE_ID);
+
+	data->tx_message.cmd = AEM_FIND_FW_CMD;
+	data->tx_message.data = (char *)&ff_req;
+	data->tx_message.data_len = sizeof(ff_req);
+
+	data->rx_msg_data = &ff_resp;
+	data->rx_msg_len = sizeof(ff_resp);
+
+	aem_send_message(data);
+
+	wait_for_completion(&data->read_complete);
+
+	if (data->rx_result || data->rx_msg_len != sizeof(ff_resp) ||
+	    memcmp(&ff_resp.id, &system_x_id, sizeof(system_x_id)))
+		return -ENOENT;
+
+	return ff_resp.num_instances;
+}
+
+/* Find and initialize one AEM1 instance */
+static int aem_init_aem1_inst(struct aem_ipmi_data *probe, u8 module_handle)
+{
+	struct aem1_data *data;
+	int i;
+	int res = -ENOMEM;
+
+	data = kzalloc(sizeof(*data), GFP_KERNEL);
+	if (!data)
+		return res;
+	mutex_init(&data->fw.lock);
+
+	/* Copy instance data */
+	data->fw.ver_major = 1;
+	data->fw.ver_minor = 0;
+	data->fw.module_handle = module_handle;
+	for (i = 0; i < AEM1_NUM_ENERGY_REGS; i++)
+		data->power_period[i] = AEM_DEFAULT_POWER_INTERVAL;
+
+	/* Create sub-device for this fw instance */
+	if (aem1_idr_get(&data->fw.id))
+		goto id_err;
+
+	data->fw.pdev = platform_device_alloc(DRVNAME "1", data->fw.id);
+	data->fw.pdev->dev.driver = &aem_driver;
+	if (IS_ERR(data->fw.pdev))
+		goto dev_err;
+
+	res = platform_device_add(data->fw.pdev);
+	if (res)
+		goto ipmi_err;
+
+	dev_set_drvdata(&data->fw.pdev->dev, &data->fw);
+
+	/* Set up IPMI interface */
+	if (aem_init_ipmi_data(&data->fw.ipmi, probe->interface,
+			       probe->bmc_device))
+		goto ipmi_err;
+
+	/* Register with hwmon */
+	data->fw.hwmon_dev = hwmon_device_register(&data->fw.pdev->dev);
+
+	if (IS_ERR(data->fw.hwmon_dev)) {
+		dev_err(&data->fw.pdev->dev, "Unable to register hwmon "
+			"device for IPMI interface %d\n",
+			probe->interface);
+		goto hwmon_reg_err;
+	}
+
+	/* Find sensors */
+	if (aem1_find_sensors(data))
+		goto sensor_err;
+
+	/* Add to our list of AEM1 devices */
+	list_add_tail(&data->list, &driver_data.aem1_devices);
+	dev_info(data->fw.ipmi.bmc_device, "Found AEM v%d.%d at 0x%X\n",
+		 data->fw.ver_major, data->fw.ver_minor,
+		 data->fw.module_handle);
+	return 0;
+
+sensor_err:
+	hwmon_device_unregister(data->fw.hwmon_dev);
+hwmon_reg_err:
+	ipmi_destroy_user(data->fw.ipmi.user);
+ipmi_err:
+	dev_set_drvdata(&data->fw.pdev->dev, NULL);
+	platform_device_unregister(data->fw.pdev);
+dev_err:
+	aem1_idr_put(data->fw.id);
+id_err:
+	kfree(data);
+
+	return res;
+}
+
+/* Find and initialize all AEM1 instances */
+static void aem_init_aem1(struct aem_ipmi_data *probe)
+{
+	int num, i, err;
+
+	num = aem_find_aem1_count(probe);
+	for (i = 0; i < num; i++) {
+		err = aem_init_aem1_inst(probe, i);
+		if (err) {
+			dev_err(probe->bmc_device,
+				"Error %d initializing AEM1 0x%X\n",
+				err, i);
+			return;
+		}
+	}
+}
+
+/* Delete an AEM instance */
+#define AEM_DELETE(type) \
+static void type##_delete(struct type##_data *data) \
+{ \
+	list_del(&data->list); \
+	type##_remove_sensors(data); \
+	hwmon_device_unregister(data->fw.hwmon_dev); \
+	ipmi_destroy_user(data->fw.ipmi.user); \
+	dev_set_drvdata(&data->fw.pdev->dev, NULL); \
+	platform_device_unregister(data->fw.pdev); \
+	type##_idr_put(data->fw.id); \
+	kfree(data); \
+}
+
+/* Delete an AEM1 instance */
+AEM_DELETE(aem1)
+
+/* Probe functions for AEM2 devices */
+
+/* Retrieve version and module handle for an AEM2 instance */
+static int aem_find_aem2(struct aem_ipmi_data *data,
+			    struct aem_find_instance_resp *fi_resp,
+			    int instance_num)
+{
+	struct aem_find_instance_req fi_req;
+
+	fi_req.id = system_x_id;
+	fi_req.instance_number = instance_num;
+	fi_req.module_type_id = cpu_to_be16(AEM_MODULE_TYPE_ID);
+
+	data->tx_message.cmd = AEM_FW_INSTANCE_CMD;
+	data->tx_message.data = (char *)&fi_req;
+	data->tx_message.data_len = sizeof(fi_req);
+
+	data->rx_msg_data = fi_resp;
+	data->rx_msg_len = sizeof(*fi_resp);
+
+	aem_send_message(data);
+
+	wait_for_completion(&data->read_complete);
+
+	if (data->rx_result || data->rx_msg_len != sizeof(*fi_resp) ||
+	    memcmp(&fi_resp->id, &system_x_id, sizeof(system_x_id)))
+		return -ENOENT;
+
+	return 0;
+}
+
+/* Get an object ID for AEM2 device */
+AEM_IDR_GET(aem2)
+
+/* Release an object ID for an AEM2 device */
+AEM_IDR_PUT(aem2)
+
+/* Find and initialize one AEM2 instance */
+static int aem_init_aem2_inst(struct aem_ipmi_data *probe,
+			      struct aem_find_instance_resp *fi_resp)
+{
+	struct aem2_data *data;
+	int i;
+	int res = -ENOMEM;
+
+	data = kzalloc(sizeof(*data), GFP_KERNEL);
+	if (!data)
+		return res;
+	mutex_init(&data->fw.lock);
+
+	/* Copy instance data */
+	data->fw.ver_major = fi_resp->major;
+	data->fw.ver_minor = fi_resp->minor;
+	data->fw.module_handle = fi_resp->module_handle;
+	for (i = 0; i < AEM2_NUM_ENERGY_REGS; i++)
+		data->power_period[i] = AEM_DEFAULT_POWER_INTERVAL;
+
+	/* Create sub-device for this fw instance */
+	if (aem2_idr_get(&data->fw.id))
+		goto id_err;
+
+	data->fw.pdev = platform_device_alloc(DRVNAME "2", data->fw.id);
+	data->fw.pdev->dev.driver = &aem_driver;
+	if (IS_ERR(data->fw.pdev))
+		goto dev_err;
+
+	res = platform_device_add(data->fw.pdev);
+	if (res)
+		goto ipmi_err;
+
+	dev_set_drvdata(&data->fw.pdev->dev, &data->fw);
+
+	/* Set up IPMI interface */
+	if (aem_init_ipmi_data(&data->fw.ipmi, probe->interface,
+			       probe->bmc_device))
+		goto ipmi_err;
+
+	/* Register with hwmon */
+	data->fw.hwmon_dev = hwmon_device_register(&data->fw.pdev->dev);
+
+	if (IS_ERR(data->fw.hwmon_dev)) {
+		dev_err(&data->fw.pdev->dev, "Unable to register hwmon "
+			"device for IPMI interface %d\n",
+			probe->interface);
+		goto hwmon_reg_err;
+	}
+
+	/* Find sensors */
+	if (aem2_find_sensors(data))
+		goto sensor_err;
+
+	/* Add to our list of AEM2 devices */
+	list_add_tail(&data->list, &driver_data.aem2_devices);
+	dev_info(data->fw.ipmi.bmc_device, "Found AEM v%d.%d at 0x%X\n",
+		 data->fw.ver_major, data->fw.ver_minor,
+		 data->fw.module_handle);
+	return 0;
+
+sensor_err:
+	hwmon_device_unregister(data->fw.hwmon_dev);
+hwmon_reg_err:
+	ipmi_destroy_user(data->fw.ipmi.user);
+ipmi_err:
+	dev_set_drvdata(&data->fw.pdev->dev, NULL);
+	platform_device_unregister(data->fw.pdev);
+dev_err:
+	aem2_idr_put(data->fw.id);
+id_err:
+	kfree(data);
+
+	return res;
+}
+
+/* Find and initialize all AEM2 instances */
+static void aem_init_aem2(struct aem_ipmi_data *probe)
+{
+	struct aem_find_instance_resp fi_resp;
+	int err;
+	int i = 0;
+
+	while (!aem_find_aem2(probe, &fi_resp, i)) {
+		if (fi_resp.major != 2) {
+			dev_err(probe->bmc_device, "Unknown AEM v%d; please "
+				"report this to the maintainer.\n",
+				fi_resp.major);
+			i++;
+			continue;
+		}
+		err = aem_init_aem2_inst(probe, &fi_resp);
+		if (err) {
+			dev_err(probe->bmc_device,
+				"Error %d initializing AEM2 0x%X\n",
+				err, fi_resp.module_handle);
+			return;
+		}
+		i++;
+	}
+}
+
+/* Delete an AEM2 instance */
+AEM_DELETE(aem2)
+
+/* Sensor support functions */
+
+/* Read a sensor value */
+static int aem_read_sensor(struct aem_fw_data *fwdata, u8 elt, u8 reg,
+			   void *data, size_t size)
+{
+	int rs_size;
+	struct aem_read_sensor_req	rs_req;
+	struct aem_read_sensor_resp	*rs_resp;
+	struct aem_ipmi_data *ipmi = &fwdata->ipmi;
+
+	/* AEM registers are 1, 2, 4 or 8 bytes */
+	switch (size) {
+	case 1:
+	case 2:
+	case 4:
+	case 8:
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	rs_req.id = system_x_id;
+	rs_req.module_handle = fwdata->module_handle;
+	rs_req.element = elt;
+	rs_req.subcommand = AEM_READ_REGISTER;
+	rs_req.reg = reg;
+	rs_req.rx_buf_size = size;
+
+	ipmi->tx_message.cmd = AEM_ELEMENT_CMD;
+	ipmi->tx_message.data = (char *)&rs_req;
+	ipmi->tx_message.data_len = sizeof(rs_req);
+
+	rs_size = sizeof(*rs_resp) + size;
+	rs_resp = kzalloc(rs_size, GFP_KERNEL);
+	if (!rs_resp)
+		return -ENOMEM;
+
+	ipmi->rx_msg_data = rs_resp;
+	ipmi->rx_msg_len = rs_size;
+
+	aem_send_message(ipmi);
+
+	wait_for_completion(&ipmi->read_complete);
+
+	if (ipmi->rx_result || ipmi->rx_msg_len != rs_size ||
+	    memcmp(&rs_resp->id, &system_x_id, sizeof(system_x_id))) {
+		kfree(rs_resp);
+		return -ENOENT;
+	}
+
+	switch (size) {
+	case 1: {
+		u8 *x = data;
+		*x = rs_resp->bytes[0];
+		break;
+	}
+	case 2: {
+		u16 *x = data;
+		*x = be16_to_cpup((u16 *)rs_resp->bytes);
+		break;
+	}
+	case 4: {
+		u32 *x = data;
+		*x = be32_to_cpup((u32 *)rs_resp->bytes);
+		break;
+	}
+	case 8: {
+		u64 *x = data;
+		*x = be64_to_cpup((u64 *)rs_resp->bytes);
+		break;
+	}
+	}
+
+	return 0;
+}
+
+/* Update AEM1 energy sensors */
+static void update_aem1_energy_sensors(struct aem1_data *data)
+{
+	aem_read_sensor(&data->fw, AEM_ENERGY_ELEMENT, 0, &data->energy, 8);
+}
+
+/* Update all AEM1 sensors */
+static void update_aem1_sensors(struct aem1_data *data)
+{
+	mutex_lock(&data->fw.lock);
+	if (time_before(jiffies, data->fw.last_updated + REFRESH_INTERVAL) &&
+	    data->fw.valid)
+		goto out;
+
+	update_aem1_energy_sensors(data);
+out:
+	mutex_unlock(&data->fw.lock);
+}
+
+/* Update AEM2 energy sensors */
+static void update_aem2_energy_sensors(struct aem2_data *data)
+{
+	aem_read_sensor(&data->fw, AEM_ENERGY_ELEMENT,	0, &data->energy[0], 8);
+	aem_read_sensor(&data->fw, AEM_ENERGY_ELEMENT,	1, &data->energy[1], 8);
+}
+
+/* Update all AEM2 sensors */
+static void update_aem2_sensors(struct aem2_data *data)
+{
+	int i;
+
+	mutex_lock(&data->fw.lock);
+	if (time_before(jiffies, data->fw.last_updated + REFRESH_INTERVAL) &&
+	    data->fw.valid)
+		goto out;
+
+	update_aem2_energy_sensors(data);
+	aem_read_sensor(&data->fw, AEM_EXHAUST_ELEMENT,	0, &data->temp[0], 1);
+	aem_read_sensor(&data->fw, AEM_EXHAUST_ELEMENT,	1, &data->temp[1], 1);
+
+	for (i = POWER_CAP; i <= POWER_AUX; i++)
+		aem_read_sensor(&data->fw, AEM_POWER_CAP_ELEMENT, i,
+				&data->pcap[i], 2);
+out:
+	mutex_unlock(&data->fw.lock);
+}
+
+/* sysfs support functions */
+
+#define AEM_SHOW_POWER(type) \
+static ssize_t type##_show_power(struct device *dev, \
+				 struct device_attribute *devattr, \
+				 char *buf) \
+{ \
+	struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); \
+	struct aem_fw_data *fwdata = dev_get_drvdata(dev); \
+	struct type##_data * a = container_of(fwdata, \
+						 struct type##_data, fw); \
+	u64 before, after; \
+	signed long leftover; \
+\
+	mutex_lock(&fwdata->lock); \
+	update_##type##_energy_sensors(a); \
+	before = a->energy[attr->index]; \
+\
+	leftover = schedule_timeout_interruptible( \
+			msecs_to_jiffies(a->power_period[attr->index]) \
+		   ); \
+	if (leftover) { \
+		mutex_unlock(&fwdata->lock); \
+		return 0; \
+	} \
+\
+	update_##type##_energy_sensors(a); \
+	after = a->energy[attr->index]; \
+	mutex_unlock(&fwdata->lock); \
+\
+	return sprintf(buf, "%llu\n", (after - before) * 1000000 / \
+		       a->power_period[attr->index]); \
+}
+
+/* Display energy use */
+#define AEM_SHOW_ENERGY(type) \
+static ssize_t type##_show_energy(struct device *dev, \
+				  struct device_attribute *devattr, \
+				  char *buf) \
+{ \
+	struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); \
+	struct aem_fw_data *fwdata = dev_get_drvdata(dev); \
+	struct type##_data * a = container_of(fwdata, struct type##_data, fw); \
+	update_##type##_sensors(a); \
+\
+	return sprintf(buf, "%llu\n", a->energy[attr->index] * 1000); \
+}
+
+/* Display power interval registers */
+#define AEM_SHOW_POWER_PERIOD(type) \
+static ssize_t type##_show_power_period(struct device *dev, \
+					struct device_attribute *devattr, \
+					char *buf) \
+{ \
+	struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); \
+	struct aem_fw_data *fwdata = dev_get_drvdata(dev); \
+	struct type##_data * a = container_of(fwdata, struct type##_data, fw); \
+	update_##type##_sensors(a); \
+\
+	return sprintf(buf, "%d\n", a->power_period[attr->index]); \
+}
+
+/* Set power interval registers */
+#define AEM_SET_POWER_PERIOD(type) \
+static ssize_t type##_set_power_period(struct device *dev, \
+				       struct device_attribute *devattr, \
+				       const char *buf, size_t count) \
+{ \
+	struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); \
+	struct aem_fw_data *fwdata = dev_get_drvdata(dev); \
+	struct type##_data * a = container_of(fwdata, struct type##_data, fw); \
+	int temp = simple_strtol(buf, NULL, 10); \
+\
+	if (temp < AEM_MIN_POWER_INTERVAL) \
+		return -EINVAL; \
+\
+	mutex_lock(&a->fw.lock); \
+	a->power_period[attr->index] = temp; \
+	mutex_unlock(&a->fw.lock); \
+\
+	return count; \
+}
+
+/* Remove sensors attached to an AEM device */
+#define AEM_REMOVE_SENSORS(type, num_sensors) \
+static void type##_remove_sensors(struct type##_data *data) \
+{ \
+	int i; \
+\
+	for (i = 0; i < num_sensors; i++) { \
+		if (!data->sensors[i].dev_attr.attr.name) \
+			continue; \
+		device_remove_file(&data->fw.pdev->dev, \
+				   &data->sensors[i].dev_attr); \
+	} \
+\
+	device_remove_file(&data->fw.pdev->dev, \
+			   &sensor_dev_attr_name.dev_attr); \
+}
+
+/* Discover sensors on an AEM device */
+#define AEM_FIND_SENSORS(type) \
+static int type##_find_sensors(struct type##_data *data) \
+{ \
+	struct aem_ro_sensor_template *ro; \
+	struct aem_rw_sensor_template *rw; \
+	int err, idx; \
+\
+	/* Set up read-only sensors */ \
+	ro = type##_ro_sensors; \
+	idx = 0; \
+	while (ro->label) { \
+		data->sensors[idx].dev_attr.attr.name = ro->label; \
+		data->sensors[idx].dev_attr.attr.mode = S_IRUGO; \
+		data->sensors[idx].dev_attr.show = ro->show; \
+		data->sensors[idx].index = ro->index; \
+\
+		err = device_create_file(&data->fw.pdev->dev, \
+					 &data->sensors[idx].dev_attr); \
+		if (err) { \
+			data->sensors[idx].dev_attr.attr.name = NULL; \
+			goto exit_remove; \
+		} \
+		idx++; \
+		ro++; \
+	} \
+\
+	/* Set up read-write sensors */ \
+	rw = type##_rw_sensors; \
+	while (rw->label) { \
+		data->sensors[idx].dev_attr.attr.name = rw->label; \
+		data->sensors[idx].dev_attr.attr.mode = S_IRUGO | S_IWUSR; \
+		data->sensors[idx].dev_attr.show = rw->show; \
+		data->sensors[idx].dev_attr.store = rw->set; \
+		data->sensors[idx].index = rw->index; \
+\
+		err = device_create_file(&data->fw.pdev->dev, \
+					 &data->sensors[idx].dev_attr); \
+		if (err) { \
+			data->sensors[idx].dev_attr.attr.name = NULL; \
+			goto exit_remove; \
+		} \
+		idx++; \
+		rw++; \
+	} \
+\
+	err = device_create_file(&data->fw.pdev->dev, \
+			&sensor_dev_attr_name.dev_attr); \
+	if (err) \
+		goto exit_remove; \
+\
+	return 0; \
+\
+exit_remove: \
+	type##_remove_sensors(data); \
+	return err; \
+}
+
+/* sysfs support functions for AEM1 sensors */
+
+/* Display power use */
+AEM_SHOW_POWER(aem1)
+
+/* Display energy use */
+AEM_SHOW_ENERGY(aem1)
+
+/* Display power interval registers */
+AEM_SHOW_POWER_PERIOD(aem1)
+
+/* Set power interval registers */
+AEM_SET_POWER_PERIOD(aem1)
+
+/* sysfs support functions for AEM2 sensors */
+
+/* Display power use */
+AEM_SHOW_POWER(aem2)
+
+/* Display energy use */
+AEM_SHOW_ENERGY(aem2)
+
+/* Display temperature use */
+static ssize_t aem2_show_temp(struct device *dev,
+			      struct device_attribute *devattr,
+			      char *buf)
+{
+	struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr);
+	struct aem_fw_data *fwdata = dev_get_drvdata(dev);
+	struct aem2_data *a2 = container_of(fwdata, struct aem2_data, fw);
+	update_aem2_sensors(a2);
+
+	return sprintf(buf, "%u\n", a2->temp[attr->index] * 1000);
+}
+
+/* Display power-capping registers */
+static ssize_t aem2_show_pcap_value(struct device *dev,
+				    struct device_attribute *devattr,
+				    char *buf)
+{
+	struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr);
+	struct aem_fw_data *fwdata = dev_get_drvdata(dev);
+	struct aem2_data *a2 = container_of(fwdata, struct aem2_data, fw);
+	update_aem2_sensors(a2);
+
+	return sprintf(buf, "%u\n", a2->pcap[attr->index] * 100000);
+}
+
+/* Display power interval registers */
+AEM_SHOW_POWER_PERIOD(aem2)
+
+/* Set power interval registers */
+AEM_SET_POWER_PERIOD(aem2)
+
+/* Device name */
+static ssize_t show_name(struct device *dev, struct device_attribute *devattr,
+			 char *buf)
+{
+	struct aem_fw_data *fwdata = dev_get_drvdata(dev);
+
+	return sprintf(buf, "%s%d\n", DRVNAME, fwdata->ver_major);
+}
+static SENSOR_DEVICE_ATTR(name, S_IRUGO, show_name, NULL, 0);
+
+/* Sensor probe functions */
+
+/* Remove sensors attached to an AEM1 device */
+AEM_REMOVE_SENSORS(aem1, AEM1_NUM_SENSORS)
+
+/* Description of AEM1 sensors */
+static struct aem_ro_sensor_template aem1_ro_sensors[] = {
+{"energy1_input",  aem1_show_energy, 0},
+{"power1_average", aem1_show_power,  0},
+{NULL,		   NULL,	     0},
+};
+
+static struct aem_rw_sensor_template aem1_rw_sensors[] = {
+{"power1_interval", aem1_show_power_period, aem1_set_power_period, 0},
+{NULL,              NULL,                   NULL,                  0},
+};
+
+/* Discover sensors on an AEM1 device */
+AEM_FIND_SENSORS(aem1)
+
+/* Remove sensors attached to an AEM2 device */
+AEM_REMOVE_SENSORS(aem2, AEM2_NUM_SENSORS)
+
+/* Description of AEM2 sensors */
+static struct aem_ro_sensor_template aem2_ro_sensors[] = {
+{"energy1_input",	  aem2_show_energy,	0},
+{"energy2_input",	  aem2_show_energy,	1},
+{"power1_average",	  aem2_show_power,	0},
+{"power2_average",	  aem2_show_power,	1},
+{"temp1_input",		  aem2_show_temp,	0},
+{"temp2_input",		  aem2_show_temp,	1},
+
+{"power4_average",	  aem2_show_pcap_value, POWER_CAP_MAX_HOTPLUG},
+{"power5_average",	  aem2_show_pcap_value,	POWER_CAP_MAX},
+{"power6_average",	  aem2_show_pcap_value, POWER_CAP_MIN_WARNING},
+{"power7_average",	  aem2_show_pcap_value,	POWER_CAP_MIN},
+
+{"power3_average", 	  aem2_show_pcap_value,	POWER_AUX},
+{NULL,                    NULL,                 0},
+{"power_cap",		  aem2_show_pcap_value, POWER_CAP},
+};
+
+static struct aem_rw_sensor_template aem2_rw_sensors[] = {
+{"power1_interval", aem2_show_power_period, aem2_set_power_period, 0},
+{"power2_interval", aem2_show_power_period, aem2_set_power_period, 1},
+{NULL,              NULL,                   NULL,                  0},
+};
+
+/* Discover sensors on an AEM2 device */
+AEM_FIND_SENSORS(aem2)
+
+/* Module init/exit routines */
+
+static int __init aem_init(void)
+{
+	int res;
+
+	res = driver_register(&aem_driver);
+	if (res) {
+		printk(KERN_ERR "Can't register aem_driver\n");
+		return res;
+	}
+
+	res = ipmi_smi_watcher_register(&driver_data.bmc_events);
+	if (res)
+		goto ipmi_reg_err;
+	return 0;
+
+ipmi_reg_err:
+	driver_unregister(&aem_driver);
+	return res;
+
+}
+
+static void __exit aem_exit(void)
+{
+	struct aem1_data *p1, *next1;
+	struct aem2_data *p2, *next2;
+
+	ipmi_smi_watcher_unregister(&driver_data.bmc_events);
+	driver_unregister(&aem_driver);
+	list_for_each_entry_safe(p1, next1, &driver_data.aem1_devices, list)
+		aem1_delete(p1);
+	list_for_each_entry_safe(p2, next2, &driver_data.aem2_devices, list)
+		aem2_delete(p2);
+}
+
+MODULE_AUTHOR("Darrick J. Wong <djwong@us.ibm.com>");
+MODULE_DESCRIPTION("IBM Active Energy Manager power/temp sensor driver");
+MODULE_LICENSE("GPL");
+
+module_init(aem_init);
+module_exit(aem_exit);

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

* Re: [PATCH 2/2] ibmaem: New driver for power/energy meters in IBM System X hardware
  2008-03-28 21:38 [PATCH 2/2] ibmaem: New driver for power/energy meters in IBM System X hardware Darrick J. Wong
@ 2008-04-01  3:05 ` Anthony Liguori
  2008-04-24 22:16   ` Darrick J. Wong
  2008-04-24 22:23   ` [PATCH v2] " Darrick J. Wong
  0 siblings, 2 replies; 4+ messages in thread
From: Anthony Liguori @ 2008-04-01  3:05 UTC (permalink / raw)
  To: Darrick J. Wong; +Cc: Mark M. Hoffman, linux-kernel, lm-sensors

Darrick J. Wong wrote:
> New driver to read energy, power, and temperature meters in various
> IBM System X hardware.

<snip>

> +/* sysfs support functions */
> +/* Discover sensors on an AEM device */
> +#define AEM_FIND_SENSORS(type) \
> +static int type##_find_sensors(struct type##_data *data) \
> +{ \
> +	struct aem_ro_sensor_template *ro; \
> +	struct aem_rw_sensor_template *rw; \
> +	int err, idx; \
> +\
> +	/* Set up read-only sensors */ \
> +	ro = type##_ro_sensors; \
> +	idx = 0; \
> +	while (ro->label) { \
> +		data->sensors[idx].dev_attr.attr.name = ro->label; \
> +		data->sensors[idx].dev_attr.attr.mode = S_IRUGO; \
> +		data->sensors[idx].dev_attr.show = ro->show; \
> +		data->sensors[idx].index = ro->index; \
> +\
> +		err = device_create_file(&data->fw.pdev->dev, \
> +					 &data->sensors[idx].dev_attr); \
> +		if (err) { \
> +			data->sensors[idx].dev_attr.attr.name = NULL; \
> +			goto exit_remove; \
> +		} \
> +		idx++; \
> +		ro++; \
> +	} \
> +\
> +	/* Set up read-write sensors */ \
> +	rw = type##_rw_sensors; \
> +	while (rw->label) { \
> +		data->sensors[idx].dev_attr.attr.name = rw->label; \
> +		data->sensors[idx].dev_attr.attr.mode = S_IRUGO | S_IWUSR; \
> +		data->sensors[idx].dev_attr.show = rw->show; \
> +		data->sensors[idx].dev_attr.store = rw->set; \
> +		data->sensors[idx].index = rw->index; \
> +\
> +		err = device_create_file(&data->fw.pdev->dev, \
> +					 &data->sensors[idx].dev_attr); \
> +		if (err) { \
> +			data->sensors[idx].dev_attr.attr.name = NULL; \
> +			goto exit_remove; \
> +		} \
> +		idx++; \
> +		rw++; \
> +	} \
> +\
> +	err = device_create_file(&data->fw.pdev->dev, \
> +			&sensor_dev_attr_name.dev_attr); \
> +	if (err) \
> +		goto exit_remove; \
> +\
> +	return 0; \
> +\
> +exit_remove: \
> +	type##_remove_sensors(data); \
> +	return err; \
> +} 

It really shouldn't be necessary to have all of these macro functions. 
In this above macro, you would just have to pass the ro and rw 
structures along with a remove function pointer.  Since all of your 
types have common members, you could just have a common substructure and 
pass that around.

Regards,

Anthony Liguori

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

* Re: [PATCH 2/2] ibmaem: New driver for power/energy meters in IBM System X hardware
  2008-04-01  3:05 ` Anthony Liguori
@ 2008-04-24 22:16   ` Darrick J. Wong
  2008-04-24 22:23   ` [PATCH v2] " Darrick J. Wong
  1 sibling, 0 replies; 4+ messages in thread
From: Darrick J. Wong @ 2008-04-24 22:16 UTC (permalink / raw)
  To: Anthony Liguori; +Cc: Mark M. Hoffman, linux-kernel, lm-sensors

On Mon, Mar 31, 2008 at 10:05:25PM -0500, Anthony Liguori wrote:

> It really shouldn't be necessary to have all of these macro functions.  
> In this above macro, you would just have to pass the ro and rw  
> structures along with a remove function pointer.  Since all of your  
> types have common members, you could just have a common substructure and  
> pass that around.

Hmm... it isn't too difficult to refactor the registration function to
make it easier to look at, though I'd rather keep the AEM1 and AEM2 guts
separate rather than aggressively eliminating macros by making the AEM1
structs a subset of the AEM2 structs.

--D

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

* [PATCH v2] ibmaem: New driver for power/energy meters in IBM System X hardware
  2008-04-01  3:05 ` Anthony Liguori
  2008-04-24 22:16   ` Darrick J. Wong
@ 2008-04-24 22:23   ` Darrick J. Wong
  1 sibling, 0 replies; 4+ messages in thread
From: Darrick J. Wong @ 2008-04-24 22:23 UTC (permalink / raw)
  To: Anthony Liguori; +Cc: Mark M. Hoffman, linux-kernel, lm-sensors

Refactor the registration function to shrink the macros.  Let me know if
more aggressive de-macroing is desirable.
---
New driver to support IBM Active Energy Manager sensors.

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

 Documentation/hwmon/ibmaem |   37 +
 drivers/hwmon/Kconfig      |   14 +
 drivers/hwmon/Makefile     |    1 
 drivers/hwmon/ibmaem.c     | 1157 ++++++++++++++++++++++++++++++++++++++++++++
 4 files changed, 1209 insertions(+), 0 deletions(-)

diff --git a/Documentation/hwmon/ibmaem b/Documentation/hwmon/ibmaem
new file mode 100644
index 0000000..2fefaf5
--- /dev/null
+++ b/Documentation/hwmon/ibmaem
@@ -0,0 +1,37 @@
+Kernel driver ibmaem
+======================
+
+Supported systems:
+  * Any recent IBM System X server with Active Energy Manager support.
+    This includes the x3350, x3550, x3650, x3655, x3755, x3850 M2,
+    x3950 M2, and certain HS2x/LS2x/QS2x blades.  The IPMI host interface
+    driver ("ipmi-si") needs to be loaded for this driver to do anything.
+    Prefix: 'ibmaem'
+    Datasheet: Not available
+
+Author: Darrick J. Wong
+
+Description
+-----------
+
+This driver implements sensor reading support for the energy and power
+meters available on various IBM System X hardware through the BMC.  All
+sensor banks will be exported as platform devices; this driver can talk
+to both v1 and v2 interfaces.  This driver is completely separate from the
+older ibmpex driver.
+
+The v1 AEM interface has a simple set of features to monitor energy use.
+There is a register that displays an estimate of raw energy consumption
+since the last BMC reset, and a power sensor that returns average power
+use over a configurable interval.
+
+The v2 AEM interface is a bit more sophisticated, being able to present
+a wider range of energy and power use registers, the power cap as
+set by the AEM software, and temperature sensors.
+
+Special Features
+----------------
+
+The "power_cap" value displays the current system power cap, as set by
+the Active Energy Manager software.  Setting the power cap from the host
+is not currently supported.
diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig
index 4dc76bc..00ff533 100644
--- a/drivers/hwmon/Kconfig
+++ b/drivers/hwmon/Kconfig
@@ -330,6 +330,20 @@ config SENSORS_CORETEMP
 	  sensor inside your CPU. Supported all are all known variants
 	  of Intel Core family.
 
+config SENSORS_IBMAEM
+	tristate "IBM Active Energy Manager temperature/power sensors and control"
+	select IPMI_SI
+	depends on IPMI_HANDLER
+	help
+	  If you say yes here you get support for the temperature and
+	  power sensors and capping hardware in various IBM System X
+	  servers that support Active Energy Manager.  This includes
+	  the x3350, x3550, x3650, x3655, x3755, x3850 M2, x3950 M2,
+	  and certain HS2x/LS2x/QS2x blades.
+
+	  This driver can also be built as a module.  If so, the module
+	  will be called ibmaem.
+
 config SENSORS_IBMPEX
 	tristate "IBM PowerExecutive temperature/power sensors"
 	select IPMI_SI
diff --git a/drivers/hwmon/Makefile b/drivers/hwmon/Makefile
index 3bdb05a..d098677 100644
--- a/drivers/hwmon/Makefile
+++ b/drivers/hwmon/Makefile
@@ -41,6 +41,7 @@ obj-$(CONFIG_SENSORS_GL518SM)	+= gl518sm.o
 obj-$(CONFIG_SENSORS_GL520SM)	+= gl520sm.o
 obj-$(CONFIG_SENSORS_HDAPS)	+= hdaps.o
 obj-$(CONFIG_SENSORS_I5K_AMB)	+= i5k_amb.o
+obj-$(CONFIG_SENSORS_IBMAEM)	+= ibmaem.o
 obj-$(CONFIG_SENSORS_IBMPEX)	+= ibmpex.o
 obj-$(CONFIG_SENSORS_IT87)	+= it87.o
 obj-$(CONFIG_SENSORS_K8TEMP)	+= k8temp.o
diff --git a/drivers/hwmon/ibmaem.c b/drivers/hwmon/ibmaem.c
new file mode 100644
index 0000000..59327d8
--- /dev/null
+++ b/drivers/hwmon/ibmaem.c
@@ -0,0 +1,1157 @@
+/*
+ * A hwmon driver for the IBM Active Energy Manager temperature/power sensors
+ * and capping functionality.
+ * Copyright (C) 2008 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/ipmi.h>
+#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/spinlock.h>
+#include <linux/idr.h>
+#include <linux/sched.h>
+#include <linux/platform_device.h>
+
+#define REFRESH_INTERVAL	(HZ)
+#define DRVNAME			"aem"
+
+#define AEM_NETFN		0x2E
+
+#define AEM_FIND_FW_CMD		0x80
+#define AEM_ELEMENT_CMD		0x81
+#define AEM_FW_INSTANCE_CMD	0x82
+
+#define AEM_READ_ELEMENT_CFG	0x80
+#define AEM_READ_BUFFER		0x81
+#define AEM_READ_REGISTER	0x82
+#define AEM_WRITE_REGISTER	0x83
+#define AEM_SET_REG_MASK	0x84
+#define AEM_CLEAR_REG_MASK	0x85
+#define AEM_READ_ELEMENT_CFG2	0x86
+
+#define AEM_CONTROL_ELEMENT	0
+#define AEM_ENERGY_ELEMENT	1
+#define AEM_CLOCK_ELEMENT	4
+#define AEM_POWER_CAP_ELEMENT	7
+#define AEM_EXHAUST_ELEMENT	9
+#define AEM_POWER_ELEMENT	10
+
+#define AEM_MODULE_TYPE_ID	0x0001
+
+#define AEM2_NUM_ENERGY_REGS	2
+#define AEM2_NUM_PCAP_REGS	6
+#define AEM2_NUM_TEMP_REGS	2
+#define AEM2_NUM_SENSORS	14
+
+#define AEM1_NUM_ENERGY_REGS	1
+#define AEM1_NUM_SENSORS	3
+
+#define POWER_CAP		0
+#define POWER_CAP_MAX_HOTPLUG	1
+#define POWER_CAP_MAX		2
+#define	POWER_CAP_MIN_WARNING	3
+#define POWER_CAP_MIN		4
+#define	POWER_AUX		5
+
+#define AEM_DEFAULT_POWER_INTERVAL 1000
+#define AEM_MIN_POWER_INTERVAL	200
+
+static DEFINE_IDR(aem1_idr);
+static DEFINE_SPINLOCK(aem1_idr_lock);
+static DEFINE_IDR(aem2_idr);
+static DEFINE_SPINLOCK(aem2_idr_lock);
+
+struct aem_ipmi_data;
+struct aem1_data;
+struct aem2_data;
+
+static void aem_register_bmc(int iface, struct device *dev);
+static void aem_bmc_gone(int iface);
+static void aem_msg_handler(struct ipmi_recv_msg *msg, void *user_msg_data);
+static void aem_init_aem1(struct aem_ipmi_data *probe);
+static void aem1_delete(struct aem1_data *data);
+static void aem_init_aem2(struct aem_ipmi_data *probe);
+static void aem2_delete(struct aem2_data *data);
+static void aem1_remove_sensors(struct aem1_data *data);
+static int aem1_find_sensors(struct aem1_data *data);
+static void aem2_remove_sensors(struct aem2_data *data);
+static int aem2_find_sensors(struct aem2_data *data);
+
+static struct device_driver aem_driver = {
+	.name = DRVNAME,
+	.bus = &platform_bus_type,
+};
+
+struct aem_ipmi_data {
+	struct completion	read_complete;
+	struct ipmi_addr	address;
+	ipmi_user_t		user;
+	int			interface;
+
+	struct kernel_ipmi_msg	tx_message;
+	long			tx_msgid;
+
+	void			*rx_msg_data;
+	unsigned short		rx_msg_len;
+	unsigned char		rx_result;
+	int			rx_recv_type;
+
+	struct device		*bmc_device;
+};
+
+struct aem_fw_data {
+	struct device		*hwmon_dev;
+	struct platform_device	*pdev;
+	struct mutex		lock;
+	char			valid;
+	unsigned long		last_updated;	/* In jiffies */
+	u8			ver_major;
+	u8			ver_minor;
+	u8			module_handle;
+	int			id;
+	struct aem_ipmi_data	ipmi;
+};
+
+struct aem_ro_sensor_template {
+	char *label;
+	ssize_t (*show)(struct device *dev,
+			struct device_attribute *devattr,
+			char *buf);
+	int index;
+};
+
+struct aem_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;
+};
+
+struct aem1_data {
+	struct aem_fw_data	fw;
+	struct list_head	list;
+
+	/*
+	 * Available sensors:
+	 * Energy meter
+	 * Power meter
+	 */
+	struct sensor_device_attribute	sensors[AEM1_NUM_SENSORS];
+
+	/* energy use */
+	u64			energy[AEM1_NUM_ENERGY_REGS];
+
+	int			power_period[AEM1_NUM_ENERGY_REGS];
+};
+
+struct aem2_data {
+	struct aem_fw_data	fw;
+	struct list_head	list;
+
+	/*
+	 * Available sensors:
+	 * Two energy meters
+	 * Two power meters
+	 * Two temperature sensors
+	 * Six powercap registers
+	 */
+	struct sensor_device_attribute	sensors[AEM2_NUM_SENSORS];
+
+	/* energy use */
+	u64			energy[AEM2_NUM_ENERGY_REGS];
+
+	/* power caps */
+	u16			pcap[AEM2_NUM_PCAP_REGS];
+
+	/* exhaust temperature */
+	u8			temp[AEM2_NUM_TEMP_REGS];
+
+	/* power sampling interval */
+	int			power_period[AEM2_NUM_ENERGY_REGS];
+};
+
+/* Data structures returned by the AEM firmware */
+struct aem_iana_id {
+	u8			bytes[3];
+};
+static struct aem_iana_id system_x_id = {
+	.bytes = {0x4D, 0x4F, 0x00}
+};
+
+/* These are used to find AEM1 instances */
+struct aem_find_firmware_req {
+	struct aem_iana_id	id;
+	u8			rsvd;
+	u16			index;
+	u16			module_type_id;
+} __attribute__ ((packed));
+
+struct aem_find_firmware_resp {
+	struct aem_iana_id	id;
+	u8			num_instances;
+} __attribute__ ((packed));
+
+/* These are used to find AEM2 instances */
+struct aem_find_instance_req {
+	struct aem_iana_id	id;
+	u8			instance_number;
+	u16			module_type_id;
+} __attribute__ ((packed));
+
+struct aem_find_instance_resp {
+	struct aem_iana_id	id;
+	u8			num_instances;
+	u8			major;
+	u8			minor;
+	u8			module_handle;
+	u16			record_id;
+} __attribute__ ((packed));
+
+/* These are used to query sensors */
+struct aem_read_sensor_req {
+	struct aem_iana_id	id;
+	u8			module_handle;
+	u8			element;
+	u8			subcommand;
+	u8			reg;
+	u8			rx_buf_size;
+} __attribute__ ((packed));
+
+struct aem_read_sensor_resp {
+	struct aem_iana_id	id;
+	u8			bytes[0];
+} __attribute__ ((packed));
+
+/* Data structures to talk to the IPMI layer */
+struct aem_driver_data {
+	struct list_head	aem1_devices;
+	struct list_head	aem2_devices;
+	struct ipmi_smi_watcher	bmc_events;
+	struct ipmi_user_hndl	ipmi_hndlrs;
+};
+
+static struct aem_driver_data driver_data = {
+	.aem1_devices = LIST_HEAD_INIT(driver_data.aem1_devices),
+	.aem2_devices = LIST_HEAD_INIT(driver_data.aem2_devices),
+	.bmc_events = {
+		.owner = THIS_MODULE,
+		.new_smi = aem_register_bmc,
+		.smi_gone = aem_bmc_gone,
+	},
+	.ipmi_hndlrs = {
+		.ipmi_recv_hndl = aem_msg_handler,
+	},
+};
+
+/* Functions to talk to the IPMI layer */
+
+/* Initialize IPMI address, message buffers and user data */
+static int aem_init_ipmi_data(struct aem_ipmi_data *data,
+				 int iface, struct device *bmc)
+{
+	int err;
+
+	init_completion(&data->read_complete);
+	data->bmc_device = bmc;
+
+	/* Initialize IPMI address */
+	data->address.addr_type = IPMI_SYSTEM_INTERFACE_ADDR_TYPE;
+	data->address.channel = IPMI_BMC_CHANNEL;
+	data->address.data[0] = 0;
+	data->interface = iface;
+
+	/* Initialize message buffers */
+	data->tx_msgid = 0;
+	data->tx_message.netfn = AEM_NETFN;
+
+	/* Create IPMI messaging interface user */
+	err = ipmi_create_user(data->interface, &driver_data.ipmi_hndlrs,
+			       data, &data->user);
+	if (err < 0) {
+		dev_err(bmc, "Unable to register user with IPMI "
+			"interface %d\n", data->interface);
+		return -EACCES;
+	}
+
+	return 0;
+}
+
+/* Send an IPMI command */
+static int aem_send_message(struct aem_ipmi_data *data)
+{
+	int err;
+
+	err = ipmi_validate_addr(&data->address, sizeof(data->address));
+	if (err)
+		goto out;
+
+	data->tx_msgid++;
+	err = ipmi_request_settime(data->user, &data->address, data->tx_msgid,
+				   &data->tx_message, data, 0, 0, 0);
+	if (err)
+		goto out1;
+
+	return 0;
+out1:
+	dev_err(data->bmc_device, "request_settime=%x\n", err);
+	return err;
+out:
+	dev_err(data->bmc_device, "validate_addr=%x\n", err);
+	return err;
+}
+
+/* Probe a BMC for AEM firmware instances */
+static void aem_register_bmc(int iface, struct device *dev)
+{
+	struct aem_ipmi_data probe;
+
+	if (aem_init_ipmi_data(&probe, iface, dev))
+		return;
+
+	aem_init_aem1(&probe);
+	aem_init_aem2(&probe);
+
+	ipmi_destroy_user(probe.user);
+}
+
+/* Handle BMC deletion */
+static void aem_bmc_gone(int iface)
+{
+	struct aem1_data *p1, *next1;
+	struct aem2_data *p2, *next2;
+
+	list_for_each_entry_safe(p1, next1, &driver_data.aem1_devices, list) {
+		if (p1->fw.ipmi.interface == iface)
+			aem1_delete(p1);
+	}
+
+	list_for_each_entry_safe(p2, next2, &driver_data.aem2_devices, list) {
+		if (p2->fw.ipmi.interface == iface)
+			aem2_delete(p2);
+	}
+}
+
+/* Dispatch IPMI messages to callers */
+static void aem_msg_handler(struct ipmi_recv_msg *msg, void *user_msg_data)
+{
+	unsigned short rx_len;
+	struct aem_ipmi_data *data = (struct aem_ipmi_data *)user_msg_data;
+
+	if (msg->msgid != data->tx_msgid) {
+		dev_err(data->bmc_device, "Mismatch between received msgid "
+			"(%02x) and transmitted msgid (%02x)!\n",
+			(int)msg->msgid,
+			(int)data->tx_msgid);
+		ipmi_free_recv_msg(msg);
+		return;
+	}
+
+	data->rx_recv_type = msg->recv_type;
+	if (msg->msg.data_len > 0)
+		data->rx_result = msg->msg.data[0];
+	else
+		data->rx_result = IPMI_UNKNOWN_ERR_COMPLETION_CODE;
+
+	if (msg->msg.data_len > 1) {
+		rx_len = msg->msg.data_len - 1;
+		if (data->rx_msg_len < rx_len)
+			rx_len = data->rx_msg_len;
+		data->rx_msg_len = rx_len;
+		memcpy(data->rx_msg_data, msg->msg.data + 1, data->rx_msg_len);
+	} else
+		data->rx_msg_len = 0;
+
+	ipmi_free_recv_msg(msg);
+	complete(&data->read_complete);
+}
+
+/* ID functions */
+
+/* Obtain an id */
+#define AEM_IDR_GET(type) \
+static int type##_idr_get(int *id) \
+{ \
+	int i, err; \
+\
+again: \
+	if (unlikely(idr_pre_get(&type##_idr, GFP_KERNEL) == 0)) \
+		return -ENOMEM; \
+\
+	spin_lock(&type##_idr_lock); \
+	err = idr_get_new(&type##_idr, NULL, &i); \
+	spin_unlock(&type##_idr_lock); \
+\
+	if (unlikely(err == -EAGAIN)) \
+		goto again; \
+	else if (unlikely(err)) \
+		return err; \
+\
+	*id = i & MAX_ID_MASK; \
+	return 0; \
+}
+
+/* Release an object ID */
+#define AEM_IDR_PUT(type) \
+static void type##_idr_put(int id) \
+{ \
+	spin_lock(&type##_idr_lock); \
+	idr_remove(&type##_idr, id); \
+	spin_unlock(&type##_idr_lock); \
+}
+
+/* AEM Device Name */
+static ssize_t show_name(struct device *dev, struct device_attribute *devattr,
+			 char *buf)
+{
+	struct aem_fw_data *fwdata = dev_get_drvdata(dev);
+
+	return sprintf(buf, "%s%d\n", DRVNAME, fwdata->ver_major);
+}
+static SENSOR_DEVICE_ATTR(name, S_IRUGO, show_name, NULL, 0);
+
+/* Probe functions for AEM1 devices */
+
+/* Get an object ID for AEM1 device */
+AEM_IDR_GET(aem1)
+
+/* Release an object ID for an AEM1 device */
+AEM_IDR_PUT(aem1)
+
+/* Retrieve version and module handle for an AEM1 instance */
+static int aem_find_aem1_count(struct aem_ipmi_data *data)
+{
+	struct aem_find_firmware_req	ff_req;
+	struct aem_find_firmware_resp	ff_resp;
+
+	ff_req.id = system_x_id;
+	ff_req.index = 0;
+	ff_req.module_type_id = cpu_to_be16(AEM_MODULE_TYPE_ID);
+
+	data->tx_message.cmd = AEM_FIND_FW_CMD;
+	data->tx_message.data = (char *)&ff_req;
+	data->tx_message.data_len = sizeof(ff_req);
+
+	data->rx_msg_data = &ff_resp;
+	data->rx_msg_len = sizeof(ff_resp);
+
+	aem_send_message(data);
+
+	wait_for_completion(&data->read_complete);
+
+	if (data->rx_result || data->rx_msg_len != sizeof(ff_resp) ||
+	    memcmp(&ff_resp.id, &system_x_id, sizeof(system_x_id)))
+		return -ENOENT;
+
+	return ff_resp.num_instances;
+}
+
+/* Find and initialize one AEM1 instance */
+static int aem_init_aem1_inst(struct aem_ipmi_data *probe, u8 module_handle)
+{
+	struct aem1_data *data;
+	int i;
+	int res = -ENOMEM;
+
+	data = kzalloc(sizeof(*data), GFP_KERNEL);
+	if (!data)
+		return res;
+	mutex_init(&data->fw.lock);
+
+	/* Copy instance data */
+	data->fw.ver_major = 1;
+	data->fw.ver_minor = 0;
+	data->fw.module_handle = module_handle;
+	for (i = 0; i < AEM1_NUM_ENERGY_REGS; i++)
+		data->power_period[i] = AEM_DEFAULT_POWER_INTERVAL;
+
+	/* Create sub-device for this fw instance */
+	if (aem1_idr_get(&data->fw.id))
+		goto id_err;
+
+	data->fw.pdev = platform_device_alloc(DRVNAME "1", data->fw.id);
+	data->fw.pdev->dev.driver = &aem_driver;
+	if (IS_ERR(data->fw.pdev))
+		goto dev_err;
+
+	res = platform_device_add(data->fw.pdev);
+	if (res)
+		goto ipmi_err;
+
+	dev_set_drvdata(&data->fw.pdev->dev, &data->fw);
+
+	/* Set up IPMI interface */
+	if (aem_init_ipmi_data(&data->fw.ipmi, probe->interface,
+			       probe->bmc_device))
+		goto ipmi_err;
+
+	/* Register with hwmon */
+	data->fw.hwmon_dev = hwmon_device_register(&data->fw.pdev->dev);
+
+	if (IS_ERR(data->fw.hwmon_dev)) {
+		dev_err(&data->fw.pdev->dev, "Unable to register hwmon "
+			"device for IPMI interface %d\n",
+			probe->interface);
+		goto hwmon_reg_err;
+	}
+
+	/* Find sensors */
+	if (aem1_find_sensors(data))
+		goto sensor_err;
+
+	/* Add to our list of AEM1 devices */
+	list_add_tail(&data->list, &driver_data.aem1_devices);
+	dev_info(data->fw.ipmi.bmc_device, "Found AEM v%d.%d at 0x%X\n",
+		 data->fw.ver_major, data->fw.ver_minor,
+		 data->fw.module_handle);
+	return 0;
+
+sensor_err:
+	hwmon_device_unregister(data->fw.hwmon_dev);
+hwmon_reg_err:
+	ipmi_destroy_user(data->fw.ipmi.user);
+ipmi_err:
+	dev_set_drvdata(&data->fw.pdev->dev, NULL);
+	platform_device_unregister(data->fw.pdev);
+dev_err:
+	aem1_idr_put(data->fw.id);
+id_err:
+	kfree(data);
+
+	return res;
+}
+
+/* Find and initialize all AEM1 instances */
+static void aem_init_aem1(struct aem_ipmi_data *probe)
+{
+	int num, i, err;
+
+	num = aem_find_aem1_count(probe);
+	for (i = 0; i < num; i++) {
+		err = aem_init_aem1_inst(probe, i);
+		if (err) {
+			dev_err(probe->bmc_device,
+				"Error %d initializing AEM1 0x%X\n",
+				err, i);
+			return;
+		}
+	}
+}
+
+/* Delete an AEM instance */
+#define AEM_DELETE(type) \
+static void type##_delete(struct type##_data *data) \
+{ \
+	list_del(&data->list); \
+	type##_remove_sensors(data); \
+	hwmon_device_unregister(data->fw.hwmon_dev); \
+	ipmi_destroy_user(data->fw.ipmi.user); \
+	dev_set_drvdata(&data->fw.pdev->dev, NULL); \
+	platform_device_unregister(data->fw.pdev); \
+	type##_idr_put(data->fw.id); \
+	kfree(data); \
+}
+
+/* Delete an AEM1 instance */
+AEM_DELETE(aem1)
+
+/* Probe functions for AEM2 devices */
+
+/* Retrieve version and module handle for an AEM2 instance */
+static int aem_find_aem2(struct aem_ipmi_data *data,
+			    struct aem_find_instance_resp *fi_resp,
+			    int instance_num)
+{
+	struct aem_find_instance_req fi_req;
+
+	fi_req.id = system_x_id;
+	fi_req.instance_number = instance_num;
+	fi_req.module_type_id = cpu_to_be16(AEM_MODULE_TYPE_ID);
+
+	data->tx_message.cmd = AEM_FW_INSTANCE_CMD;
+	data->tx_message.data = (char *)&fi_req;
+	data->tx_message.data_len = sizeof(fi_req);
+
+	data->rx_msg_data = fi_resp;
+	data->rx_msg_len = sizeof(*fi_resp);
+
+	aem_send_message(data);
+
+	wait_for_completion(&data->read_complete);
+
+	if (data->rx_result || data->rx_msg_len != sizeof(*fi_resp) ||
+	    memcmp(&fi_resp->id, &system_x_id, sizeof(system_x_id)))
+		return -ENOENT;
+
+	return 0;
+}
+
+/* Get an object ID for AEM2 device */
+AEM_IDR_GET(aem2)
+
+/* Release an object ID for an AEM2 device */
+AEM_IDR_PUT(aem2)
+
+/* Find and initialize one AEM2 instance */
+static int aem_init_aem2_inst(struct aem_ipmi_data *probe,
+			      struct aem_find_instance_resp *fi_resp)
+{
+	struct aem2_data *data;
+	int i;
+	int res = -ENOMEM;
+
+	data = kzalloc(sizeof(*data), GFP_KERNEL);
+	if (!data)
+		return res;
+	mutex_init(&data->fw.lock);
+
+	/* Copy instance data */
+	data->fw.ver_major = fi_resp->major;
+	data->fw.ver_minor = fi_resp->minor;
+	data->fw.module_handle = fi_resp->module_handle;
+	for (i = 0; i < AEM2_NUM_ENERGY_REGS; i++)
+		data->power_period[i] = AEM_DEFAULT_POWER_INTERVAL;
+
+	/* Create sub-device for this fw instance */
+	if (aem2_idr_get(&data->fw.id))
+		goto id_err;
+
+	data->fw.pdev = platform_device_alloc(DRVNAME "2", data->fw.id);
+	data->fw.pdev->dev.driver = &aem_driver;
+	if (IS_ERR(data->fw.pdev))
+		goto dev_err;
+
+	res = platform_device_add(data->fw.pdev);
+	if (res)
+		goto ipmi_err;
+
+	dev_set_drvdata(&data->fw.pdev->dev, &data->fw);
+
+	/* Set up IPMI interface */
+	if (aem_init_ipmi_data(&data->fw.ipmi, probe->interface,
+			       probe->bmc_device))
+		goto ipmi_err;
+
+	/* Register with hwmon */
+	data->fw.hwmon_dev = hwmon_device_register(&data->fw.pdev->dev);
+
+	if (IS_ERR(data->fw.hwmon_dev)) {
+		dev_err(&data->fw.pdev->dev, "Unable to register hwmon "
+			"device for IPMI interface %d\n",
+			probe->interface);
+		goto hwmon_reg_err;
+	}
+
+	/* Find sensors */
+	if (aem2_find_sensors(data))
+		goto sensor_err;
+
+	/* Add to our list of AEM2 devices */
+	list_add_tail(&data->list, &driver_data.aem2_devices);
+	dev_info(data->fw.ipmi.bmc_device, "Found AEM v%d.%d at 0x%X\n",
+		 data->fw.ver_major, data->fw.ver_minor,
+		 data->fw.module_handle);
+	return 0;
+
+sensor_err:
+	hwmon_device_unregister(data->fw.hwmon_dev);
+hwmon_reg_err:
+	ipmi_destroy_user(data->fw.ipmi.user);
+ipmi_err:
+	dev_set_drvdata(&data->fw.pdev->dev, NULL);
+	platform_device_unregister(data->fw.pdev);
+dev_err:
+	aem2_idr_put(data->fw.id);
+id_err:
+	kfree(data);
+
+	return res;
+}
+
+/* Find and initialize all AEM2 instances */
+static void aem_init_aem2(struct aem_ipmi_data *probe)
+{
+	struct aem_find_instance_resp fi_resp;
+	int err;
+	int i = 0;
+
+	while (!aem_find_aem2(probe, &fi_resp, i)) {
+		if (fi_resp.major != 2) {
+			dev_err(probe->bmc_device, "Unknown AEM v%d; please "
+				"report this to the maintainer.\n",
+				fi_resp.major);
+			i++;
+			continue;
+		}
+		err = aem_init_aem2_inst(probe, &fi_resp);
+		if (err) {
+			dev_err(probe->bmc_device,
+				"Error %d initializing AEM2 0x%X\n",
+				err, fi_resp.module_handle);
+			return;
+		}
+		i++;
+	}
+}
+
+/* Delete an AEM2 instance */
+AEM_DELETE(aem2)
+
+/* Sensor support functions */
+
+/* Read a sensor value */
+static int aem_read_sensor(struct aem_fw_data *fwdata, u8 elt, u8 reg,
+			   void *data, size_t size)
+{
+	int rs_size;
+	struct aem_read_sensor_req	rs_req;
+	struct aem_read_sensor_resp	*rs_resp;
+	struct aem_ipmi_data *ipmi = &fwdata->ipmi;
+
+	/* AEM registers are 1, 2, 4 or 8 bytes */
+	switch (size) {
+	case 1:
+	case 2:
+	case 4:
+	case 8:
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	rs_req.id = system_x_id;
+	rs_req.module_handle = fwdata->module_handle;
+	rs_req.element = elt;
+	rs_req.subcommand = AEM_READ_REGISTER;
+	rs_req.reg = reg;
+	rs_req.rx_buf_size = size;
+
+	ipmi->tx_message.cmd = AEM_ELEMENT_CMD;
+	ipmi->tx_message.data = (char *)&rs_req;
+	ipmi->tx_message.data_len = sizeof(rs_req);
+
+	rs_size = sizeof(*rs_resp) + size;
+	rs_resp = kzalloc(rs_size, GFP_KERNEL);
+	if (!rs_resp)
+		return -ENOMEM;
+
+	ipmi->rx_msg_data = rs_resp;
+	ipmi->rx_msg_len = rs_size;
+
+	aem_send_message(ipmi);
+
+	wait_for_completion(&ipmi->read_complete);
+
+	if (ipmi->rx_result || ipmi->rx_msg_len != rs_size ||
+	    memcmp(&rs_resp->id, &system_x_id, sizeof(system_x_id))) {
+		kfree(rs_resp);
+		return -ENOENT;
+	}
+
+	switch (size) {
+	case 1: {
+		u8 *x = data;
+		*x = rs_resp->bytes[0];
+		break;
+	}
+	case 2: {
+		u16 *x = data;
+		*x = be16_to_cpup((u16 *)rs_resp->bytes);
+		break;
+	}
+	case 4: {
+		u32 *x = data;
+		*x = be32_to_cpup((u32 *)rs_resp->bytes);
+		break;
+	}
+	case 8: {
+		u64 *x = data;
+		*x = be64_to_cpup((u64 *)rs_resp->bytes);
+		break;
+	}
+	}
+
+	return 0;
+}
+
+/* Update AEM1 energy sensors */
+static void update_aem1_energy_sensors(struct aem1_data *data)
+{
+	aem_read_sensor(&data->fw, AEM_ENERGY_ELEMENT, 0, &data->energy, 8);
+}
+
+/* Update all AEM1 sensors */
+static void update_aem1_sensors(struct aem1_data *data)
+{
+	mutex_lock(&data->fw.lock);
+	if (time_before(jiffies, data->fw.last_updated + REFRESH_INTERVAL) &&
+	    data->fw.valid)
+		goto out;
+
+	update_aem1_energy_sensors(data);
+out:
+	mutex_unlock(&data->fw.lock);
+}
+
+/* Update AEM2 energy sensors */
+static void update_aem2_energy_sensors(struct aem2_data *data)
+{
+	aem_read_sensor(&data->fw, AEM_ENERGY_ELEMENT,	0, &data->energy[0], 8);
+	aem_read_sensor(&data->fw, AEM_ENERGY_ELEMENT,	1, &data->energy[1], 8);
+}
+
+/* Update all AEM2 sensors */
+static void update_aem2_sensors(struct aem2_data *data)
+{
+	int i;
+
+	mutex_lock(&data->fw.lock);
+	if (time_before(jiffies, data->fw.last_updated + REFRESH_INTERVAL) &&
+	    data->fw.valid)
+		goto out;
+
+	update_aem2_energy_sensors(data);
+	aem_read_sensor(&data->fw, AEM_EXHAUST_ELEMENT,	0, &data->temp[0], 1);
+	aem_read_sensor(&data->fw, AEM_EXHAUST_ELEMENT,	1, &data->temp[1], 1);
+
+	for (i = POWER_CAP; i <= POWER_AUX; i++)
+		aem_read_sensor(&data->fw, AEM_POWER_CAP_ELEMENT, i,
+				&data->pcap[i], 2);
+out:
+	mutex_unlock(&data->fw.lock);
+}
+
+/* sysfs support functions */
+
+#define AEM_SHOW_POWER(type) \
+static ssize_t type##_show_power(struct device *dev, \
+				 struct device_attribute *devattr, \
+				 char *buf) \
+{ \
+	struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); \
+	struct aem_fw_data *fwdata = dev_get_drvdata(dev); \
+	struct type##_data * a = container_of(fwdata, \
+						 struct type##_data, fw); \
+	u64 before, after; \
+	signed long leftover; \
+\
+	mutex_lock(&fwdata->lock); \
+	update_##type##_energy_sensors(a); \
+	before = a->energy[attr->index]; \
+\
+	leftover = schedule_timeout_interruptible( \
+			msecs_to_jiffies(a->power_period[attr->index]) \
+		   ); \
+	if (leftover) { \
+		mutex_unlock(&fwdata->lock); \
+		return 0; \
+	} \
+\
+	update_##type##_energy_sensors(a); \
+	after = a->energy[attr->index]; \
+	mutex_unlock(&fwdata->lock); \
+\
+	return sprintf(buf, "%llu\n", (after - before) * 1000000 / \
+		       a->power_period[attr->index]); \
+}
+
+/* Display energy use */
+#define AEM_SHOW_ENERGY(type) \
+static ssize_t type##_show_energy(struct device *dev, \
+				  struct device_attribute *devattr, \
+				  char *buf) \
+{ \
+	struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); \
+	struct aem_fw_data *fwdata = dev_get_drvdata(dev); \
+	struct type##_data * a = container_of(fwdata, struct type##_data, fw); \
+	update_##type##_sensors(a); \
+\
+	return sprintf(buf, "%llu\n", a->energy[attr->index] * 1000); \
+}
+
+/* Display power interval registers */
+#define AEM_SHOW_POWER_PERIOD(type) \
+static ssize_t type##_show_power_period(struct device *dev, \
+					struct device_attribute *devattr, \
+					char *buf) \
+{ \
+	struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); \
+	struct aem_fw_data *fwdata = dev_get_drvdata(dev); \
+	struct type##_data * a = container_of(fwdata, struct type##_data, fw); \
+	update_##type##_sensors(a); \
+\

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

end of thread, other threads:[~2008-04-24 22:23 UTC | newest]

Thread overview: 4+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2008-03-28 21:38 [PATCH 2/2] ibmaem: New driver for power/energy meters in IBM System X hardware Darrick J. Wong
2008-04-01  3:05 ` Anthony Liguori
2008-04-24 22:16   ` Darrick J. Wong
2008-04-24 22:23   ` [PATCH v2] " Darrick J. Wong

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).