Hi everyone,
Attached is a driver to export sensor readings from power meters that
are found in several IBM x86 systems. At the moment, the hwmon sysfs
documentation doesn't mention any naming conventions for sensors that
measure Watts, so I am proposing that they be called "powerX_input" in a
fashion similar to temperature/rpm/current sensors. If that is
agreeable to everyone, I'll post a follow-up patch to amend the
documentation.
The patch should apply against 2.6.23-rc3 and has been tested on the
x3550, x3650, x3655, x3755 and HS20 blades that support it. As far
as I know, those are the only systems in existence that have this
interface.
---
ibm_pex: Driver to export IBM PowerExecutive power meter sensors.
Signed-off-by: Darrick J. Wong <[email protected]>
---
drivers/hwmon/Kconfig | 12 +
drivers/hwmon/Makefile | 1
drivers/hwmon/ibmpex.c | 615 ++++++++++++++++++++++++++++++++++++++++++++++++
3 files changed, 628 insertions(+), 0 deletions(-)
diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig
index 555f470..41ffa2e 100644
--- a/drivers/hwmon/Kconfig
+++ b/drivers/hwmon/Kconfig
@@ -275,6 +275,18 @@ config SENSORS_CORETEMP
sensor inside your CPU. Supported all are all known variants
of Intel Core family.
+config SENSORS_IBMPEX
+ tristate "IBM PowerExecutive temperature/power sensors"
+ depends on IPMI_SI
+ help
+ If you say yes here you get support for the temperature and
+ power sensors in various IBM System X servers that support
+ PowerExecutive. So far this includes the x3550, x3650, x3655,
+ x3755, and certain HS20 blades.
+
+ This driver can also be built as a module. If so, the module
+ will be called ibmpex.
+
config SENSORS_IT87
tristate "ITE IT87xx and compatibles"
select HWMON_VID
diff --git a/drivers/hwmon/Makefile b/drivers/hwmon/Makefile
index a133981..31da6fe 100644
--- a/drivers/hwmon/Makefile
+++ b/drivers/hwmon/Makefile
@@ -35,6 +35,7 @@ obj-$(CONFIG_SENSORS_FSCPOS) += fscpos.o
obj-$(CONFIG_SENSORS_GL518SM) += gl518sm.o
obj-$(CONFIG_SENSORS_GL520SM) += gl520sm.o
obj-$(CONFIG_SENSORS_HDAPS) += hdaps.o
+obj-$(CONFIG_SENSORS_IBMPEX) += ibmpex.o
obj-$(CONFIG_SENSORS_IT87) += it87.o
obj-$(CONFIG_SENSORS_K8TEMP) += k8temp.o
obj-$(CONFIG_SENSORS_LM63) += lm63.o
diff --git a/drivers/hwmon/ibmpex.c b/drivers/hwmon/ibmpex.c
new file mode 100644
index 0000000..15ae9ec
--- /dev/null
+++ b/drivers/hwmon/ibmpex.c
@@ -0,0 +1,615 @@
+/*
+ * A hwmon driver for the IBM PowerExecutive temperature/power sensors
+ * Copyright (C) 2007 IBM
+ *
+ * Author: Darrick J. Wong <[email protected]>
+ *
+ * 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/mutex.h>
+
+#define REFRESH_INTERVAL (5 * HZ)
+#define DRVNAME "ibmpex"
+
+static void ibmpex_msg_handler(struct ipmi_recv_msg *msg, void *user_msg_data);
+static void ibmpex_register_bmc(int iface, struct device *dev);
+static void ibmpex_bmc_gone(int iface);
+
+struct ibmpex_sensor_data {
+ int in_use;
+ s16 value;
+ s16 max;
+ s16 min;
+ int divisor;
+
+ struct sensor_device_attribute attr_value;
+ struct sensor_device_attribute attr_max;
+ struct sensor_device_attribute attr_min;
+};
+
+struct ibmpex_bmc_data {
+ struct list_head list;
+ struct class_device *class_dev;
+ struct device *bmc_device;
+ struct mutex lock;
+ char valid;
+ unsigned long last_updated; /* In jiffies */
+
+ struct ipmi_addr address;
+ struct completion read_complete;
+ ipmi_user_t user;
+ int interface;
+
+ struct kernel_ipmi_msg tx_message;
+ unsigned char tx_msg_data[IPMI_MAX_MSG_LENGTH];
+ long tx_msgid;
+
+ unsigned char rx_msg_data[IPMI_MAX_MSG_LENGTH];
+ unsigned long rx_msg_len;
+ unsigned char rx_result;
+ int rx_recv_type;
+
+ unsigned char sensor_major;
+ unsigned char sensor_minor;
+
+ unsigned char num_sensors;
+ struct ibmpex_sensor_data *sensors;
+};
+
+struct ibmpex_driver_data {
+ struct list_head bmc_data;
+ struct ipmi_smi_watcher bmc_events;
+ struct ipmi_user_hndl ipmi_hndlrs;
+};
+
+static struct ibmpex_driver_data driver_data = {
+ .bmc_data = LIST_HEAD_INIT(driver_data.bmc_data),
+ .bmc_events = {
+ .owner = THIS_MODULE,
+ .new_smi = ibmpex_register_bmc,
+ .smi_gone = ibmpex_bmc_gone,
+ },
+ .ipmi_hndlrs = {
+ .ipmi_recv_hndl = ibmpex_msg_handler,
+ },
+};
+
+static int ibmpex_send_message(struct ibmpex_bmc_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:
+ printk(KERN_ERR "%s: request_settime=%x\n", __FUNCTION__, err);
+ return err;
+out:
+ printk(KERN_ERR "%s: validate_addr=%x\n", __FUNCTION__, err);
+ return err;
+}
+
+static int ibmpex_ver_check(struct ibmpex_bmc_data *data)
+{
+ data->tx_msg_data[0] = 0x1;
+ data->tx_message.data_len = 1;
+ ibmpex_send_message(data);
+
+ wait_for_completion(&data->read_complete);
+
+ if (data->rx_result || data->rx_msg_len != 6)
+ return -ENOENT;
+
+ data->sensor_major = data->rx_msg_data[0];
+ data->sensor_minor = data->rx_msg_data[1];
+
+ printk(KERN_INFO DRVNAME ": Found BMC with sensor interface "
+ "v%d.%d %d-%02d-%02d on interface %d\n",
+ data->sensor_major,
+ data->sensor_minor,
+ data->rx_msg_data[3] | ((u16)data->rx_msg_data[2] << 8),
+ data->rx_msg_data[4],
+ data->rx_msg_data[5],
+ data->interface);
+
+ return 0;
+}
+
+static int ibmpex_query_sensor_count(struct ibmpex_bmc_data *data)
+{
+ data->tx_msg_data[0] = 0x2;
+ data->tx_message.data_len = 1;
+ ibmpex_send_message(data);
+
+ wait_for_completion(&data->read_complete);
+
+ if (data->rx_result || data->rx_msg_len != 1)
+ return -ENOENT;
+
+ return data->rx_msg_data[0];
+}
+
+static int ibmpex_query_sensor_name(struct ibmpex_bmc_data *data, int sensor)
+{
+ data->tx_msg_data[0] = 0x3;
+ data->tx_msg_data[1] = sensor;
+ data->tx_message.data_len = 2;
+ ibmpex_send_message(data);
+
+ wait_for_completion(&data->read_complete);
+
+ if (data->rx_result || data->rx_msg_len < 1)
+ return -ENOENT;
+
+ return 0;
+}
+
+static int ibmpex_query_sensor_data(struct ibmpex_bmc_data *data, int sensor)
+{
+ data->tx_msg_data[0] = 0x6;
+ data->tx_msg_data[1] = sensor;
+ data->tx_message.data_len = 2;
+ ibmpex_send_message(data);
+
+ wait_for_completion(&data->read_complete);
+
+ if (data->rx_result || data->rx_msg_len < 26) {
+ printk(KERN_ERR "Error reading sensor %d, please check.\n",
+ sensor);
+ return -ENOENT;
+ }
+
+ return 0;
+}
+
+static void ibmpex_update_device(struct ibmpex_bmc_data *data)
+{
+ int i, err;
+
+ mutex_lock(&data->lock);
+ if (time_before(jiffies, data->last_updated + REFRESH_INTERVAL) &&
+ data->valid)
+ goto out;
+
+ for (i = 0; i < data->num_sensors; i++) {
+ if (!data->sensors[i].in_use)
+ continue;
+ err = ibmpex_query_sensor_data(data, i);
+ if (err)
+ continue;
+ data->sensors[i].value = ((u16)data->rx_msg_data[16] << 8) |
+ data->rx_msg_data[17];
+ data->sensors[i].min = ((u16)data->rx_msg_data[18] << 8) |
+ data->rx_msg_data[19];
+ data->sensors[i].max = ((u16)data->rx_msg_data[20] << 8) |
+ data->rx_msg_data[21];
+ }
+
+ data->last_updated = jiffies;
+ data->valid = 1;
+
+out:
+ mutex_unlock(&data->lock);
+}
+
+static struct ibmpex_bmc_data *get_bmc_data(int iface)
+{
+ struct ibmpex_bmc_data *p, *next;
+
+ list_for_each_entry_safe(p, next, &driver_data.bmc_data, list)
+ if (p->interface == iface)
+ return p;
+
+ return NULL;
+}
+
+static ssize_t show_name(struct device *dev, struct device_attribute *devattr,
+ char *buf)
+{
+ return sprintf(buf, "%s\n", DRVNAME);
+}
+static SENSOR_DEVICE_ATTR(name, S_IRUGO, show_name, NULL, 0);
+
+static ssize_t ibmpex_show_sensor(struct device *dev,
+ struct device_attribute *devattr,
+ char *buf)
+{
+ struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr);
+ int iface = attr->index >> 8;
+ int sensor = attr->index & 0xFF;
+ struct ibmpex_bmc_data *data = get_bmc_data(iface);
+ int divisor = data->sensors[sensor].divisor;
+ ibmpex_update_device(data);
+
+ if (divisor == 1)
+ return sprintf(buf, "%d\n", data->sensors[sensor].value);
+
+ return sprintf(buf, "%d.%d\n",
+ data->sensors[sensor].value / divisor,
+ data->sensors[sensor].value % divisor);
+}
+
+static ssize_t ibmpex_show_max(struct device *dev,
+ struct device_attribute *devattr,
+ char *buf)
+{
+ struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr);
+ int iface = attr->index >> 8;
+ int sensor = attr->index & 0xFF;
+ struct ibmpex_bmc_data *data = get_bmc_data(iface);
+ int divisor = data->sensors[sensor].divisor;
+ ibmpex_update_device(data);
+
+ if (divisor == 1)
+ return sprintf(buf, "%d\n", data->sensors[sensor].max);
+
+ return sprintf(buf, "%d.%d\n",
+ data->sensors[sensor].max / divisor,
+ data->sensors[sensor].max % divisor);
+}
+
+static ssize_t ibmpex_show_min(struct device *dev,
+ struct device_attribute *devattr,
+ char *buf)
+{
+ struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr);
+ int iface = attr->index >> 8;
+ int sensor = attr->index & 0xFF;
+ struct ibmpex_bmc_data *data = get_bmc_data(iface);
+ int divisor = data->sensors[sensor].divisor;
+ ibmpex_update_device(data);
+
+ if (divisor == 1)
+ return sprintf(buf, "%d\n", data->sensors[sensor].min);
+
+ return sprintf(buf, "%d.%d\n",
+ data->sensors[sensor].min / divisor,
+ data->sensors[sensor].min % divisor);
+}
+
+static int is_power_sensor(const char *sensor_id, int len)
+{
+ if (len < 3)
+ return 0;
+
+ if (sensor_id[0] == 0x70 &&
+ sensor_id[1] == 0x77 &&
+ sensor_id[2] == 0x72)
+ return 1;
+ return 0;
+}
+
+static int is_temp_sensor(const char *sensor_id, int len)
+{
+ if (len < 3)
+ return 0;
+
+ if (sensor_id[0] == 0x74 &&
+ sensor_id[1] == 0x65 &&
+ sensor_id[2] == 0x6D)
+ return 1;
+ return 0;
+}
+
+static int power_sensor_divisor(const char *sensor_id, int len)
+{
+ int i;
+
+ for (i = 3; i < len - 1; i++)
+ if (sensor_id[i] == 0x41 &&
+ sensor_id[i + 1] == 0x43)
+ return 1;
+
+ return 10;
+}
+
+static int ibmpex_find_sensors(struct ibmpex_bmc_data *data)
+{
+ int i, err;
+ char *n, *sensor_type;
+ int sensor_counter;
+ int num_power = 0;
+ int num_temp = 0;
+
+ err = ibmpex_query_sensor_count(data);
+ if (err < 0)
+ return -ENOENT;
+ data->num_sensors = err;
+
+ data->sensors = kzalloc(data->num_sensors * sizeof(*data->sensors),
+ GFP_KERNEL);
+ if (!data->sensors)
+ return -ENOMEM;
+
+ for (i = 0; i < data->num_sensors; i++) {
+ err = ibmpex_query_sensor_name(data, i);
+ if (err)
+ continue;
+
+ if (is_power_sensor(data->rx_msg_data, data->rx_msg_len)) {
+ sensor_type = "power";
+ num_power++;
+ sensor_counter = num_power;
+ data->sensors[i].divisor =
+ power_sensor_divisor(data->rx_msg_data,
+ data->rx_msg_len);
+ } else if (is_temp_sensor(data->rx_msg_data,
+ data->rx_msg_len)) {
+ sensor_type = "temp";
+ num_temp++;
+ sensor_counter = num_temp;
+ data->sensors[i].divisor = 1;
+ } else
+ continue;
+
+ data->sensors[i].in_use = 1;
+
+ /* Create value attribute */
+ n = kmalloc(32, GFP_KERNEL);
+ if (!n)
+ goto exit_remove;
+ sprintf(n, "%s%d_input", sensor_type, sensor_counter);
+ data->sensors[i].attr_value.dev_attr.attr.name = n;
+ data->sensors[i].attr_value.dev_attr.attr.mode = S_IRUGO;
+ data->sensors[i].attr_value.dev_attr.show = ibmpex_show_sensor;
+ data->sensors[i].attr_value.index = (data->interface << 8) |
+ (i & 0xFF);
+
+ err = device_create_file(data->bmc_device,
+ &data->sensors[i].attr_value.dev_attr);
+ if (err) {
+ data->sensors[i].attr_value.dev_attr.attr.name = NULL;
+ kfree(n);
+ goto exit_remove;
+ }
+
+ /* Create max attribute */
+ n = kmalloc(32, GFP_KERNEL);
+ if (!n)
+ goto exit_remove;
+ sprintf(n, "%s%d_max_input", sensor_type, sensor_counter);
+ data->sensors[i].attr_max.dev_attr.attr.name = n;
+ data->sensors[i].attr_max.dev_attr.attr.mode = S_IRUGO;
+ data->sensors[i].attr_max.dev_attr.show = ibmpex_show_max;
+ data->sensors[i].attr_max.index = (data->interface << 8) |
+ (i & 0xFF);
+
+ err = device_create_file(data->bmc_device,
+ &data->sensors[i].attr_max.dev_attr);
+ if (err) {
+ data->sensors[i].attr_max.dev_attr.attr.name = NULL;
+ kfree(n);
+ goto exit_remove;
+ }
+
+ /* Create min attribute */
+ n = kmalloc(32, GFP_KERNEL);
+ if (!n)
+ goto exit_remove;
+ sprintf(n, "%s%d_min_input", sensor_type, sensor_counter);
+ data->sensors[i].attr_min.dev_attr.attr.name = n;
+ data->sensors[i].attr_min.dev_attr.attr.mode = S_IRUGO;
+ data->sensors[i].attr_min.dev_attr.show = ibmpex_show_min;
+ data->sensors[i].attr_min.index = (data->interface << 8) |
+ (i & 0xFF);
+
+ err = device_create_file(data->bmc_device,
+ &data->sensors[i].attr_min.dev_attr);
+ if (err) {
+ data->sensors[i].attr_min.dev_attr.attr.name = NULL;
+ kfree(n);
+ goto exit_remove;
+ }
+ }
+
+ err = device_create_file(data->bmc_device,
+ &sensor_dev_attr_name.dev_attr);
+ if (err)
+ goto exit_remove;
+
+ return 0;
+
+exit_remove:
+ for (i = 0; i < data->num_sensors; i++) {
+ if (data->sensors[i].attr_value.dev_attr.attr.name)
+ device_remove_file(data->bmc_device,
+ &data->sensors[i].attr_value.dev_attr);
+ kfree(data->sensors[i].attr_value.dev_attr.attr.name);
+ if (data->sensors[i].attr_max.dev_attr.attr.name)
+ device_remove_file(data->bmc_device,
+ &data->sensors[i].attr_max.dev_attr);
+ kfree(data->sensors[i].attr_max.dev_attr.attr.name);
+ if (data->sensors[i].attr_min.dev_attr.attr.name)
+ device_remove_file(data->bmc_device,
+ &data->sensors[i].attr_min.dev_attr);
+ kfree(data->sensors[i].attr_min.dev_attr.attr.name);
+ }
+
+ kfree(data->sensors);
+ return -ENOENT;
+}
+
+static void ibmpex_register_bmc(int iface, struct device *dev)
+{
+ struct ibmpex_bmc_data *data;
+ int err;
+
+ data = kzalloc(sizeof(*data), GFP_KERNEL);
+ if (!data) {
+ printk(KERN_ERR DRVNAME ": Insufficient memory for BMC "
+ "interface %d.\n", data->interface);
+ return;
+ }
+
+ data->address.addr_type = IPMI_SYSTEM_INTERFACE_ADDR_TYPE;
+ data->address.channel = IPMI_BMC_CHANNEL;
+ data->address.data[0] = 0;
+ data->interface = iface;
+ data->bmc_device = dev;
+
+ /* Create IPMI messaging interface user */
+ err = ipmi_create_user(data->interface, &driver_data.ipmi_hndlrs,
+ data, &data->user);
+ if (err < 0) {
+ printk(KERN_ERR DRVNAME ": Error, unable to register user with "
+ "ipmi interface %d\n",
+ data->interface);
+ goto out;
+ }
+
+ mutex_init(&data->lock);
+
+ /* Initialize message */
+ data->tx_msgid = 0;
+ init_completion(&data->read_complete);
+ data->tx_message.netfn = 0x3A;
+ data->tx_message.cmd = 0x3C;
+ data->tx_message.data = data->tx_msg_data;
+
+ /* Does this BMC support PowerExecutive? */
+ err = ibmpex_ver_check(data);
+ if (err)
+ goto out_user;
+
+ /* Register the BMC as a HWMON class device */
+ data->class_dev = hwmon_device_register(data->bmc_device);
+
+ if (IS_ERR(data->class_dev)) {
+ printk(KERN_ERR DRVNAME ": Error, unable to register hwmon "
+ "class device for interface %d\n",
+ data->interface);
+ kfree(data);
+ return;
+ }
+
+ /* finally add the new bmc data to the bmc data list */
+ list_add_tail(&data->list, &driver_data.bmc_data);
+
+ /* Now go find all the sensors */
+ err = ibmpex_find_sensors(data);
+ if (err) {
+ printk(KERN_ERR "Error %d allocating memory\n", err);
+ goto out_register;
+ }
+
+ return;
+
+out_register:
+ hwmon_device_unregister(data->class_dev);
+out_user:
+ ipmi_destroy_user(data->user);
+out:
+ kfree(data);
+}
+
+static void ibmpex_bmc_delete(struct ibmpex_bmc_data *data)
+{
+ int i;
+
+ device_remove_file(data->bmc_device, &sensor_dev_attr_name.dev_attr);
+ for (i = 0; i < data->num_sensors; i++) {
+ if (data->sensors[i].attr_max.dev_attr.attr.name)
+ device_remove_file(data->bmc_device,
+ &data->sensors[i].attr_max.dev_attr);
+ kfree(data->sensors[i].attr_max.dev_attr.attr.name);
+ if (data->sensors[i].attr_min.dev_attr.attr.name)
+ device_remove_file(data->bmc_device,
+ &data->sensors[i].attr_min.dev_attr);
+ kfree(data->sensors[i].attr_min.dev_attr.attr.name);
+ if (data->sensors[i].attr_value.dev_attr.attr.name)
+ device_remove_file(data->bmc_device,
+ &data->sensors[i].attr_value.dev_attr);
+ kfree(data->sensors[i].attr_value.dev_attr.attr.name);
+ }
+ list_del(&data->list);
+ hwmon_device_unregister(data->class_dev);
+ ipmi_destroy_user(data->user);
+ if (data->sensors)
+ kfree(data->sensors);
+ kfree(data);
+}
+
+static void ibmpex_bmc_gone(int iface)
+{
+ struct ibmpex_bmc_data *data = get_bmc_data(iface);
+
+ if (!data)
+ return;
+
+ ibmpex_bmc_delete(data);
+}
+
+static void ibmpex_msg_handler(struct ipmi_recv_msg *msg, void *user_msg_data)
+{
+ struct ibmpex_bmc_data *data = (struct ibmpex_bmc_data *)user_msg_data;
+
+ if (msg->msgid != data->tx_msgid) {
+ printk(KERN_ERR "Received msgid (%02x) and transmitted "
+ "msgid (%02x) mismatch!\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) {
+ data->rx_msg_len = msg->msg.data_len - 1;
+ 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);
+}
+
+static int __init ibmpex_init(void)
+{
+ return ipmi_smi_watcher_register(&driver_data.bmc_events);
+}
+
+static void __exit ibmpex_exit(void)
+{
+ struct ibmpex_bmc_data *p, *next;
+
+ ipmi_smi_watcher_unregister(&driver_data.bmc_events);
+ list_for_each_entry_safe(p, next, &driver_data.bmc_data, list)
+ ibmpex_bmc_delete(p);
+}
+
+MODULE_AUTHOR("Darrick J. Wong <[email protected]>");
+MODULE_DESCRIPTION("IBM PowerExecutive power/temperature sensor driver");
+MODULE_LICENSE("GPL");
+
+module_init(ibmpex_init);
+module_exit(ibmpex_exit);
On Mon, 27 Aug 2007, Darrick J. Wong wrote:
> documentation doesn't mention any naming conventions for sensors that
> measure Watts, so I am proposing that they be called "powerX_input" in a
> fashion similar to temperature/rpm/current sensors. If that is
> agreeable to everyone, I'll post a follow-up patch to amend the
> documentation.
What unit should we use? Watts are way, way too big as there is no
floating/fixed point in sysfs. 10^-6 W is probably what is called for,
since we already need 10^-3 V and 10^-3 A. Small portable devices can
easily draw less than 10^-3 W nowadays.
--
"One disk to rule them all, One disk to find them. One disk to bring
them all and in the darkness grind them. In the Land of Redmond
where the shadows lie." -- The Silicon Valley Tarot
Henrique Holschuh
Hi Darrick, hi Henrique,
Good thing that manufacturers start including wattmeters in their
hardware. Hopefully this will help users better control their power
consumption in the long run.
On Mon, 27 Aug 2007 22:50:29 -0300, Henrique de Moraes Holschuh wrote:
> On Mon, 27 Aug 2007, Darrick J. Wong wrote:
> > documentation doesn't mention any naming conventions for sensors that
> > measure Watts, so I am proposing that they be called "powerX_input" in a
> > fashion similar to temperature/rpm/current sensors. If that is
> > agreeable to everyone, I'll post a follow-up patch to amend the
> > documentation.
>
> What unit should we use? Watts are way, way too big as there is no
> floating/fixed point in sysfs. 10^-6 W is probably what is called for,
> since we already need 10^-3 V and 10^-3 A. Small portable devices can
> easily draw less than 10^-3 W nowadays.
Good point. The driver currently exports non-integer values, which is
not acceptable, so indeed it needs to be changed. We want at least a
resolution of 10^-3 W. Not sure about 10^-6 W. I am surprised that
portable devices can really draw less than 1 mW, and be it the case, I
doubt that manufacturers will embed a wattmetter: it would probably
draw more current than the rest of the device ;) so it may not be
relevant for our decision.
So I think I'd go with 10^-3 W, but I welcome diverging opinions. Out
of curiosity, what is the physical resolution of IBM's device?
I see that the driver relies on IPMI. Can't it be merged with the
out-of-tree impisensors driver then? This would give that driver some
momentum so that it can finally be merged, and I would like to avoid
having two drivers if one is enough. Note though that I don't know
anything about IPMI so I might as well be totally wrong ;)
--
Jean Delvare
On Tue, Aug 28, 2007 at 01:19:42PM +0200, Jean Delvare wrote:
> Hi Darrick, hi Henrique,
>
> Good thing that manufacturers start including wattmeters in their
> hardware. Hopefully this will help users better control their power
> consumption in the long run.
Yes, we're working on that too. :)
> > What unit should we use? Watts are way, way too big as there is no
> > floating/fixed point in sysfs. 10^-6 W is probably what is called for,
> > since we already need 10^-3 V and 10^-3 A. Small portable devices can
> > easily draw less than 10^-3 W nowadays.
>
> Good point. The driver currently exports non-integer values, which is
> not acceptable, so indeed it needs to be changed. We want at least a
I had a feeling you might say that. :) I'll post a follow-up patch that
presents the power meters in mW instead of W.
> resolution of 10^-3 W. Not sure about 10^-6 W. I am surprised that
> portable devices can really draw less than 1 mW, and be it the case, I
Are there meters that can measure that small a quantity of power? If
so, then I'll vote for uW; otherwise, mW is fine enough for me.
> doubt that manufacturers will embed a wattmetter: it would probably
> draw more current than the rest of the device ;) so it may not be
> relevant for our decision.
>
> So I think I'd go with 10^-3 W, but I welcome diverging opinions. Out
> of curiosity, what is the physical resolution of IBM's device?
I don't know for sure, but my observation is that they're no more
accurate than a Watt. Some of those meters appear to be averages, which
would explain why they occasionally have deciWatt components.
> I see that the driver relies on IPMI. Can't it be merged with the
> out-of-tree impisensors driver then? This would give that driver some
> momentum so that it can finally be merged, and I would like to avoid
> having two drivers if one is enough. Note though that I don't know
> anything about IPMI so I might as well be totally wrong ;)
The IBM PEx meters are accessible via custom IPMI commands, not the
standard IPMI sensor interface. I considered modifying the ipmisensors
driver, but reached the opinion that it would clutter that driver
unnecessarily, particularly since there are a lot of systems with IPMI
BMCs, but most of them will not have this interface.
That said, what is standing in the way of ipmisensors being merged? I
think I can shake out some free time to apply TLC.
--D
Update the hwmon sysfs interface documentation to include a specification
for power meters.
Signed-off-by: Darrick J. Wong <[email protected]>
---
Documentation/hwmon/sysfs-interface | 15 +++++++++++++++
1 files changed, 15 insertions(+), 0 deletions(-)
diff --git a/Documentation/hwmon/sysfs-interface b/Documentation/hwmon/sysfs-interface
index b3a9e1b..da546ce 100644
--- a/Documentation/hwmon/sysfs-interface
+++ b/Documentation/hwmon/sysfs-interface
@@ -304,6 +304,21 @@ curr[1-*]_input Current input value
Unit: milliampere
RO
+*********
+* Power *
+*********
+
+power[1-*]_input Current power use
+ Unit: milliWatt
+ RO
+
+power[1-*]_max_input Historical maximum power use
+ Unit: milliWatt
+ RO
+
+power[1-*]_min_input Historical minimum power use
+ Unit: milliWatt
+ RO
**********
* Alarms *
ibm_pex: Driver to export IBM PowerExecutive power meter sensor readings
in mW.
Signed-off-by: Darrick J. Wong <[email protected]>
---
drivers/hwmon/Kconfig | 12 +
drivers/hwmon/Makefile | 1
drivers/hwmon/ibmpex.c | 600 ++++++++++++++++++++++++++++++++++++++++++++++++
3 files changed, 613 insertions(+), 0 deletions(-)
diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig
index 555f470..41ffa2e 100644
--- a/drivers/hwmon/Kconfig
+++ b/drivers/hwmon/Kconfig
@@ -275,6 +275,18 @@ config SENSORS_CORETEMP
sensor inside your CPU. Supported all are all known variants
of Intel Core family.
+config SENSORS_IBMPEX
+ tristate "IBM PowerExecutive temperature/power sensors"
+ depends on IPMI_SI
+ help
+ If you say yes here you get support for the temperature and
+ power sensors in various IBM System X servers that support
+ PowerExecutive. So far this includes the x3550, x3650, x3655,
+ x3755, and certain HS20 blades.
+
+ This driver can also be built as a module. If so, the module
+ will be called ibmpex.
+
config SENSORS_IT87
tristate "ITE IT87xx and compatibles"
select HWMON_VID
diff --git a/drivers/hwmon/Makefile b/drivers/hwmon/Makefile
index a133981..31da6fe 100644
--- a/drivers/hwmon/Makefile
+++ b/drivers/hwmon/Makefile
@@ -35,6 +35,7 @@ obj-$(CONFIG_SENSORS_FSCPOS) += fscpos.o
obj-$(CONFIG_SENSORS_GL518SM) += gl518sm.o
obj-$(CONFIG_SENSORS_GL520SM) += gl520sm.o
obj-$(CONFIG_SENSORS_HDAPS) += hdaps.o
+obj-$(CONFIG_SENSORS_IBMPEX) += ibmpex.o
obj-$(CONFIG_SENSORS_IT87) += it87.o
obj-$(CONFIG_SENSORS_K8TEMP) += k8temp.o
obj-$(CONFIG_SENSORS_LM63) += lm63.o
diff --git a/drivers/hwmon/ibmpex.c b/drivers/hwmon/ibmpex.c
new file mode 100644
index 0000000..10f1205
--- /dev/null
+++ b/drivers/hwmon/ibmpex.c
@@ -0,0 +1,600 @@
+/*
+ * A hwmon driver for the IBM PowerExecutive temperature/power sensors
+ * Copyright (C) 2007 IBM
+ *
+ * Author: Darrick J. Wong <[email protected]>
+ *
+ * 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/mutex.h>
+
+#define REFRESH_INTERVAL (5 * HZ)
+#define DRVNAME "ibmpex"
+
+static void ibmpex_msg_handler(struct ipmi_recv_msg *msg, void *user_msg_data);
+static void ibmpex_register_bmc(int iface, struct device *dev);
+static void ibmpex_bmc_gone(int iface);
+
+struct ibmpex_sensor_data {
+ int in_use;
+ s16 value;
+ s16 max;
+ s16 min;
+ int multiplier;
+
+ struct sensor_device_attribute attr_value;
+ struct sensor_device_attribute attr_max;
+ struct sensor_device_attribute attr_min;
+};
+
+struct ibmpex_bmc_data {
+ struct list_head list;
+ struct class_device *class_dev;
+ struct device *bmc_device;
+ struct mutex lock;
+ char valid;
+ unsigned long last_updated; /* In jiffies */
+
+ struct ipmi_addr address;
+ struct completion read_complete;
+ ipmi_user_t user;
+ int interface;
+
+ struct kernel_ipmi_msg tx_message;
+ unsigned char tx_msg_data[IPMI_MAX_MSG_LENGTH];
+ long tx_msgid;
+
+ unsigned char rx_msg_data[IPMI_MAX_MSG_LENGTH];
+ unsigned long rx_msg_len;
+ unsigned char rx_result;
+ int rx_recv_type;
+
+ unsigned char sensor_major;
+ unsigned char sensor_minor;
+
+ unsigned char num_sensors;
+ struct ibmpex_sensor_data *sensors;
+};
+
+struct ibmpex_driver_data {
+ struct list_head bmc_data;
+ struct ipmi_smi_watcher bmc_events;
+ struct ipmi_user_hndl ipmi_hndlrs;
+};
+
+static struct ibmpex_driver_data driver_data = {
+ .bmc_data = LIST_HEAD_INIT(driver_data.bmc_data),
+ .bmc_events = {
+ .owner = THIS_MODULE,
+ .new_smi = ibmpex_register_bmc,
+ .smi_gone = ibmpex_bmc_gone,
+ },
+ .ipmi_hndlrs = {
+ .ipmi_recv_hndl = ibmpex_msg_handler,
+ },
+};
+
+static int ibmpex_send_message(struct ibmpex_bmc_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:
+ printk(KERN_ERR "%s: request_settime=%x\n", __FUNCTION__, err);
+ return err;
+out:
+ printk(KERN_ERR "%s: validate_addr=%x\n", __FUNCTION__, err);
+ return err;
+}
+
+static int ibmpex_ver_check(struct ibmpex_bmc_data *data)
+{
+ data->tx_msg_data[0] = 0x1;
+ data->tx_message.data_len = 1;
+ ibmpex_send_message(data);
+
+ wait_for_completion(&data->read_complete);
+
+ if (data->rx_result || data->rx_msg_len != 6)
+ return -ENOENT;
+
+ data->sensor_major = data->rx_msg_data[0];
+ data->sensor_minor = data->rx_msg_data[1];
+
+ printk(KERN_INFO DRVNAME ": Found BMC with sensor interface "
+ "v%d.%d %d-%02d-%02d on interface %d\n",
+ data->sensor_major,
+ data->sensor_minor,
+ data->rx_msg_data[3] | ((u16)data->rx_msg_data[2] << 8),
+ data->rx_msg_data[4],
+ data->rx_msg_data[5],
+ data->interface);
+
+ return 0;
+}
+
+static int ibmpex_query_sensor_count(struct ibmpex_bmc_data *data)
+{
+ data->tx_msg_data[0] = 0x2;
+ data->tx_message.data_len = 1;
+ ibmpex_send_message(data);
+
+ wait_for_completion(&data->read_complete);
+
+ if (data->rx_result || data->rx_msg_len != 1)
+ return -ENOENT;
+
+ return data->rx_msg_data[0];
+}
+
+static int ibmpex_query_sensor_name(struct ibmpex_bmc_data *data, int sensor)
+{
+ data->tx_msg_data[0] = 0x3;
+ data->tx_msg_data[1] = sensor;
+ data->tx_message.data_len = 2;
+ ibmpex_send_message(data);
+
+ wait_for_completion(&data->read_complete);
+
+ if (data->rx_result || data->rx_msg_len < 1)
+ return -ENOENT;
+
+ return 0;
+}
+
+static int ibmpex_query_sensor_data(struct ibmpex_bmc_data *data, int sensor)
+{
+ data->tx_msg_data[0] = 0x6;
+ data->tx_msg_data[1] = sensor;
+ data->tx_message.data_len = 2;
+ ibmpex_send_message(data);
+
+ wait_for_completion(&data->read_complete);
+
+ if (data->rx_result || data->rx_msg_len < 26) {
+ printk(KERN_ERR "Error reading sensor %d, please check.\n",
+ sensor);
+ return -ENOENT;
+ }
+
+ return 0;
+}
+
+static void ibmpex_update_device(struct ibmpex_bmc_data *data)
+{
+ int i, err;
+
+ mutex_lock(&data->lock);
+ if (time_before(jiffies, data->last_updated + REFRESH_INTERVAL) &&
+ data->valid)
+ goto out;
+
+ for (i = 0; i < data->num_sensors; i++) {
+ if (!data->sensors[i].in_use)
+ continue;
+ err = ibmpex_query_sensor_data(data, i);
+ if (err)
+ continue;
+ data->sensors[i].value = ((u16)data->rx_msg_data[16] << 8) |
+ data->rx_msg_data[17];
+ data->sensors[i].min = ((u16)data->rx_msg_data[18] << 8) |
+ data->rx_msg_data[19];
+ data->sensors[i].max = ((u16)data->rx_msg_data[20] << 8) |
+ data->rx_msg_data[21];
+ }
+
+ data->last_updated = jiffies;
+ data->valid = 1;
+
+out:
+ mutex_unlock(&data->lock);
+}
+
+static struct ibmpex_bmc_data *get_bmc_data(int iface)
+{
+ struct ibmpex_bmc_data *p, *next;
+
+ list_for_each_entry_safe(p, next, &driver_data.bmc_data, list)
+ if (p->interface == iface)
+ return p;
+
+ return NULL;
+}
+
+static ssize_t show_name(struct device *dev, struct device_attribute *devattr,
+ char *buf)
+{
+ return sprintf(buf, "%s\n", DRVNAME);
+}
+static SENSOR_DEVICE_ATTR(name, S_IRUGO, show_name, NULL, 0);
+
+static ssize_t ibmpex_show_sensor(struct device *dev,
+ struct device_attribute *devattr,
+ char *buf)
+{
+ struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr);
+ int iface = attr->index >> 8;
+ int sensor = attr->index & 0xFF;
+ struct ibmpex_bmc_data *data = get_bmc_data(iface);
+ int multiplier = data->sensors[sensor].multiplier;
+ ibmpex_update_device(data);
+
+ return sprintf(buf, "%d\n", data->sensors[sensor].value * multiplier);
+}
+
+static ssize_t ibmpex_show_max(struct device *dev,
+ struct device_attribute *devattr,
+ char *buf)
+{
+ struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr);
+ int iface = attr->index >> 8;
+ int sensor = attr->index & 0xFF;
+ struct ibmpex_bmc_data *data = get_bmc_data(iface);
+ int multiplier = data->sensors[sensor].multiplier;
+ ibmpex_update_device(data);
+
+ return sprintf(buf, "%d\n", data->sensors[sensor].max * multiplier);
+}
+
+static ssize_t ibmpex_show_min(struct device *dev,
+ struct device_attribute *devattr,
+ char *buf)
+{
+ struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr);
+ int iface = attr->index >> 8;
+ int sensor = attr->index & 0xFF;
+ struct ibmpex_bmc_data *data = get_bmc_data(iface);
+ int multiplier = data->sensors[sensor].multiplier;
+ ibmpex_update_device(data);
+
+ return sprintf(buf, "%d\n", data->sensors[sensor].min * multiplier);
+}
+
+static int is_power_sensor(const char *sensor_id, int len)
+{
+ if (len < 3)
+ return 0;
+
+ if (sensor_id[0] == 0x70 &&
+ sensor_id[1] == 0x77 &&
+ sensor_id[2] == 0x72)
+ return 1;
+ return 0;
+}
+
+static int is_temp_sensor(const char *sensor_id, int len)
+{
+ if (len < 3)
+ return 0;
+
+ if (sensor_id[0] == 0x74 &&
+ sensor_id[1] == 0x65 &&
+ sensor_id[2] == 0x6D)
+ return 1;
+ return 0;
+}
+
+static int power_sensor_multiplier(const char *sensor_id, int len)
+{
+ int i;
+
+ for (i = 3; i < len - 1; i++)
+ if (sensor_id[i] == 0x41 &&
+ sensor_id[i + 1] == 0x43)
+ return 1000;
+
+ return 100;
+}
+
+static int ibmpex_find_sensors(struct ibmpex_bmc_data *data)
+{
+ int i, err;
+ char *n, *sensor_type;
+ int sensor_counter;
+ int num_power = 0;
+ int num_temp = 0;
+
+ err = ibmpex_query_sensor_count(data);
+ if (err < 0)
+ return -ENOENT;
+ data->num_sensors = err;
+
+ data->sensors = kzalloc(data->num_sensors * sizeof(*data->sensors),
+ GFP_KERNEL);
+ if (!data->sensors)
+ return -ENOMEM;
+
+ for (i = 0; i < data->num_sensors; i++) {
+ err = ibmpex_query_sensor_name(data, i);
+ if (err)
+ continue;
+
+ if (is_power_sensor(data->rx_msg_data, data->rx_msg_len)) {
+ sensor_type = "power";
+ num_power++;
+ sensor_counter = num_power;
+ data->sensors[i].multiplier =
+ power_sensor_multiplier(data->rx_msg_data,
+ data->rx_msg_len);
+ } else if (is_temp_sensor(data->rx_msg_data,
+ data->rx_msg_len)) {
+ sensor_type = "temp";
+ num_temp++;
+ sensor_counter = num_temp;
+ data->sensors[i].multiplier = 1;
+ } else
+ continue;
+
+ data->sensors[i].in_use = 1;
+
+ /* Create value attribute */
+ n = kmalloc(32, GFP_KERNEL);
+ if (!n)
+ goto exit_remove;
+ sprintf(n, "%s%d_input", sensor_type, sensor_counter);
+ data->sensors[i].attr_value.dev_attr.attr.name = n;
+ data->sensors[i].attr_value.dev_attr.attr.mode = S_IRUGO;
+ data->sensors[i].attr_value.dev_attr.show = ibmpex_show_sensor;
+ data->sensors[i].attr_value.index = (data->interface << 8) |
+ (i & 0xFF);
+
+ err = device_create_file(data->bmc_device,
+ &data->sensors[i].attr_value.dev_attr);
+ if (err) {
+ data->sensors[i].attr_value.dev_attr.attr.name = NULL;
+ kfree(n);
+ goto exit_remove;
+ }
+
+ /* Create max attribute */
+ n = kmalloc(32, GFP_KERNEL);
+ if (!n)
+ goto exit_remove;
+ sprintf(n, "%s%d_max_input", sensor_type, sensor_counter);
+ data->sensors[i].attr_max.dev_attr.attr.name = n;
+ data->sensors[i].attr_max.dev_attr.attr.mode = S_IRUGO;
+ data->sensors[i].attr_max.dev_attr.show = ibmpex_show_max;
+ data->sensors[i].attr_max.index = (data->interface << 8) |
+ (i & 0xFF);
+
+ err = device_create_file(data->bmc_device,
+ &data->sensors[i].attr_max.dev_attr);
+ if (err) {
+ data->sensors[i].attr_max.dev_attr.attr.name = NULL;
+ kfree(n);
+ goto exit_remove;
+ }
+
+ /* Create min attribute */
+ n = kmalloc(32, GFP_KERNEL);
+ if (!n)
+ goto exit_remove;
+ sprintf(n, "%s%d_min_input", sensor_type, sensor_counter);
+ data->sensors[i].attr_min.dev_attr.attr.name = n;
+ data->sensors[i].attr_min.dev_attr.attr.mode = S_IRUGO;
+ data->sensors[i].attr_min.dev_attr.show = ibmpex_show_min;
+ data->sensors[i].attr_min.index = (data->interface << 8) |
+ (i & 0xFF);
+
+ err = device_create_file(data->bmc_device,
+ &data->sensors[i].attr_min.dev_attr);
+ if (err) {
+ data->sensors[i].attr_min.dev_attr.attr.name = NULL;
+ kfree(n);
+ goto exit_remove;
+ }
+ }
+
+ err = device_create_file(data->bmc_device,
+ &sensor_dev_attr_name.dev_attr);
+ if (err)
+ goto exit_remove;
+
+ return 0;
+
+exit_remove:
+ for (i = 0; i < data->num_sensors; i++) {
+ if (data->sensors[i].attr_value.dev_attr.attr.name)
+ device_remove_file(data->bmc_device,
+ &data->sensors[i].attr_value.dev_attr);
+ kfree(data->sensors[i].attr_value.dev_attr.attr.name);
+ if (data->sensors[i].attr_max.dev_attr.attr.name)
+ device_remove_file(data->bmc_device,
+ &data->sensors[i].attr_max.dev_attr);
+ kfree(data->sensors[i].attr_max.dev_attr.attr.name);
+ if (data->sensors[i].attr_min.dev_attr.attr.name)
+ device_remove_file(data->bmc_device,
+ &data->sensors[i].attr_min.dev_attr);
+ kfree(data->sensors[i].attr_min.dev_attr.attr.name);
+ }
+
+ kfree(data->sensors);
+ return -ENOENT;
+}
+
+static void ibmpex_register_bmc(int iface, struct device *dev)
+{
+ struct ibmpex_bmc_data *data;
+ int err;
+
+ data = kzalloc(sizeof(*data), GFP_KERNEL);
+ if (!data) {
+ printk(KERN_ERR DRVNAME ": Insufficient memory for BMC "
+ "interface %d.\n", data->interface);
+ return;
+ }
+
+ data->address.addr_type = IPMI_SYSTEM_INTERFACE_ADDR_TYPE;
+ data->address.channel = IPMI_BMC_CHANNEL;
+ data->address.data[0] = 0;
+ data->interface = iface;
+ data->bmc_device = dev;
+
+ /* Create IPMI messaging interface user */
+ err = ipmi_create_user(data->interface, &driver_data.ipmi_hndlrs,
+ data, &data->user);
+ if (err < 0) {
+ printk(KERN_ERR DRVNAME ": Error, unable to register user with "
+ "ipmi interface %d\n",
+ data->interface);
+ goto out;
+ }
+
+ mutex_init(&data->lock);
+
+ /* Initialize message */
+ data->tx_msgid = 0;
+ init_completion(&data->read_complete);
+ data->tx_message.netfn = 0x3A;
+ data->tx_message.cmd = 0x3C;
+ data->tx_message.data = data->tx_msg_data;
+
+ /* Does this BMC support PowerExecutive? */
+ err = ibmpex_ver_check(data);
+ if (err)
+ goto out_user;
+
+ /* Register the BMC as a HWMON class device */
+ data->class_dev = hwmon_device_register(data->bmc_device);
+
+ if (IS_ERR(data->class_dev)) {
+ printk(KERN_ERR DRVNAME ": Error, unable to register hwmon "
+ "class device for interface %d\n",
+ data->interface);
+ kfree(data);
+ return;
+ }
+
+ /* finally add the new bmc data to the bmc data list */
+ list_add_tail(&data->list, &driver_data.bmc_data);
+
+ /* Now go find all the sensors */
+ err = ibmpex_find_sensors(data);
+ if (err) {
+ printk(KERN_ERR "Error %d allocating memory\n", err);
+ goto out_register;
+ }
+
+ return;
+
+out_register:
+ hwmon_device_unregister(data->class_dev);
+out_user:
+ ipmi_destroy_user(data->user);
+out:
+ kfree(data);
+}
+
+static void ibmpex_bmc_delete(struct ibmpex_bmc_data *data)
+{
+ int i;
+
+ device_remove_file(data->bmc_device, &sensor_dev_attr_name.dev_attr);
+ for (i = 0; i < data->num_sensors; i++) {
+ if (data->sensors[i].attr_max.dev_attr.attr.name)
+ device_remove_file(data->bmc_device,
+ &data->sensors[i].attr_max.dev_attr);
+ kfree(data->sensors[i].attr_max.dev_attr.attr.name);
+ if (data->sensors[i].attr_min.dev_attr.attr.name)
+ device_remove_file(data->bmc_device,
+ &data->sensors[i].attr_min.dev_attr);
+ kfree(data->sensors[i].attr_min.dev_attr.attr.name);
+ if (data->sensors[i].attr_value.dev_attr.attr.name)
+ device_remove_file(data->bmc_device,
+ &data->sensors[i].attr_value.dev_attr);
+ kfree(data->sensors[i].attr_value.dev_attr.attr.name);
+ }
+ list_del(&data->list);
+ hwmon_device_unregister(data->class_dev);
+ ipmi_destroy_user(data->user);
+ if (data->sensors)
+ kfree(data->sensors);
+ kfree(data);
+}
+
+static void ibmpex_bmc_gone(int iface)
+{
+ struct ibmpex_bmc_data *data = get_bmc_data(iface);
+
+ if (!data)
+ return;
+
+ ibmpex_bmc_delete(data);
+}
+
+static void ibmpex_msg_handler(struct ipmi_recv_msg *msg, void *user_msg_data)
+{
+ struct ibmpex_bmc_data *data = (struct ibmpex_bmc_data *)user_msg_data;
+
+ if (msg->msgid != data->tx_msgid) {
+ printk(KERN_ERR "Received msgid (%02x) and transmitted "
+ "msgid (%02x) mismatch!\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) {
+ data->rx_msg_len = msg->msg.data_len - 1;
+ 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);
+}
+
+static int __init ibmpex_init(void)
+{
+ return ipmi_smi_watcher_register(&driver_data.bmc_events);
+}
+
+static void __exit ibmpex_exit(void)
+{
+ struct ibmpex_bmc_data *p, *next;
+
+ ipmi_smi_watcher_unregister(&driver_data.bmc_events);
+ list_for_each_entry_safe(p, next, &driver_data.bmc_data, list)
+ ibmpex_bmc_delete(p);
+}
+
+MODULE_AUTHOR("Darrick J. Wong <[email protected]>");
+MODULE_DESCRIPTION("IBM PowerExecutive power/temperature sensor driver");
+MODULE_LICENSE("GPL");
+
+module_init(ibmpex_init);
+module_exit(ibmpex_exit);
Dave Hansen complained about the magic numbers, repetitive code, and
various other minor problems with the driver code, so here's a v3 with
the magic numbers migrated to the top of the file and #define'd,
helper macros taking place of the bit shifting/masking activities, and
the compression of the value/min/max sysfs code into parameterized
functions.
--
ibm_pex: Driver to export IBM PowerExecutive power meter sensors.
Signed-off-by: Darrick J. Wong <[email protected]>
---
drivers/hwmon/Kconfig | 12 +
drivers/hwmon/Makefile | 1
drivers/hwmon/ibmpex.c | 564 ++++++++++++++++++++++++++++++++++++++++++++++++
3 files changed, 577 insertions(+), 0 deletions(-)
diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig
index 555f470..41ffa2e 100644
--- a/drivers/hwmon/Kconfig
+++ b/drivers/hwmon/Kconfig
@@ -275,6 +275,18 @@ config SENSORS_CORETEMP
sensor inside your CPU. Supported all are all known variants
of Intel Core family.
+config SENSORS_IBMPEX
+ tristate "IBM PowerExecutive temperature/power sensors"
+ depends on IPMI_SI
+ help
+ If you say yes here you get support for the temperature and
+ power sensors in various IBM System X servers that support
+ PowerExecutive. So far this includes the x3550, x3650, x3655,
+ x3755, and certain HS20 blades.
+
+ This driver can also be built as a module. If so, the module
+ will be called ibmpex.
+
config SENSORS_IT87
tristate "ITE IT87xx and compatibles"
select HWMON_VID
diff --git a/drivers/hwmon/Makefile b/drivers/hwmon/Makefile
index a133981..31da6fe 100644
--- a/drivers/hwmon/Makefile
+++ b/drivers/hwmon/Makefile
@@ -35,6 +35,7 @@ obj-$(CONFIG_SENSORS_FSCPOS) += fscpos.o
obj-$(CONFIG_SENSORS_GL518SM) += gl518sm.o
obj-$(CONFIG_SENSORS_GL520SM) += gl520sm.o
obj-$(CONFIG_SENSORS_HDAPS) += hdaps.o
+obj-$(CONFIG_SENSORS_IBMPEX) += ibmpex.o
obj-$(CONFIG_SENSORS_IT87) += it87.o
obj-$(CONFIG_SENSORS_K8TEMP) += k8temp.o
obj-$(CONFIG_SENSORS_LM63) += lm63.o
diff --git a/drivers/hwmon/ibmpex.c b/drivers/hwmon/ibmpex.c
new file mode 100644
index 0000000..632f897
--- /dev/null
+++ b/drivers/hwmon/ibmpex.c
@@ -0,0 +1,564 @@
+/*
+ * A hwmon driver for the IBM PowerExecutive temperature/power sensors
+ * Copyright (C) 2007 IBM
+ *
+ * Author: Darrick J. Wong <[email protected]>
+ *
+ * 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/mutex.h>
+
+#define REFRESH_INTERVAL (5 * HZ)
+#define DRVNAME "ibmpex"
+
+#define PEX_GET_VERSION 1
+#define PEX_GET_SENSOR_COUNT 2
+#define PEX_GET_SENSOR_NAME 3
+#define PEX_GET_SENSOR_DATA 6
+
+#define PEX_NET_FUNCTION 0x3A
+#define PEX_COMMAND 0x3C
+
+static inline u16 extract_value(const char *data, int offset)
+{
+ u16 val = *(u16*)&data[offset];
+ return be16_to_cpu(val);
+}
+
+#define PEX_INTERFACE(idx) ((idx) >> 16)
+#define PEX_SENSOR(idx) (((idx) >> 8) & 0xFF)
+#define PEX_FUNC(idx) ((idx) & 0xFF)
+#define PEX_INDEX(iface, num, fn) (((iface) << 16) | ((num) << 8) | (fn))
+
+#define PEX_SENSOR_TYPE_LEN 3
+static char power_sensor_sig[] = {0x70, 0x77, 0x72};
+static char temp_sensor_sig[] = {0x74, 0x65, 0x6D};
+
+#define PEX_MULT_LEN 2
+static char watt_sensor_sig[] = {0x41, 0x43};
+
+#define PEX_NUM_SENSOR_FUNCS 3
+static char *sensor_name_templates[] = {
+ "%s%d_input",
+ "%s%d_min_input",
+ "%s%d_max_input"
+};
+
+static void ibmpex_msg_handler(struct ipmi_recv_msg *msg, void *user_msg_data);
+static void ibmpex_register_bmc(int iface, struct device *dev);
+static void ibmpex_bmc_gone(int iface);
+
+struct ibmpex_sensor_data {
+ int in_use;
+ s16 values[PEX_NUM_SENSOR_FUNCS];
+ int multiplier;
+
+ struct sensor_device_attribute attr[PEX_NUM_SENSOR_FUNCS];
+};
+
+struct ibmpex_bmc_data {
+ struct list_head list;
+ struct class_device *class_dev;
+ struct device *bmc_device;
+ struct mutex lock;
+ char valid;
+ unsigned long last_updated; /* In jiffies */
+
+ struct ipmi_addr address;
+ struct completion read_complete;
+ ipmi_user_t user;
+ int interface;
+
+ struct kernel_ipmi_msg tx_message;
+ unsigned char tx_msg_data[IPMI_MAX_MSG_LENGTH];
+ long tx_msgid;
+
+ unsigned char rx_msg_data[IPMI_MAX_MSG_LENGTH];
+ unsigned long rx_msg_len;
+ unsigned char rx_result;
+ int rx_recv_type;
+
+ unsigned char sensor_major;
+ unsigned char sensor_minor;
+
+ unsigned char num_sensors;
+ struct ibmpex_sensor_data *sensors;
+};
+
+struct ibmpex_driver_data {
+ struct list_head bmc_data;
+ struct ipmi_smi_watcher bmc_events;
+ struct ipmi_user_hndl ipmi_hndlrs;
+};
+
+static struct ibmpex_driver_data driver_data = {
+ .bmc_data = LIST_HEAD_INIT(driver_data.bmc_data),
+ .bmc_events = {
+ .owner = THIS_MODULE,
+ .new_smi = ibmpex_register_bmc,
+ .smi_gone = ibmpex_bmc_gone,
+ },
+ .ipmi_hndlrs = {
+ .ipmi_recv_hndl = ibmpex_msg_handler,
+ },
+};
+
+static int ibmpex_send_message(struct ibmpex_bmc_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:
+ printk(KERN_ERR "%s: request_settime=%x\n", __FUNCTION__, err);
+ return err;
+out:
+ printk(KERN_ERR "%s: validate_addr=%x\n", __FUNCTION__, err);
+ return err;
+}
+
+static int ibmpex_ver_check(struct ibmpex_bmc_data *data)
+{
+ data->tx_msg_data[0] = PEX_GET_VERSION;
+ data->tx_message.data_len = 1;
+ ibmpex_send_message(data);
+
+ wait_for_completion(&data->read_complete);
+
+ if (data->rx_result || data->rx_msg_len != 6)
+ return -ENOENT;
+
+ data->sensor_major = data->rx_msg_data[0];
+ data->sensor_minor = data->rx_msg_data[1];
+
+ printk(KERN_INFO DRVNAME ": Found BMC with sensor interface "
+ "v%d.%d %d-%02d-%02d on interface %d\n",
+ data->sensor_major,
+ data->sensor_minor,
+ extract_value(data->rx_msg_data, 2),
+ data->rx_msg_data[4],
+ data->rx_msg_data[5],
+ data->interface);
+
+ return 0;
+}
+
+static int ibmpex_query_sensor_count(struct ibmpex_bmc_data *data)
+{
+ data->tx_msg_data[0] = PEX_GET_SENSOR_COUNT;
+ data->tx_message.data_len = 1;
+ ibmpex_send_message(data);
+
+ wait_for_completion(&data->read_complete);
+
+ if (data->rx_result || data->rx_msg_len != 1)
+ return -ENOENT;
+
+ return data->rx_msg_data[0];
+}
+
+static int ibmpex_query_sensor_name(struct ibmpex_bmc_data *data, int sensor)
+{
+ data->tx_msg_data[0] = PEX_GET_SENSOR_NAME;
+ data->tx_msg_data[1] = sensor;
+ data->tx_message.data_len = 2;
+ ibmpex_send_message(data);
+
+ wait_for_completion(&data->read_complete);
+
+ if (data->rx_result || data->rx_msg_len < 1)
+ return -ENOENT;
+
+ return 0;
+}
+
+static int ibmpex_query_sensor_data(struct ibmpex_bmc_data *data, int sensor)
+{
+ data->tx_msg_data[0] = PEX_GET_SENSOR_DATA;
+ data->tx_msg_data[1] = sensor;
+ data->tx_message.data_len = 2;
+ ibmpex_send_message(data);
+
+ wait_for_completion(&data->read_complete);
+
+ if (data->rx_result || data->rx_msg_len < 26) {
+ printk(KERN_ERR "Error reading sensor %d, please check.\n",
+ sensor);
+ return -ENOENT;
+ }
+
+ return 0;
+}
+
+static void ibmpex_update_device(struct ibmpex_bmc_data *data)
+{
+ int i, err;
+
+ mutex_lock(&data->lock);
+ if (time_before(jiffies, data->last_updated + REFRESH_INTERVAL) &&
+ data->valid)
+ goto out;
+
+ for (i = 0; i < data->num_sensors; i++) {
+ if (!data->sensors[i].in_use)
+ continue;
+ err = ibmpex_query_sensor_data(data, i);
+ if (err)
+ continue;
+ data->sensors[i].values[0] =
+ extract_value(data->rx_msg_data, 16);
+ data->sensors[i].values[1] =
+ extract_value(data->rx_msg_data, 18);
+ data->sensors[i].values[2] =
+ extract_value(data->rx_msg_data, 20);
+ }
+
+ data->last_updated = jiffies;
+ data->valid = 1;
+
+out:
+ mutex_unlock(&data->lock);
+}
+
+static struct ibmpex_bmc_data *get_bmc_data(int iface)
+{
+ struct ibmpex_bmc_data *p, *next;
+
+ list_for_each_entry_safe(p, next, &driver_data.bmc_data, list)
+ if (p->interface == iface)
+ return p;
+
+ return NULL;
+}
+
+static ssize_t show_name(struct device *dev, struct device_attribute *devattr,
+ char *buf)
+{
+ return sprintf(buf, "%s\n", DRVNAME);
+}
+static SENSOR_DEVICE_ATTR(name, S_IRUGO, show_name, NULL, 0);
+
+static ssize_t ibmpex_show_sensor(struct device *dev,
+ struct device_attribute *devattr,
+ char *buf)
+{
+ struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr);
+ int iface = PEX_INTERFACE(attr->index);
+ int sensor = PEX_SENSOR(attr->index);
+ int func = PEX_FUNC(attr->index);
+ struct ibmpex_bmc_data *data = get_bmc_data(iface);
+ int multiplier = data->sensors[sensor].multiplier;
+ ibmpex_update_device(data);
+
+ return sprintf(buf, "%d\n",
+ data->sensors[sensor].values[func] * multiplier);
+}
+
+static int is_power_sensor(const char *sensor_id, int len)
+{
+ if (len < PEX_SENSOR_TYPE_LEN)
+ return 0;
+
+ if (!memcmp(sensor_id, power_sensor_sig, PEX_SENSOR_TYPE_LEN))
+ return 1;
+ return 0;
+}
+
+static int is_temp_sensor(const char *sensor_id, int len)
+{
+ if (len < PEX_SENSOR_TYPE_LEN)
+ return 0;
+
+ if (!memcmp(sensor_id, temp_sensor_sig, PEX_SENSOR_TYPE_LEN))
+ return 1;
+ return 0;
+}
+
+static int power_sensor_multiplier(const char *sensor_id, int len)
+{
+ int i;
+
+ for (i = PEX_SENSOR_TYPE_LEN; i < len - 1; i++)
+ if (!memcmp(&sensor_id[i], watt_sensor_sig, PEX_MULT_LEN))
+ return 1000;
+
+ return 100;
+}
+
+static int create_sensor(struct ibmpex_bmc_data *data, const char *type,
+ int counter, int sensor, int func)
+{
+ int err;
+ char *n;
+
+ n = kmalloc(32, GFP_KERNEL);
+ if (!n)
+ return -ENOMEM;
+
+ sprintf(n, sensor_name_templates[func], type, counter);
+ data->sensors[sensor].attr[func].dev_attr.attr.name = n;
+ data->sensors[sensor].attr[func].dev_attr.attr.mode = S_IRUGO;
+ data->sensors[sensor].attr[func].dev_attr.show = ibmpex_show_sensor;
+ data->sensors[sensor].attr[func].index =
+ PEX_INDEX(data->interface, sensor, func);
+
+ err = device_create_file(data->bmc_device,
+ &data->sensors[sensor].attr[func].dev_attr);
+ if (err) {
+ data->sensors[sensor].attr[func].dev_attr.attr.name = NULL;
+ kfree(n);
+ return err;
+ }
+
+ return 0;
+}
+
+static int ibmpex_find_sensors(struct ibmpex_bmc_data *data)
+{
+ int i, j, err;
+ char *sensor_type;
+ int sensor_counter;
+ int num_power = 0;
+ int num_temp = 0;
+
+ err = ibmpex_query_sensor_count(data);
+ if (err < 0)
+ return -ENOENT;
+ data->num_sensors = err;
+
+ data->sensors = kzalloc(data->num_sensors * sizeof(*data->sensors),
+ GFP_KERNEL);
+ if (!data->sensors)
+ return -ENOMEM;
+
+ for (i = 0; i < data->num_sensors; i++) {
+ err = ibmpex_query_sensor_name(data, i);
+ if (err)
+ continue;
+
+ if (is_power_sensor(data->rx_msg_data, data->rx_msg_len)) {
+ sensor_type = "power";
+ num_power++;
+ sensor_counter = num_power;
+ data->sensors[i].multiplier =
+ power_sensor_multiplier(data->rx_msg_data,
+ data->rx_msg_len);
+ } else if (is_temp_sensor(data->rx_msg_data,
+ data->rx_msg_len)) {
+ sensor_type = "temp";
+ num_temp++;
+ sensor_counter = num_temp;
+ data->sensors[i].multiplier = 1;
+ } else
+ continue;
+
+ data->sensors[i].in_use = 1;
+
+ /* Create attributes */
+ for (j = 0; j < PEX_NUM_SENSOR_FUNCS; j++)
+ if (create_sensor(data, sensor_type, sensor_counter,
+ i, j))
+ goto exit_remove;
+ }
+
+ err = device_create_file(data->bmc_device,
+ &sensor_dev_attr_name.dev_attr);
+ if (err)
+ goto exit_remove;
+
+ return 0;
+
+exit_remove:
+ for (i = 0; i < data->num_sensors; i++)
+ for (j = 0; j < PEX_NUM_SENSOR_FUNCS; j++) {
+ if (!data->sensors[i].attr[j].dev_attr.attr.name)
+ continue;
+ device_remove_file(data->bmc_device,
+ &data->sensors[i].attr[j].dev_attr);
+ kfree(data->sensors[i].attr[j].dev_attr.attr.name);
+ }
+
+ kfree(data->sensors);
+ return -ENOENT;
+}
+
+static void ibmpex_register_bmc(int iface, struct device *dev)
+{
+ struct ibmpex_bmc_data *data;
+ int err;
+
+ data = kzalloc(sizeof(*data), GFP_KERNEL);
+ if (!data) {
+ printk(KERN_ERR DRVNAME ": Insufficient memory for BMC "
+ "interface %d.\n", data->interface);
+ return;
+ }
+
+ data->address.addr_type = IPMI_SYSTEM_INTERFACE_ADDR_TYPE;
+ data->address.channel = IPMI_BMC_CHANNEL;
+ data->address.data[0] = 0;
+ data->interface = iface;
+ data->bmc_device = dev;
+
+ /* Create IPMI messaging interface user */
+ err = ipmi_create_user(data->interface, &driver_data.ipmi_hndlrs,
+ data, &data->user);
+ if (err < 0) {
+ printk(KERN_ERR DRVNAME ": Error, unable to register user with "
+ "ipmi interface %d\n",
+ data->interface);
+ goto out;
+ }
+
+ mutex_init(&data->lock);
+
+ /* Initialize message */
+ data->tx_msgid = 0;
+ init_completion(&data->read_complete);
+ data->tx_message.netfn = PEX_NET_FUNCTION;
+ data->tx_message.cmd = PEX_COMMAND;
+ data->tx_message.data = data->tx_msg_data;
+
+ /* Does this BMC support PowerExecutive? */
+ err = ibmpex_ver_check(data);
+ if (err)
+ goto out_user;
+
+ /* Register the BMC as a HWMON class device */
+ data->class_dev = hwmon_device_register(data->bmc_device);
+
+ if (IS_ERR(data->class_dev)) {
+ printk(KERN_ERR DRVNAME ": Error, unable to register hwmon "
+ "class device for interface %d\n",
+ data->interface);
+ kfree(data);
+ return;
+ }
+
+ /* finally add the new bmc data to the bmc data list */
+ list_add_tail(&data->list, &driver_data.bmc_data);
+
+ /* Now go find all the sensors */
+ err = ibmpex_find_sensors(data);
+ if (err) {
+ printk(KERN_ERR "Error %d allocating memory\n", err);
+ goto out_register;
+ }
+
+ return;
+
+out_register:
+ hwmon_device_unregister(data->class_dev);
+out_user:
+ ipmi_destroy_user(data->user);
+out:
+ kfree(data);
+}
+
+static void ibmpex_bmc_delete(struct ibmpex_bmc_data *data)
+{
+ int i, j;
+
+ device_remove_file(data->bmc_device, &sensor_dev_attr_name.dev_attr);
+ for (i = 0; i < data->num_sensors; i++)
+ for (j = 0; j < PEX_NUM_SENSOR_FUNCS; j++) {
+ if (!data->sensors[i].attr[j].dev_attr.attr.name)
+ continue;
+ device_remove_file(data->bmc_device,
+ &data->sensors[i].attr[j].dev_attr);
+ kfree(data->sensors[i].attr[j].dev_attr.attr.name);
+ }
+
+ list_del(&data->list);
+ hwmon_device_unregister(data->class_dev);
+ ipmi_destroy_user(data->user);
+ if (data->sensors)
+ kfree(data->sensors);
+ kfree(data);
+}
+
+static void ibmpex_bmc_gone(int iface)
+{
+ struct ibmpex_bmc_data *data = get_bmc_data(iface);
+
+ if (!data)
+ return;
+
+ ibmpex_bmc_delete(data);
+}
+
+static void ibmpex_msg_handler(struct ipmi_recv_msg *msg, void *user_msg_data)
+{
+ struct ibmpex_bmc_data *data = (struct ibmpex_bmc_data *)user_msg_data;
+
+ if (msg->msgid != data->tx_msgid) {
+ printk(KERN_ERR "Received msgid (%02x) and transmitted "
+ "msgid (%02x) mismatch!\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) {
+ data->rx_msg_len = msg->msg.data_len - 1;
+ 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);
+}
+
+static int __init ibmpex_init(void)
+{
+ return ipmi_smi_watcher_register(&driver_data.bmc_events);
+}
+
+static void __exit ibmpex_exit(void)
+{
+ struct ibmpex_bmc_data *p, *next;
+
+ ipmi_smi_watcher_unregister(&driver_data.bmc_events);
+ list_for_each_entry_safe(p, next, &driver_data.bmc_data, list)
+ ibmpex_bmc_delete(p);
+}
+
+MODULE_AUTHOR("Darrick J. Wong <[email protected]>");
+MODULE_DESCRIPTION("IBM PowerExecutive power/temperature sensor driver");
+MODULE_LICENSE("GPL");
+
+module_init(ibmpex_init);
+module_exit(ibmpex_exit);
Hi Darrick,
On Tue, 28 Aug 2007 09:44:40 -0700, Darrick J. Wong wrote:
> Update the hwmon sysfs interface documentation to include a specification
> for power meters.
>
> Signed-off-by: Darrick J. Wong <[email protected]>
> ---
>
> Documentation/hwmon/sysfs-interface | 15 +++++++++++++++
> 1 files changed, 15 insertions(+), 0 deletions(-)
>
> diff --git a/Documentation/hwmon/sysfs-interface b/Documentation/hwmon/sysfs-interface
> index b3a9e1b..da546ce 100644
> --- a/Documentation/hwmon/sysfs-interface
> +++ b/Documentation/hwmon/sysfs-interface
> @@ -304,6 +304,21 @@ curr[1-*]_input Current input value
> Unit: milliampere
> RO
>
> +*********
> +* Power *
> +*********
> +
> +power[1-*]_input Current power use
> + Unit: milliWatt
> + RO
> +
> +power[1-*]_max_input Historical maximum power use
> + Unit: milliWatt
> + RO
> +
> +power[1-*]_min_input Historical minimum power use
> + Unit: milliWatt
> + RO
>
I'm not sure if we want these "historical" files. We don't have them for
the other input types, and I believe that it's not the driver's job to
compute and export these values. If anyone cares about the history of
sensed values, that's something for a user-space application to
implement. This will also be much more flexible in user-space, as it
becomes possible to decide the exact time range to consider, to
remember at which time the peak occurred, etc.
--
Jean Delvare
Hi Darrick,
On Tue, 28 Aug 2007 09:28:51 -0700, Darrick J. Wong wrote:
> On Tue, Aug 28, 2007 at 01:19:42PM +0200, Jean Delvare wrote:
> > I see that the driver relies on IPMI. Can't it be merged with the
> > out-of-tree impisensors driver then? This would give that driver some
> > momentum so that it can finally be merged, and I would like to avoid
> > having two drivers if one is enough. Note though that I don't know
> > anything about IPMI so I might as well be totally wrong ;)
>
> The IBM PEx meters are accessible via custom IPMI commands, not the
> standard IPMI sensor interface. I considered modifying the ipmisensors
> driver, but reached the opinion that it would clutter that driver
> unnecessarily, particularly since there are a lot of systems with IPMI
> BMCs, but most of them will not have this interface.
OK, fine with me then.
> That said, what is standing in the way of ipmisensors being merged? I
> think I can shake out some free time to apply TLC.
I don't know exactly, check out the website for details:
http://bmcsensors-26.sourceforge.net/
It needs at least review and testing, but maybe there are a few more
steps before merging can be considered. Adding Yani (the author) to Cc,
please work with him if you're interested.
--
Jean Delvare
> It needs at least review and testing, but maybe there are a few more
> steps before merging can be considered. Adding Yani (the author) to Cc,
> please work with him if you're interested.
As a user, I can tell you that it completely fills my dmesg with this sort
ipmisensors: sensor 50 (type 1) reading 60
ipmisensors: Send 0x2d 0x30 0x0
ipmisensors: received message
ipmisensors: sensor 48 (type 1) reading 28
ipmisensors: Send 0x2d 0x1b 0x0
ipmisensors: received message
ipmisensors: sensor 27 (type 2) reading 34
ipmisensors: Send 0x2d 0x1a 0x0
ipmisensors: received message
ipmisensors: sensor 26 (type 2) reading 187
ipmisensors: Send 0x2d 0x18 0x0
That's the only problem I have. It would be great to see it merged.
Frank
On Wed, Aug 29, 2007 at 11:10:52AM +0200, Jean Delvare wrote:
> I'm not sure if we want these "historical" files. We don't have them for
> the other input types, and I believe that it's not the driver's job to
> compute and export these values. If anyone cares about the history of
In the case of ibmpex, it is the _hardware_ that computes the historical
data; the driver merely exports what it sees.
--D
Hi Darrick,
On Wed, 29 Aug 2007 07:50:03 -0700, Darrick J. Wong wrote:
> On Wed, Aug 29, 2007 at 11:10:52AM +0200, Jean Delvare wrote:
>
> > I'm not sure if we want these "historical" files. We don't have them for
> > the other input types, and I believe that it's not the driver's job to
> > compute and export these values. If anyone cares about the history of
>
> In the case of ibmpex, it is the _hardware_ that computes the historical
> data; the driver merely exports what it sees.
OK, that's a bit different then, but I'm still not sure that there is
much value in exporting these values in sysfs, in particular if there
is no way to reset them.
I am also not happy with the names you proposed: power1_max_input and
power1_min_input are somewhat confusing IMHO, I'd suggest
power1_input_highest and power1_input_lowest to make them clearly
different from the min and max limits we have for other sensor types.
If we have them at all, of course.
--
Jean Delvare
Hi!
> diff --git a/Documentation/hwmon/sysfs-interface b/Documentation/hwmon/sysfs-interface
> index b3a9e1b..da546ce 100644
> --- a/Documentation/hwmon/sysfs-interface
> +++ b/Documentation/hwmon/sysfs-interface
> @@ -304,6 +304,21 @@ curr[1-*]_input Current input value
> Unit: milliampere
> RO
>
> +*********
> +* Power *
> +*********
> +
> +power[1-*]_input Current power use
> + Unit: milliWatt
> + RO
> +
> +power[1-*]_max_input Historical maximum power use
> + Unit: milliWatt
> + RO
> +
> +power[1-*]_min_input Historical minimum power use
> + Unit: milliWatt
> + RO
Should we add power?_10sec_avg_input? IIRC thinkpads export that,
too.
Pavel
--
(english) http://www.livejournal.com/~pavelmachek
(cesky, pictures) http://atrey.karlin.mff.cuni.cz/~pavel/picture/horses/blog.html
On 9/1/07, Pavel Machek <[email protected]> wrote:
> > diff --git a/Documentation/hwmon/sysfs-interface b/Documentation/hwmon/sysfs-interface
> > index b3a9e1b..da546ce 100644
> > --- a/Documentation/hwmon/sysfs-interface
> > +++ b/Documentation/hwmon/sysfs-interface
> > @@ -304,6 +304,21 @@ curr[1-*]_input Current input value
> > Unit: milliampere
> > RO
> >
> > +*********
> > +* Power *
> > +*********
> > +
> > +power[1-*]_input Current power use
> > + Unit: milliWatt
> > + RO
> > +
> > +power[1-*]_max_input Historical maximum power use
> > + Unit: milliWatt
> > + RO
> > +
> > +power[1-*]_min_input Historical minimum power use
> > + Unit: milliWatt
> > + RO
>
> Should we add power?_10sec_avg_input? IIRC thinkpads export that,
> too.
Indeed, ThinkPads report both instantaneous power/current and a
rolling average (exponentially decaying, I think) over the last ~10
seconds. ACPI provides only the rolling average, and the out-of-tree
tp_smapi driver provides both.
More generally, linux/power_supply.h defines these attributes:
POWER_SUPPLY_PROP_VOLTAGE_NOW,
POWER_SUPPLY_PROP_VOLTAGE_AVG,
POWER_SUPPLY_PROP_CURRENT_NOW,
POWER_SUPPLY_PROP_CURRENT_AVG,
It would be nice if the power meter interface uses the same
conventions as the power supply interface, since the former is
essentially a subset of the latter. Userspace app that read power
meters via sysfs should work for power supplies too.
Shem
On Sat, 01 Sep 2007, Shem Multinymous wrote:
> > Should we add power?_10sec_avg_input? IIRC thinkpads export that,
> > too.
I'd say it is enough to know it is an average, you don't need to specify HOW
it is averaged.
But there is a real need to export both instantaneous and averaged values in
power supplies/power sources, so we should have a standard way to do so in
hwmon, and power_supply class should use it (or the opposite, it doesn't
matter, as long as everyone does it the same way).
> It would be nice if the power meter interface uses the same
> conventions as the power supply interface, since the former is
> essentially a subset of the latter. Userspace app that read power
> meters via sysfs should work for power supplies too.
Agreed.
--
"One disk to rule them all, One disk to find them. One disk to bring
them all and in the darkness grind them. In the Land of Redmond
where the shadows lie." -- The Silicon Valley Tarot
Henrique Holschuh
On Sat, 1 Sep 2007 16:44:17 -0300, Henrique de Moraes Holschuh wrote:
> On Sat, 01 Sep 2007, Shem Multinymous wrote:
> > > Should we add power?_10sec_avg_input? IIRC thinkpads export that,
> > > too.
>
> I'd say it is enough to know it is an average, you don't need to specify HOW
> it is averaged.
Agreed.
> But there is a real need to export both instantaneous and averaged values in
> power supplies/power sources, so we should have a standard way to do so in
> hwmon, and power_supply class should use it (or the opposite, it doesn't
> matter, as long as everyone does it the same way).
I guess power[1-*]_average would be OK?
--
Jean Delvare
On Sun, 02 Sep 2007, Jean Delvare wrote:
> On Sat, 1 Sep 2007 16:44:17 -0300, Henrique de Moraes Holschuh wrote:
> > On Sat, 01 Sep 2007, Shem Multinymous wrote:
> > > > Should we add power?_10sec_avg_input? IIRC thinkpads export that,
> > > > too.
> >
> > I'd say it is enough to know it is an average, you don't need to specify HOW
> > it is averaged.
>
> Agreed.
>
> > But there is a real need to export both instantaneous and averaged values in
> > power supplies/power sources, so we should have a standard way to do so in
> > hwmon, and power_supply class should use it (or the opposite, it doesn't
> > matter, as long as everyone does it the same way).
>
> I guess power[1-*]_average would be OK?
AFAIK, yes. It is probably not 100% in sync with the power supply class,
though.
--
"One disk to rule them all, One disk to find them. One disk to bring
them all and in the darkness grind them. In the Land of Redmond
where the shadows lie." -- The Silicon Valley Tarot
Henrique Holschuh
On Sun, 2 Sep 2007 23:02:01 -0300, Henrique de Moraes Holschuh wrote:
> On Sun, 02 Sep 2007, Jean Delvare wrote:
> > I guess power[1-*]_average would be OK?
>
> AFAIK, yes. It is probably not 100% in sync with the power supply class,
> though.
Is the power supply class creating sysfs files? I see a number of
attributes listed in Documentation/power_supply_class.txt, but they are
all uppercase, which doesn't seem suitable for sysfs file names. That
document also doesn't list the numbering convention when multiple
channels are present.
My main worry is that we will have to add support for power measurement
in libsensors, and I would like it to be as easy as possible. Thus
sticking to the same naming convention hwmon have been using for years
appears to be the best solution.
I see that the power supply class units are 10^-6 A and 10^-6 W, so if
we are supposed to be compatible, I guess we'll have to use this too.
--
Jean Delvare
On Mon, 03 Sep 2007, Jean Delvare wrote:
> On Sun, 2 Sep 2007 23:02:01 -0300, Henrique de Moraes Holschuh wrote:
> > On Sun, 02 Sep 2007, Jean Delvare wrote:
> > > I guess power[1-*]_average would be OK?
> >
> > AFAIK, yes. It is probably not 100% in sync with the power supply class,
> > though.
>
> Is the power supply class creating sysfs files? I see a number of
> attributes listed in Documentation/power_supply_class.txt, but they are
> all uppercase, which doesn't seem suitable for sysfs file names. That
> document also doesn't list the numbering convention when multiple
> channels are present.
Looks like power supply needs some tweaks, then.
> My main worry is that we will have to add support for power measurement
> in libsensors, and I would like it to be as easy as possible. Thus
> sticking to the same naming convention hwmon have been using for years
> appears to be the best solution.
Agreed.
> I see that the power supply class units are 10^-6 A and 10^-6 W, so if
> we are supposed to be compatible, I guess we'll have to use this too.
There was a good reason for that, and people who deal with small portables
said that they needed 10^-4 A or somesuch, at which point it makes more
sense to just go to 10^-6 already. I don't recall why 10^-6 W, though.
--
"One disk to rule them all, One disk to find them. One disk to bring
them all and in the darkness grind them. In the Land of Redmond
where the shadows lie." -- The Silicon Valley Tarot
Henrique Holschuh
On Mon, 3 Sep 2007 20:22:25 -0300, Henrique de Moraes Holschuh wrote:
> On Mon, 03 Sep 2007, Jean Delvare wrote:
> > I see that the power supply class units are 10^-6 A and 10^-6 W, so if
> > we are supposed to be compatible, I guess we'll have to use this too.
>
> There was a good reason for that, and people who deal with small portables
> said that they needed 10^-4 A or somesuch, at which point it makes more
> sense to just go to 10^-6 already. I don't recall why 10^-6 W, though.
Well, if we need 10^-4 A, and we use voltages in the 1-2 V range
(that's what CPU, AGP etc. use these days), then we obviously need
10^-4 W as well.
--
Jean Delvare
On Thu, 06 Sep 2007, Jean Delvare wrote:
> On Mon, 3 Sep 2007 20:22:25 -0300, Henrique de Moraes Holschuh wrote:
> > On Mon, 03 Sep 2007, Jean Delvare wrote:
> > > I see that the power supply class units are 10^-6 A and 10^-6 W, so if
> > > we are supposed to be compatible, I guess we'll have to use this too.
> >
> > There was a good reason for that, and people who deal with small portables
> > said that they needed 10^-4 A or somesuch, at which point it makes more
> > sense to just go to 10^-6 already. I don't recall why 10^-6 W, though.
>
> Well, if we need 10^-4 A, and we use voltages in the 1-2 V range
> (that's what CPU, AGP etc. use these days), then we obviously need
> 10^-4 W as well.
I see, and in that case it is safer to just go to 10^-6 for both. It is more
future-proof.
--
"One disk to rule them all, One disk to find them. One disk to bring
them all and in the darkness grind them. In the Land of Redmond
where the shadows lie." -- The Silicon Valley Tarot
Henrique Holschuh
Hi Darrick:
* Darrick J. Wong <[email protected]> [2007-08-28 16:25:05 -0700]:
> Dave Hansen complained about the magic numbers, repetitive code, and
> various other minor problems with the driver code, so here's a v3 with
> the magic numbers migrated to the top of the file and #define'd,
> helper macros taking place of the bit shifting/masking activities, and
> the compression of the value/min/max sysfs code into parameterized
> functions.
> --
> ibm_pex: Driver to export IBM PowerExecutive power meter sensors.
>
> Signed-off-by: Darrick J. Wong <[email protected]>
I am not an IPMI expert, so I would appreciate getting an Acked-by from
someone who knows more about that subsystem.
Anyway, some comments are below. This is nowhere near a complete review yet.
> ---
>
> drivers/hwmon/Kconfig | 12 +
> drivers/hwmon/Makefile | 1
> drivers/hwmon/ibmpex.c | 564 ++++++++++++++++++++++++++++++++++++++++++++++++
> 3 files changed, 577 insertions(+), 0 deletions(-)
>
> diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig
> index 555f470..41ffa2e 100644
> --- a/drivers/hwmon/Kconfig
> +++ b/drivers/hwmon/Kconfig
> @@ -275,6 +275,18 @@ config SENSORS_CORETEMP
> sensor inside your CPU. Supported all are all known variants
> of Intel Core family.
>
> +config SENSORS_IBMPEX
> + tristate "IBM PowerExecutive temperature/power sensors"
> + depends on IPMI_SI
Open question: can we use "select" here? As written, it took some hunting to
even get this driver to show up as an option in menuconfig.
> + help
> + If you say yes here you get support for the temperature and
> + power sensors in various IBM System X servers that support
> + PowerExecutive. So far this includes the x3550, x3650, x3655,
> + x3755, and certain HS20 blades.
> +
> + This driver can also be built as a module. If so, the module
> + will be called ibmpex.
> +
> config SENSORS_IT87
> tristate "ITE IT87xx and compatibles"
> select HWMON_VID
> diff --git a/drivers/hwmon/Makefile b/drivers/hwmon/Makefile
> index a133981..31da6fe 100644
> --- a/drivers/hwmon/Makefile
> +++ b/drivers/hwmon/Makefile
> @@ -35,6 +35,7 @@ obj-$(CONFIG_SENSORS_FSCPOS) += fscpos.o
> obj-$(CONFIG_SENSORS_GL518SM) += gl518sm.o
> obj-$(CONFIG_SENSORS_GL520SM) += gl520sm.o
> obj-$(CONFIG_SENSORS_HDAPS) += hdaps.o
> +obj-$(CONFIG_SENSORS_IBMPEX) += ibmpex.o
> obj-$(CONFIG_SENSORS_IT87) += it87.o
> obj-$(CONFIG_SENSORS_K8TEMP) += k8temp.o
> obj-$(CONFIG_SENSORS_LM63) += lm63.o
> diff --git a/drivers/hwmon/ibmpex.c b/drivers/hwmon/ibmpex.c
> new file mode 100644
> index 0000000..632f897
> --- /dev/null
> +++ b/drivers/hwmon/ibmpex.c
> @@ -0,0 +1,564 @@
> +/*
> + * A hwmon driver for the IBM PowerExecutive temperature/power sensors
> + * Copyright (C) 2007 IBM
> + *
> + * Author: Darrick J. Wong <[email protected]>
> + *
> + * 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>
> +
> +#define REFRESH_INTERVAL (5 * HZ)
> +#define DRVNAME "ibmpex"
> +
> +#define PEX_GET_VERSION 1
> +#define PEX_GET_SENSOR_COUNT 2
> +#define PEX_GET_SENSOR_NAME 3
> +#define PEX_GET_SENSOR_DATA 6
> +
> +#define PEX_NET_FUNCTION 0x3A
> +#define PEX_COMMAND 0x3C
> +
> +static inline u16 extract_value(const char *data, int offset)
> +{
> + u16 val = *(u16*)&data[offset];
> + return be16_to_cpu(val);
return be16_to_cpup((u16*)&data[offset]);
> +}
> +
> +#define PEX_INTERFACE(idx) ((idx) >> 16)
> +#define PEX_SENSOR(idx) (((idx) >> 8) & 0xFF)
> +#define PEX_FUNC(idx) ((idx) & 0xFF)
> +#define PEX_INDEX(iface, num, fn) (((iface) << 16) | ((num) << 8) | (fn))
> +
> +#define PEX_SENSOR_TYPE_LEN 3
> +static char power_sensor_sig[] = {0x70, 0x77, 0x72};
> +static char temp_sensor_sig[] = {0x74, 0x65, 0x6D};
> +
> +#define PEX_MULT_LEN 2
> +static char watt_sensor_sig[] = {0x41, 0x43};
> +
All three above should be "static u8 const"
> +#define PEX_NUM_SENSOR_FUNCS 3
> +static char *sensor_name_templates[] = {
> + "%s%d_input",
> + "%s%d_min_input",
> + "%s%d_max_input"
> +};
> +
static char const * const sensor_name_templates[] = {
> +static void ibmpex_msg_handler(struct ipmi_recv_msg *msg, void *user_msg_data);
> +static void ibmpex_register_bmc(int iface, struct device *dev);
> +static void ibmpex_bmc_gone(int iface);
> +
> +struct ibmpex_sensor_data {
> + int in_use;
> + s16 values[PEX_NUM_SENSOR_FUNCS];
> + int multiplier;
> +
> + struct sensor_device_attribute attr[PEX_NUM_SENSOR_FUNCS];
> +};
> +
> +struct ibmpex_bmc_data {
> + struct list_head list;
> + struct class_device *class_dev;
My current stack of patches includes one which requires that this be changed
to 'struct device *hwmon_dev', as 'struct class_device' is going away soon.
You may rebase on my testing tree[1], or else I will just follow up with a
patch to fix this up after I eventually merge yours.
[1] http://lm-sensors.org/kernel?p=kernel/mhoffman/hwmon-2.6.git;a=shortlog;h=testing
> + struct device *bmc_device;
> + struct mutex lock;
> + char valid;
> + unsigned long last_updated; /* In jiffies */
> +
> + struct ipmi_addr address;
> + struct completion read_complete;
> + ipmi_user_t user;
> + int interface;
> +
> + struct kernel_ipmi_msg tx_message;
> + unsigned char tx_msg_data[IPMI_MAX_MSG_LENGTH];
> + long tx_msgid;
> +
> + unsigned char rx_msg_data[IPMI_MAX_MSG_LENGTH];
> + unsigned long rx_msg_len;
> + unsigned char rx_result;
> + int rx_recv_type;
> +
> + unsigned char sensor_major;
> + unsigned char sensor_minor;
> +
> + unsigned char num_sensors;
> + struct ibmpex_sensor_data *sensors;
> +};
> +
> +struct ibmpex_driver_data {
> + struct list_head bmc_data;
> + struct ipmi_smi_watcher bmc_events;
> + struct ipmi_user_hndl ipmi_hndlrs;
> +};
> +
> +static struct ibmpex_driver_data driver_data = {
> + .bmc_data = LIST_HEAD_INIT(driver_data.bmc_data),
> + .bmc_events = {
> + .owner = THIS_MODULE,
> + .new_smi = ibmpex_register_bmc,
> + .smi_gone = ibmpex_bmc_gone,
> + },
> + .ipmi_hndlrs = {
> + .ipmi_recv_hndl = ibmpex_msg_handler,
> + },
> +};
> +
> +static int ibmpex_send_message(struct ibmpex_bmc_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:
> + printk(KERN_ERR "%s: request_settime=%x\n", __FUNCTION__, err);
> + return err;
> +out:
> + printk(KERN_ERR "%s: validate_addr=%x\n", __FUNCTION__, err);
> + return err;
> +}
> +
> +static int ibmpex_ver_check(struct ibmpex_bmc_data *data)
> +{
> + data->tx_msg_data[0] = PEX_GET_VERSION;
> + data->tx_message.data_len = 1;
> + ibmpex_send_message(data);
> +
> + wait_for_completion(&data->read_complete);
> +
> + if (data->rx_result || data->rx_msg_len != 6)
> + return -ENOENT;
> +
> + data->sensor_major = data->rx_msg_data[0];
> + data->sensor_minor = data->rx_msg_data[1];
> +
> + printk(KERN_INFO DRVNAME ": Found BMC with sensor interface "
> + "v%d.%d %d-%02d-%02d on interface %d\n",
> + data->sensor_major,
> + data->sensor_minor,
> + extract_value(data->rx_msg_data, 2),
> + data->rx_msg_data[4],
> + data->rx_msg_data[5],
> + data->interface);
> +
> + return 0;
> +}
> +
> +static int ibmpex_query_sensor_count(struct ibmpex_bmc_data *data)
> +{
> + data->tx_msg_data[0] = PEX_GET_SENSOR_COUNT;
> + data->tx_message.data_len = 1;
> + ibmpex_send_message(data);
> +
> + wait_for_completion(&data->read_complete);
> +
> + if (data->rx_result || data->rx_msg_len != 1)
> + return -ENOENT;
> +
> + return data->rx_msg_data[0];
> +}
> +
> +static int ibmpex_query_sensor_name(struct ibmpex_bmc_data *data, int sensor)
> +{
> + data->tx_msg_data[0] = PEX_GET_SENSOR_NAME;
> + data->tx_msg_data[1] = sensor;
> + data->tx_message.data_len = 2;
> + ibmpex_send_message(data);
> +
> + wait_for_completion(&data->read_complete);
> +
> + if (data->rx_result || data->rx_msg_len < 1)
> + return -ENOENT;
> +
> + return 0;
> +}
> +
> +static int ibmpex_query_sensor_data(struct ibmpex_bmc_data *data, int sensor)
> +{
> + data->tx_msg_data[0] = PEX_GET_SENSOR_DATA;
> + data->tx_msg_data[1] = sensor;
> + data->tx_message.data_len = 2;
> + ibmpex_send_message(data);
> +
> + wait_for_completion(&data->read_complete);
> +
> + if (data->rx_result || data->rx_msg_len < 26) {
> + printk(KERN_ERR "Error reading sensor %d, please check.\n",
> + sensor);
> + return -ENOENT;
> + }
> +
> + return 0;
> +}
> +
> +static void ibmpex_update_device(struct ibmpex_bmc_data *data)
> +{
> + int i, err;
> +
> + mutex_lock(&data->lock);
> + if (time_before(jiffies, data->last_updated + REFRESH_INTERVAL) &&
> + data->valid)
> + goto out;
> +
> + for (i = 0; i < data->num_sensors; i++) {
> + if (!data->sensors[i].in_use)
> + continue;
> + err = ibmpex_query_sensor_data(data, i);
> + if (err)
> + continue;
> + data->sensors[i].values[0] =
> + extract_value(data->rx_msg_data, 16);
> + data->sensors[i].values[1] =
> + extract_value(data->rx_msg_data, 18);
> + data->sensors[i].values[2] =
> + extract_value(data->rx_msg_data, 20);
> + }
> +
> + data->last_updated = jiffies;
> + data->valid = 1;
> +
> +out:
> + mutex_unlock(&data->lock);
> +}
> +
> +static struct ibmpex_bmc_data *get_bmc_data(int iface)
> +{
> + struct ibmpex_bmc_data *p, *next;
> +
> + list_for_each_entry_safe(p, next, &driver_data.bmc_data, list)
> + if (p->interface == iface)
> + return p;
> +
> + return NULL;
> +}
> +
That function is yucky...
> +static ssize_t show_name(struct device *dev, struct device_attribute *devattr,
> + char *buf)
> +{
> + return sprintf(buf, "%s\n", DRVNAME);
> +}
> +static SENSOR_DEVICE_ATTR(name, S_IRUGO, show_name, NULL, 0);
> +
> +static ssize_t ibmpex_show_sensor(struct device *dev,
> + struct device_attribute *devattr,
> + char *buf)
> +{
> + struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr);
> + int iface = PEX_INTERFACE(attr->index);
> + int sensor = PEX_SENSOR(attr->index);
> + int func = PEX_FUNC(attr->index);
> + struct ibmpex_bmc_data *data = get_bmc_data(iface);
... especially given how many times you're going to call it. Is there any
reason you can't use the driver_data field of struct device *dev for that?
E.g. i2c based hwmon drivers do this at some point during the probe:
i2c_set_clientdata(new_client, data);
(which becomes)
dev_set_drvdata(&new_client->dev, data);
If you could do that, then you no longer need 'iface' at all in the function
above... *that* may allow you to use the SENSOR_ATTR_2 mechanism from
hwmon-sysfs.h - much easier to read than the manual number packing for 'sensor'
and 'func'.
> + int multiplier = data->sensors[sensor].multiplier;
> + ibmpex_update_device(data);
> +
> + return sprintf(buf, "%d\n",
> + data->sensors[sensor].values[func] * multiplier);
> +}
> +
> +static int is_power_sensor(const char *sensor_id, int len)
> +{
> + if (len < PEX_SENSOR_TYPE_LEN)
> + return 0;
> +
> + if (!memcmp(sensor_id, power_sensor_sig, PEX_SENSOR_TYPE_LEN))
> + return 1;
> + return 0;
> +}
> +
> +static int is_temp_sensor(const char *sensor_id, int len)
> +{
> + if (len < PEX_SENSOR_TYPE_LEN)
> + return 0;
> +
> + if (!memcmp(sensor_id, temp_sensor_sig, PEX_SENSOR_TYPE_LEN))
> + return 1;
> + return 0;
> +}
> +
> +static int power_sensor_multiplier(const char *sensor_id, int len)
> +{
> + int i;
> +
> + for (i = PEX_SENSOR_TYPE_LEN; i < len - 1; i++)
> + if (!memcmp(&sensor_id[i], watt_sensor_sig, PEX_MULT_LEN))
> + return 1000;
> +
> + return 100;
> +}
> +
> +static int create_sensor(struct ibmpex_bmc_data *data, const char *type,
> + int counter, int sensor, int func)
> +{
> + int err;
> + char *n;
> +
> + n = kmalloc(32, GFP_KERNEL);
> + if (!n)
> + return -ENOMEM;
> +
> + sprintf(n, sensor_name_templates[func], type, counter);
> + data->sensors[sensor].attr[func].dev_attr.attr.name = n;
> + data->sensors[sensor].attr[func].dev_attr.attr.mode = S_IRUGO;
> + data->sensors[sensor].attr[func].dev_attr.show = ibmpex_show_sensor;
> + data->sensors[sensor].attr[func].index =
> + PEX_INDEX(data->interface, sensor, func);
> +
> + err = device_create_file(data->bmc_device,
> + &data->sensors[sensor].attr[func].dev_attr);
> + if (err) {
> + data->sensors[sensor].attr[func].dev_attr.attr.name = NULL;
> + kfree(n);
> + return err;
> + }
> +
> + return 0;
> +}
> +
> +static int ibmpex_find_sensors(struct ibmpex_bmc_data *data)
> +{
> + int i, j, err;
> + char *sensor_type;
> + int sensor_counter;
> + int num_power = 0;
> + int num_temp = 0;
> +
> + err = ibmpex_query_sensor_count(data);
> + if (err < 0)
> + return -ENOENT;
> + data->num_sensors = err;
> +
Did you mean 'if (err <= 0)' ?
> + data->sensors = kzalloc(data->num_sensors * sizeof(*data->sensors),
> + GFP_KERNEL);
> + if (!data->sensors)
> + return -ENOMEM;
> +
> + for (i = 0; i < data->num_sensors; i++) {
> + err = ibmpex_query_sensor_name(data, i);
> + if (err)
> + continue;
> +
> + if (is_power_sensor(data->rx_msg_data, data->rx_msg_len)) {
> + sensor_type = "power";
> + num_power++;
> + sensor_counter = num_power;
> + data->sensors[i].multiplier =
> + power_sensor_multiplier(data->rx_msg_data,
> + data->rx_msg_len);
> + } else if (is_temp_sensor(data->rx_msg_data,
> + data->rx_msg_len)) {
> + sensor_type = "temp";
> + num_temp++;
> + sensor_counter = num_temp;
> + data->sensors[i].multiplier = 1;
> + } else
> + continue;
> +
> + data->sensors[i].in_use = 1;
> +
> + /* Create attributes */
> + for (j = 0; j < PEX_NUM_SENSOR_FUNCS; j++)
> + if (create_sensor(data, sensor_type, sensor_counter,
> + i, j))
Why not 'err = create_sensor(...)' and propagate the actual error here?
> + goto exit_remove;
> + }
> +
> + err = device_create_file(data->bmc_device,
> + &sensor_dev_attr_name.dev_attr);
> + if (err)
> + goto exit_remove;
> +
> + return 0;
> +
> +exit_remove:
> + for (i = 0; i < data->num_sensors; i++)
> + for (j = 0; j < PEX_NUM_SENSOR_FUNCS; j++) {
> + if (!data->sensors[i].attr[j].dev_attr.attr.name)
> + continue;
> + device_remove_file(data->bmc_device,
> + &data->sensors[i].attr[j].dev_attr);
> + kfree(data->sensors[i].attr[j].dev_attr.attr.name);
> + }
> +
> + kfree(data->sensors);
> + return -ENOENT;
... and 'return err' here instead.
> +}
> +
> +static void ibmpex_register_bmc(int iface, struct device *dev)
> +{
> + struct ibmpex_bmc_data *data;
> + int err;
> +
> + data = kzalloc(sizeof(*data), GFP_KERNEL);
> + if (!data) {
> + printk(KERN_ERR DRVNAME ": Insufficient memory for BMC "
> + "interface %d.\n", data->interface);
> + return;
> + }
> +
> + data->address.addr_type = IPMI_SYSTEM_INTERFACE_ADDR_TYPE;
> + data->address.channel = IPMI_BMC_CHANNEL;
> + data->address.data[0] = 0;
> + data->interface = iface;
> + data->bmc_device = dev;
> +
> + /* Create IPMI messaging interface user */
> + err = ipmi_create_user(data->interface, &driver_data.ipmi_hndlrs,
> + data, &data->user);
> + if (err < 0) {
> + printk(KERN_ERR DRVNAME ": Error, unable to register user with "
> + "ipmi interface %d\n",
> + data->interface);
> + goto out;
> + }
> +
> + mutex_init(&data->lock);
> +
> + /* Initialize message */
> + data->tx_msgid = 0;
> + init_completion(&data->read_complete);
> + data->tx_message.netfn = PEX_NET_FUNCTION;
> + data->tx_message.cmd = PEX_COMMAND;
> + data->tx_message.data = data->tx_msg_data;
> +
> + /* Does this BMC support PowerExecutive? */
> + err = ibmpex_ver_check(data);
> + if (err)
> + goto out_user;
> +
> + /* Register the BMC as a HWMON class device */
> + data->class_dev = hwmon_device_register(data->bmc_device);
> +
> + if (IS_ERR(data->class_dev)) {
> + printk(KERN_ERR DRVNAME ": Error, unable to register hwmon "
> + "class device for interface %d\n",
> + data->interface);
> + kfree(data);
> + return;
> + }
> +
> + /* finally add the new bmc data to the bmc data list */
> + list_add_tail(&data->list, &driver_data.bmc_data);
> +
> + /* Now go find all the sensors */
> + err = ibmpex_find_sensors(data);
> + if (err) {
> + printk(KERN_ERR "Error %d allocating memory\n", err);
> + goto out_register;
> + }
> +
> + return;
> +
> +out_register:
> + hwmon_device_unregister(data->class_dev);
> +out_user:
> + ipmi_destroy_user(data->user);
> +out:
> + kfree(data);
> +}
> +
> +static void ibmpex_bmc_delete(struct ibmpex_bmc_data *data)
> +{
> + int i, j;
> +
> + device_remove_file(data->bmc_device, &sensor_dev_attr_name.dev_attr);
> + for (i = 0; i < data->num_sensors; i++)
> + for (j = 0; j < PEX_NUM_SENSOR_FUNCS; j++) {
> + if (!data->sensors[i].attr[j].dev_attr.attr.name)
> + continue;
> + device_remove_file(data->bmc_device,
> + &data->sensors[i].attr[j].dev_attr);
> + kfree(data->sensors[i].attr[j].dev_attr.attr.name);
> + }
> +
> + list_del(&data->list);
> + hwmon_device_unregister(data->class_dev);
> + ipmi_destroy_user(data->user);
> + if (data->sensors)
> + kfree(data->sensors);
> + kfree(data);
> +}
> +
> +static void ibmpex_bmc_gone(int iface)
> +{
> + struct ibmpex_bmc_data *data = get_bmc_data(iface);
> +
> + if (!data)
> + return;
> +
> + ibmpex_bmc_delete(data);
> +}
> +
> +static void ibmpex_msg_handler(struct ipmi_recv_msg *msg, void *user_msg_data)
> +{
> + struct ibmpex_bmc_data *data = (struct ibmpex_bmc_data *)user_msg_data;
> +
> + if (msg->msgid != data->tx_msgid) {
> + printk(KERN_ERR "Received msgid (%02x) and transmitted "
> + "msgid (%02x) mismatch!\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) {
> + data->rx_msg_len = msg->msg.data_len - 1;
> + 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);
> +}
> +
> +static int __init ibmpex_init(void)
> +{
> + return ipmi_smi_watcher_register(&driver_data.bmc_events);
> +}
> +
> +static void __exit ibmpex_exit(void)
> +{
> + struct ibmpex_bmc_data *p, *next;
> +
> + ipmi_smi_watcher_unregister(&driver_data.bmc_events);
> + list_for_each_entry_safe(p, next, &driver_data.bmc_data, list)
> + ibmpex_bmc_delete(p);
> +}
> +
> +MODULE_AUTHOR("Darrick J. Wong <[email protected]>");
> +MODULE_DESCRIPTION("IBM PowerExecutive power/temperature sensor driver");
> +MODULE_LICENSE("GPL");
> +
> +module_init(ibmpex_init);
> +module_exit(ibmpex_exit);
Regards,
--
Mark M. Hoffman
[email protected]
On 9/11/2007, "Mark M. Hoffman" <[email protected]> wrote:
>* Darrick J. Wong <[email protected]> [2007-08-28 16:25:05 -0700]:
>> --- a/drivers/hwmon/Kconfig
>> +++ b/drivers/hwmon/Kconfig
>> @@ -275,6 +275,18 @@ config SENSORS_CORETEMP
>> sensor inside your CPU. Supported all are all known variants
>> of Intel Core family.
>>
>> +config SENSORS_IBMPEX
>> + tristate "IBM PowerExecutive temperature/power sensors"
>> + depends on IPMI_SI
>
> Open question: can we use "select" here? As written, it took some hunting
> to even get this driver to show up as an option in menuconfig.
My 2 cents: I assume that people who have IPMI-capable hardware are aware
that they do. Thus, if you change the above to "select IPMI_SI", you
probably want to add "depends on IPMI_HANDLER" (not sure why it's not
just named CONFIG_IPMI).
--
Jean Delvare
On Thu, Aug 30, 2007 at 11:57:43AM +0200, Jean Delvare wrote:
> OK, that's a bit different then, but I'm still not sure that there is
> much value in exporting these values in sysfs, in particular if there
> is no way to reset them.
It turns out there _is_ a way to reset them; the next iteration of the
driver will have it.
> I am also not happy with the names you proposed: power1_max_input and
> power1_min_input are somewhat confusing IMHO, I'd suggest
> power1_input_highest and power1_input_lowest to make them clearly
> different from the min and max limits we have for other sensor types.
> If we have them at all, of course.
Agreed and changed.
--D
On Tue, Sep 11, 2007 at 09:23:35AM -0400, Mark M. Hoffman wrote:
> I am not an IPMI expert, so I would appreciate getting an Acked-by from
> someone who knows more about that subsystem.
>
> Anyway, some comments are below. This is nowhere near a complete review yet.
Thank you for the review! Comments interspersed below, though for
brevity the one-liners have been fixed.
> > +config SENSORS_IBMPEX
> > + tristate "IBM PowerExecutive temperature/power sensors"
> > + depends on IPMI_SI
>
> Open question: can we use "select" here? As written, it took some hunting to
> even get this driver to show up as an option in menuconfig.
Changed, since it seems reasonable that someone looking for PEx support
might not necessarily know that it is based upon IPMI.
> > +struct ibmpex_bmc_data {
> > + struct list_head list;
> > + struct class_device *class_dev;
>
> My current stack of patches includes one which requires that this be changed
> to 'struct device *hwmon_dev', as 'struct class_device' is going away soon.
> You may rebase on my testing tree[1], or else I will just follow up with a
> patch to fix this up after I eventually merge yours.
>
> [1] http://lm-sensors.org/kernel?p=kernel/mhoffman/hwmon-2.6.git;a=shortlog;h=testing
Done.
> > +static ssize_t ibmpex_show_sensor(struct device *dev,
> > + struct device_attribute *devattr,
> > + char *buf)
> > +{
> > + struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr);
> > + int iface = PEX_INTERFACE(attr->index);
> > + int sensor = PEX_SENSOR(attr->index);
> > + int func = PEX_FUNC(attr->index);
> > + struct ibmpex_bmc_data *data = get_bmc_data(iface);
>
> ... especially given how many times you're going to call it. Is there any
> reason you can't use the driver_data field of struct device *dev for that?
I can (and did) update the code to use dev_get/set_drvdata for the
accessors. However, the "iface" field exists as a mechanism to map
interface numbers to struct ibmpex_bmc_data/struct device data because
the callback that IPMI uses to notify clients that BMCs are going away
only passes the interface number, not the struct device itself.
Unfortunately, this means that get_bmc_data() must remain, but now it is
only used once at the end of life.
> E.g. i2c based hwmon drivers do this at some point during the probe:
>
> i2c_set_clientdata(new_client, data);
>
> (which becomes)
>
> dev_set_drvdata(&new_client->dev, data);
>
> If you could do that, then you no longer need 'iface' at all in the function
> above... *that* may allow you to use the SENSOR_ATTR_2 mechanism from
> hwmon-sysfs.h - much easier to read than the manual number packing for 'sensor'
> and 'func'.
Doesn't look too hard; I'll have a go at it and see how it does.
> > + err = ibmpex_query_sensor_count(data);
> > + if (err < 0)
> > + return -ENOENT;
> > + data->num_sensors = err;
> > +
>
> Did you mean 'if (err <= 0)' ?
Yes.
> > + /* Create attributes */
> > + for (j = 0; j < PEX_NUM_SENSOR_FUNCS; j++)
> > + if (create_sensor(data, sensor_type, sensor_counter,
> > + i, j))
>
> Why not 'err = create_sensor(...)' and propagate the actual error here?
Rough draft syndrome? 'tis fixed. :)
--D
Update the hwmon sysfs interface documentation to include a specification
for power meters.
Signed-off-by: Darrick J. Wong <[email protected]>
---
Documentation/hwmon/sysfs-interface | 30 ++++++++++++++++++++++++++++++
1 files changed, 30 insertions(+), 0 deletions(-)
diff --git a/Documentation/hwmon/sysfs-interface b/Documentation/hwmon/sysfs-interface
index db7bb4a..5c98bee 100644
--- a/Documentation/hwmon/sysfs-interface
+++ b/Documentation/hwmon/sysfs-interface
@@ -324,6 +324,36 @@ curr[1-*]_input Current input value
Unit: milliampere
RO
+*********
+* Power *
+*********
+
+power[1-*]_average Average power use
+ Unit: microWatt
+ RO
+
+power[1-*]_average_highest Historical average maximum power use
+ Unit: microWatt
+ RO
+
+power[1-*]_average_lowest Historical average minimum power use
+ Unit: microWatt
+ RO
+
+power[1-*]_input Instantaneous power use
+ Unit: microWatt
+ RO
+
+power[1-*]_input_highest Historical maximum power use
+ Unit: microWatt
+ RO
+
+power[1-*]_input_lowest Historical minimum power use
+ Unit: microWatt
+ RO
+
+power[1-*]_high_low_reset Reset input_highest/input_lowest.
+ WO
**********
* Alarms *
Here's a fourth revision where I've tried to clean up the things that
people complained about, as well as shifted the sysfs file names to
match the spec that we've been drifting towards.
---
ibm_pex: Driver to export IBM PowerExecutive power meter sensors.
Signed-off-by: Darrick J. Wong <[email protected]>
---
drivers/hwmon/Kconfig | 13 +
drivers/hwmon/Makefile | 1
drivers/hwmon/ibmpex.c | 608 ++++++++++++++++++++++++++++++++++++++++++++++++
3 files changed, 622 insertions(+), 0 deletions(-)
diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig
index 5ca5d95..591b666 100644
--- a/drivers/hwmon/Kconfig
+++ b/drivers/hwmon/Kconfig
@@ -315,6 +315,19 @@ config SENSORS_CORETEMP
sensor inside your CPU. Supported all are all known variants
of Intel Core family.
+config SENSORS_IBMPEX
+ tristate "IBM PowerExecutive temperature/power sensors"
+ select IPMI_SI
+ depends on IPMI_HANDLER
+ help
+ If you say yes here you get support for the temperature and
+ power sensors in various IBM System X servers that support
+ PowerExecutive. So far this includes the x3550, x3650, x3655,
+ x3755, and certain HS20 blades.
+
+ This driver can also be built as a module. If so, the module
+ will be called ibmpex.
+
config SENSORS_IT87
tristate "ITE IT87xx and compatibles"
select HWMON_VID
diff --git a/drivers/hwmon/Makefile b/drivers/hwmon/Makefile
index 5070cf7..0fcbcb4 100644
--- a/drivers/hwmon/Makefile
+++ b/drivers/hwmon/Makefile
@@ -38,6 +38,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_IBMPEX) += ibmpex.o
obj-$(CONFIG_SENSORS_IPMI) += ipmisensors.o
obj-$(CONFIG_SENSORS_IT87) += it87.o
obj-$(CONFIG_SENSORS_K8TEMP) += k8temp.o
diff --git a/drivers/hwmon/ibmpex.c b/drivers/hwmon/ibmpex.c
new file mode 100644
index 0000000..fe2c261
--- /dev/null
+++ b/drivers/hwmon/ibmpex.c
@@ -0,0 +1,608 @@
+/*
+ * A hwmon driver for the IBM PowerExecutive temperature/power sensors
+ * Copyright (C) 2007 IBM
+ *
+ * Author: Darrick J. Wong <[email protected]>
+ *
+ * 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>
+
+#define REFRESH_INTERVAL (2 * HZ)
+#define DRVNAME "ibmpex"
+
+#define PEX_GET_VERSION 1
+#define PEX_GET_SENSOR_COUNT 2
+#define PEX_GET_SENSOR_NAME 3
+#define PEX_RESET_HIGH_LOW 4
+#define PEX_GET_SENSOR_DATA 6
+
+#define PEX_NET_FUNCTION 0x3A
+#define PEX_COMMAND 0x3C
+
+static inline u16 extract_value(const char *data, int offset)
+{
+ return be16_to_cpup((u16 *)&data[offset]);
+}
+
+#define TEMP_SENSOR 1
+#define POWER_SENSOR 2
+
+#define PEX_SENSOR_TYPE_LEN 3
+static u8 const power_sensor_sig[] = {0x70, 0x77, 0x72};
+static u8 const temp_sensor_sig[] = {0x74, 0x65, 0x6D};
+
+#define PEX_MULT_LEN 2
+static u8 const watt_sensor_sig[] = {0x41, 0x43};
+
+#define PEX_NUM_SENSOR_FUNCS 3
+static char const * const power_sensor_name_templates[] = {
+ "%s%d_average",
+ "%s%d_average_lowest",
+ "%s%d_average_highest"
+};
+static char const * const temp_sensor_name_templates[] = {
+ "%s%d_input",
+ "%s%d_input_lowest",
+ "%s%d_input_highest"
+};
+
+static void ibmpex_msg_handler(struct ipmi_recv_msg *msg, void *user_msg_data);
+static void ibmpex_register_bmc(int iface, struct device *dev);
+static void ibmpex_bmc_gone(int iface);
+
+struct ibmpex_sensor_data {
+ int in_use;
+ s16 values[PEX_NUM_SENSOR_FUNCS];
+ int multiplier;
+
+ struct sensor_device_attribute_2 attr[PEX_NUM_SENSOR_FUNCS];
+};
+
+struct ibmpex_bmc_data {
+ struct list_head list;
+ struct device *hwmon_dev;
+ struct device *bmc_device;
+ struct mutex lock;
+ char valid;
+ unsigned long last_updated; /* In jiffies */
+
+ struct ipmi_addr address;
+ struct completion read_complete;
+ ipmi_user_t user;
+ int interface;
+
+ struct kernel_ipmi_msg tx_message;
+ unsigned char tx_msg_data[IPMI_MAX_MSG_LENGTH];
+ long tx_msgid;
+
+ unsigned char rx_msg_data[IPMI_MAX_MSG_LENGTH];
+ unsigned long rx_msg_len;
+ unsigned char rx_result;
+ int rx_recv_type;
+
+ unsigned char sensor_major;
+ unsigned char sensor_minor;
+
+ unsigned char num_sensors;
+ struct ibmpex_sensor_data *sensors;
+};
+
+struct ibmpex_driver_data {
+ struct list_head bmc_data;
+ struct ipmi_smi_watcher bmc_events;
+ struct ipmi_user_hndl ipmi_hndlrs;
+};
+
+static struct ibmpex_driver_data driver_data = {
+ .bmc_data = LIST_HEAD_INIT(driver_data.bmc_data),
+ .bmc_events = {
+ .owner = THIS_MODULE,
+ .new_smi = ibmpex_register_bmc,
+ .smi_gone = ibmpex_bmc_gone,
+ },
+ .ipmi_hndlrs = {
+ .ipmi_recv_hndl = ibmpex_msg_handler,
+ },
+};
+
+static int ibmpex_send_message(struct ibmpex_bmc_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:
+ printk(KERN_ERR "%s: request_settime=%x\n", __FUNCTION__, err);
+ return err;
+out:
+ printk(KERN_ERR "%s: validate_addr=%x\n", __FUNCTION__, err);
+ return err;
+}
+
+static int ibmpex_ver_check(struct ibmpex_bmc_data *data)
+{
+ data->tx_msg_data[0] = PEX_GET_VERSION;
+ data->tx_message.data_len = 1;
+ ibmpex_send_message(data);
+
+ wait_for_completion(&data->read_complete);
+
+ if (data->rx_result || data->rx_msg_len != 6)
+ return -ENOENT;
+
+ data->sensor_major = data->rx_msg_data[0];
+ data->sensor_minor = data->rx_msg_data[1];
+
+ printk(KERN_INFO DRVNAME ": Found BMC with sensor interface "
+ "v%d.%d %d-%02d-%02d on interface %d\n",
+ data->sensor_major,
+ data->sensor_minor,
+ extract_value(data->rx_msg_data, 2),
+ data->rx_msg_data[4],
+ data->rx_msg_data[5],
+ data->interface);
+
+ return 0;
+}
+
+static int ibmpex_query_sensor_count(struct ibmpex_bmc_data *data)
+{
+ data->tx_msg_data[0] = PEX_GET_SENSOR_COUNT;
+ data->tx_message.data_len = 1;
+ ibmpex_send_message(data);
+
+ wait_for_completion(&data->read_complete);
+
+ if (data->rx_result || data->rx_msg_len != 1)
+ return -ENOENT;
+
+ return data->rx_msg_data[0];
+}
+
+static int ibmpex_query_sensor_name(struct ibmpex_bmc_data *data, int sensor)
+{
+ data->tx_msg_data[0] = PEX_GET_SENSOR_NAME;
+ data->tx_msg_data[1] = sensor;
+ data->tx_message.data_len = 2;
+ ibmpex_send_message(data);
+
+ wait_for_completion(&data->read_complete);
+
+ if (data->rx_result || data->rx_msg_len < 1)
+ return -ENOENT;
+
+ return 0;
+}
+
+static int ibmpex_query_sensor_data(struct ibmpex_bmc_data *data, int sensor)
+{
+ data->tx_msg_data[0] = PEX_GET_SENSOR_DATA;
+ data->tx_msg_data[1] = sensor;
+ data->tx_message.data_len = 2;
+ ibmpex_send_message(data);
+
+ wait_for_completion(&data->read_complete);
+
+ if (data->rx_result || data->rx_msg_len < 26) {
+ printk(KERN_ERR "Error reading sensor %d, please check.\n",
+ sensor);
+ return -ENOENT;
+ }
+
+ return 0;
+}
+
+static int ibmpex_reset_high_low_data(struct ibmpex_bmc_data *data)
+{
+ data->tx_msg_data[0] = PEX_RESET_HIGH_LOW;
+ data->tx_message.data_len = 1;
+ ibmpex_send_message(data);
+
+ wait_for_completion(&data->read_complete);
+
+ return 0;
+}
+
+static void ibmpex_update_device(struct ibmpex_bmc_data *data)
+{
+ int i, err;
+
+ mutex_lock(&data->lock);
+ if (time_before(jiffies, data->last_updated + REFRESH_INTERVAL) &&
+ data->valid)
+ goto out;
+
+ for (i = 0; i < data->num_sensors; i++) {
+ if (!data->sensors[i].in_use)
+ continue;
+ err = ibmpex_query_sensor_data(data, i);
+ if (err)
+ continue;
+ data->sensors[i].values[0] =
+ extract_value(data->rx_msg_data, 16);
+ data->sensors[i].values[1] =
+ extract_value(data->rx_msg_data, 18);
+ data->sensors[i].values[2] =
+ extract_value(data->rx_msg_data, 20);
+ }
+
+ data->last_updated = jiffies;
+ data->valid = 1;
+
+out:
+ mutex_unlock(&data->lock);
+}
+
+static struct ibmpex_bmc_data *get_bmc_data(int iface)
+{
+ struct ibmpex_bmc_data *p, *next;
+
+ list_for_each_entry_safe(p, next, &driver_data.bmc_data, list)
+ if (p->interface == iface)
+ return p;
+
+ return NULL;
+}
+
+static ssize_t show_name(struct device *dev, struct device_attribute *devattr,
+ char *buf)
+{
+ return sprintf(buf, "%s\n", DRVNAME);
+}
+static SENSOR_DEVICE_ATTR(name, S_IRUGO, show_name, NULL, 0);
+
+static ssize_t ibmpex_show_sensor(struct device *dev,
+ struct device_attribute *devattr,
+ char *buf)
+{
+ struct sensor_device_attribute_2 *attr = to_sensor_dev_attr_2(devattr);
+ struct ibmpex_bmc_data *data = dev_get_drvdata(dev);
+ int mult = data->sensors[attr->index].multiplier;
+ ibmpex_update_device(data);
+
+ return sprintf(buf, "%d\n",
+ data->sensors[attr->index].values[attr->nr] * mult);
+}
+
+static ssize_t ibmpex_reset_high_low(struct device *dev,
+ struct device_attribute *devattr,
+ const char *buf,
+ size_t count)
+{
+ struct ibmpex_bmc_data *data = dev_get_drvdata(dev);
+
+ ibmpex_reset_high_low_data(data);
+
+ return count;
+}
+
+static SENSOR_DEVICE_ATTR(reset_high_low, S_IWUSR, NULL,
+ ibmpex_reset_high_low, 0);
+
+static int is_power_sensor(const char *sensor_id, int len)
+{
+ if (len < PEX_SENSOR_TYPE_LEN)
+ return 0;
+
+ if (!memcmp(sensor_id, power_sensor_sig, PEX_SENSOR_TYPE_LEN))
+ return 1;
+ return 0;
+}
+
+static int is_temp_sensor(const char *sensor_id, int len)
+{
+ if (len < PEX_SENSOR_TYPE_LEN)
+ return 0;
+
+ if (!memcmp(sensor_id, temp_sensor_sig, PEX_SENSOR_TYPE_LEN))
+ return 1;
+ return 0;
+}
+
+static int power_sensor_multiplier(const char *sensor_id, int len)
+{
+ int i;
+
+ for (i = PEX_SENSOR_TYPE_LEN; i < len - 1; i++)
+ if (!memcmp(&sensor_id[i], watt_sensor_sig, PEX_MULT_LEN))
+ return 1000000;
+
+ return 100000;
+}
+
+static int create_sensor(struct ibmpex_bmc_data *data, int type,
+ int counter, int sensor, int func)
+{
+ int err;
+ char *n;
+
+ n = kmalloc(32, GFP_KERNEL);
+ if (!n)
+ return -ENOMEM;
+
+ if (type == TEMP_SENSOR)
+ sprintf(n, temp_sensor_name_templates[func], "temp", counter);
+ else if (type == POWER_SENSOR)
+ sprintf(n, power_sensor_name_templates[func], "power", counter);
+
+ data->sensors[sensor].attr[func].dev_attr.attr.name = n;
+ data->sensors[sensor].attr[func].dev_attr.attr.mode = S_IRUGO;
+ data->sensors[sensor].attr[func].dev_attr.show = ibmpex_show_sensor;
+ data->sensors[sensor].attr[func].index = sensor;
+ data->sensors[sensor].attr[func].nr = func;
+
+ err = device_create_file(data->bmc_device,
+ &data->sensors[sensor].attr[func].dev_attr);
+ if (err) {
+ data->sensors[sensor].attr[func].dev_attr.attr.name = NULL;
+ kfree(n);
+ return err;
+ }
+
+ return 0;
+}
+
+static int ibmpex_find_sensors(struct ibmpex_bmc_data *data)
+{
+ int i, j, err;
+ int sensor_type;
+ int sensor_counter;
+ int num_power = 0;
+ int num_temp = 0;
+
+ err = ibmpex_query_sensor_count(data);
+ if (err <= 0)
+ return -ENOENT;
+ data->num_sensors = err;
+
+ data->sensors = kzalloc(data->num_sensors * sizeof(*data->sensors),
+ GFP_KERNEL);
+ if (!data->sensors)
+ return -ENOMEM;
+
+ for (i = 0; i < data->num_sensors; i++) {
+ err = ibmpex_query_sensor_name(data, i);
+ if (err)
+ continue;
+
+ if (is_power_sensor(data->rx_msg_data, data->rx_msg_len)) {
+ sensor_type = POWER_SENSOR;
+ num_power++;
+ sensor_counter = num_power;
+ data->sensors[i].multiplier =
+ power_sensor_multiplier(data->rx_msg_data,
+ data->rx_msg_len);
+ } else if (is_temp_sensor(data->rx_msg_data,
+ data->rx_msg_len)) {
+ sensor_type = TEMP_SENSOR;
+ num_temp++;
+ sensor_counter = num_temp;
+ data->sensors[i].multiplier = 1;
+ } else
+ continue;
+
+ data->sensors[i].in_use = 1;
+
+ /* Create attributes */
+ for (j = 0; j < PEX_NUM_SENSOR_FUNCS; j++) {
+ err = create_sensor(data, sensor_type, sensor_counter,
+ i, j);
+ if (err)
+ goto exit_remove;
+ }
+ }
+
+ err = device_create_file(data->bmc_device,
+ &sensor_dev_attr_reset_high_low.dev_attr);
+ if (err)
+ goto exit_remove;
+
+ err = device_create_file(data->bmc_device,
+ &sensor_dev_attr_name.dev_attr);
+ if (err)
+ goto exit_remove;
+
+ return 0;
+
+exit_remove:
+ device_remove_file(data->bmc_device,
+ &sensor_dev_attr_reset_high_low.dev_attr);
+ device_remove_file(data->bmc_device, &sensor_dev_attr_name.dev_attr);
+ for (i = 0; i < data->num_sensors; i++)
+ for (j = 0; j < PEX_NUM_SENSOR_FUNCS; j++) {
+ if (!data->sensors[i].attr[j].dev_attr.attr.name)
+ continue;
+ device_remove_file(data->bmc_device,
+ &data->sensors[i].attr[j].dev_attr);
+ kfree(data->sensors[i].attr[j].dev_attr.attr.name);
+ }
+
+ kfree(data->sensors);
+ return err;
+}
+
+static void ibmpex_register_bmc(int iface, struct device *dev)
+{
+ struct ibmpex_bmc_data *data;
+ int err;
+
+ data = kzalloc(sizeof(*data), GFP_KERNEL);
+ if (!data) {
+ printk(KERN_ERR DRVNAME ": Insufficient memory for BMC "
+ "interface %d.\n", data->interface);
+ return;
+ }
+
+ data->address.addr_type = IPMI_SYSTEM_INTERFACE_ADDR_TYPE;
+ data->address.channel = IPMI_BMC_CHANNEL;
+ data->address.data[0] = 0;
+ data->interface = iface;
+ data->bmc_device = dev;
+
+ /* Create IPMI messaging interface user */
+ err = ipmi_create_user(data->interface, &driver_data.ipmi_hndlrs,
+ data, &data->user);
+ if (err < 0) {
+ printk(KERN_ERR DRVNAME ": Error, unable to register user with "
+ "ipmi interface %d\n",
+ data->interface);
+ goto out;
+ }
+
+ mutex_init(&data->lock);
+
+ /* Initialize message */
+ data->tx_msgid = 0;
+ init_completion(&data->read_complete);
+ data->tx_message.netfn = PEX_NET_FUNCTION;
+ data->tx_message.cmd = PEX_COMMAND;
+ data->tx_message.data = data->tx_msg_data;
+
+ /* Does this BMC support PowerExecutive? */
+ err = ibmpex_ver_check(data);
+ if (err)
+ goto out_user;
+
+ /* Register the BMC as a HWMON class device */
+ data->hwmon_dev = hwmon_device_register(data->bmc_device);
+
+ if (IS_ERR(data->hwmon_dev)) {
+ printk(KERN_ERR DRVNAME ": Error, unable to register hwmon "
+ "class device for interface %d\n",
+ data->interface);
+ kfree(data);
+ return;
+ }
+
+ /* finally add the new bmc data to the bmc data list */
+ dev_set_drvdata(dev, data);
+ list_add_tail(&data->list, &driver_data.bmc_data);
+
+ /* Now go find all the sensors */
+ err = ibmpex_find_sensors(data);
+ if (err) {
+ printk(KERN_ERR "Error %d allocating memory\n", err);
+ goto out_register;
+ }
+
+ return;
+
+out_register:
+ hwmon_device_unregister(data->hwmon_dev);
+out_user:
+ ipmi_destroy_user(data->user);
+out:
+ kfree(data);
+}
+
+static void ibmpex_bmc_delete(struct ibmpex_bmc_data *data)
+{
+ int i, j;
+
+ device_remove_file(data->bmc_device,
+ &sensor_dev_attr_reset_high_low.dev_attr);
+ device_remove_file(data->bmc_device, &sensor_dev_attr_name.dev_attr);
+ for (i = 0; i < data->num_sensors; i++)
+ for (j = 0; j < PEX_NUM_SENSOR_FUNCS; j++) {
+ if (!data->sensors[i].attr[j].dev_attr.attr.name)
+ continue;
+ device_remove_file(data->bmc_device,
+ &data->sensors[i].attr[j].dev_attr);
+ kfree(data->sensors[i].attr[j].dev_attr.attr.name);
+ }
+
+ list_del(&data->list);
+ dev_set_drvdata(data->bmc_device, NULL);
+ hwmon_device_unregister(data->hwmon_dev);
+ ipmi_destroy_user(data->user);
+ kfree(data->sensors);
+ kfree(data);
+}
+
+static void ibmpex_bmc_gone(int iface)
+{
+ struct ibmpex_bmc_data *data = get_bmc_data(iface);
+
+ if (!data)
+ return;
+
+ ibmpex_bmc_delete(data);
+}
+
+static void ibmpex_msg_handler(struct ipmi_recv_msg *msg, void *user_msg_data)
+{
+ struct ibmpex_bmc_data *data = (struct ibmpex_bmc_data *)user_msg_data;
+
+ if (msg->msgid != data->tx_msgid) {
+ printk(KERN_ERR "Received msgid (%02x) and transmitted "
+ "msgid (%02x) mismatch!\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) {
+ data->rx_msg_len = msg->msg.data_len - 1;
+ 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);
+}
+
+static int __init ibmpex_init(void)
+{
+ return ipmi_smi_watcher_register(&driver_data.bmc_events);
+}
+
+static void __exit ibmpex_exit(void)
+{
+ struct ibmpex_bmc_data *p, *next;
+
+ ipmi_smi_watcher_unregister(&driver_data.bmc_events);
+ list_for_each_entry_safe(p, next, &driver_data.bmc_data, list)
+ ibmpex_bmc_delete(p);
+}
+
+MODULE_AUTHOR("Darrick J. Wong <[email protected]>");
+MODULE_DESCRIPTION("IBM PowerExecutive power/temperature sensor driver");
+MODULE_LICENSE("GPL");
+
+module_init(ibmpex_init);
+module_exit(ibmpex_exit);
Hi Darrick,
On Fri, 14 Sep 2007 12:29:24 -0700, Darrick J. Wong wrote:
> Update the hwmon sysfs interface documentation to include a specification
> for power meters.
Thanks for the update. I have some more comments. And sorry if it looks
like nitpicking, but this is a standard interface we're defining so we
better make sure that we get it right.
First of all, please think of a better subject line for this patch.
"Update Documentation/hwmon/sysfs-interface" is too vague when your
patch is rather specific.
> Signed-off-by: Darrick J. Wong <[email protected]>
> ---
>
> Documentation/hwmon/sysfs-interface | 30 ++++++++++++++++++++++++++++++
> 1 files changed, 30 insertions(+), 0 deletions(-)
>
> diff --git a/Documentation/hwmon/sysfs-interface b/Documentation/hwmon/sysfs-interface
> index db7bb4a..5c98bee 100644
> --- a/Documentation/hwmon/sysfs-interface
> +++ b/Documentation/hwmon/sysfs-interface
> @@ -324,6 +324,36 @@ curr[1-*]_input Current input value
> Unit: milliampere
> RO
>
> +*********
> +* Power *
> +*********
> +
> +power[1-*]_average Average power use
> + Unit: microWatt
> + RO
> +
> +power[1-*]_average_highest Historical average maximum power use
> + Unit: microWatt
> + RO
> +
> +power[1-*]_average_lowest Historical average minimum power use
> + Unit: microWatt
> + RO
How useful are historical extremes of an average?
> +
> +power[1-*]_input Instantaneous power use
> + Unit: microWatt
> + RO
> +
> +power[1-*]_input_highest Historical maximum power use
> + Unit: microWatt
> + RO
> +
> +power[1-*]_input_lowest Historical minimum power use
> + Unit: microWatt
> + RO
> +
> +power[1-*]_high_low_reset Reset input_highest/input_lowest.
> + WO
I don't much like this name. It sounds like a name crafted for the
specific feature of a given chip, while we try to use generic names for
the standard interface. I would rather go for power[1-*]_reset_history
if we have a single file for resetting all the extremes of a given
channel, or power[1-*]_input_lowest_reset and
power[1-*]_input_highest_reset if we go for a per-value reset.
Alternatively, we could simply make the power[1-*]_input_lowest and
power[1-*]_input_highest files writable, and "cat power1_input >
power1_input_lowest" (or any write?) would reset the history.
>
> **********
> * Alarms *
Thanks,
--
Jean Delvare
On Mon, Sep 17, 2007 at 07:28:08PM +0200, Jean Delvare wrote:
> Thanks for the update. I have some more comments. And sorry if it looks
> like nitpicking, but this is a standard interface we're defining so we
> better make sure that we get it right.
Agreed. :)
>
> First of all, please think of a better subject line for this patch.
> "Update Documentation/hwmon/sysfs-interface" is too vague when your
> patch is rather specific.
Will change to:
"Add power meter spec to Documentation/hwmon/sysfs-interface"
> > +power[1-*]_average_lowest Historical average minimum power use
> > + Unit: microWatt
> > + RO
>
> How useful are historical extremes of an average?
I don't know if anybody ever really requires this data, but the device
I'm talking to records the extremes in hardware, so I put it in the driver.
> > +power[1-*]_high_low_reset Reset input_highest/input_lowest.
> > + WO
>
> I don't much like this name. It sounds like a name crafted for the
> specific feature of a given chip, while we try to use generic names for
Guilty as charged. Since ibmpex is the only on-board power meter hardware
to which I have access, the interface proposal includes the various odd
pieces that the hardware provides in addition to the meters. I
could take the historical extreme and reset bits out of the proposal and
put them in something like Documentation/hwmon/ibmpex.txt as extra
chip-specific features if people prefer that.
> the standard interface. I would rather go for power[1-*]_reset_history
Me too.
> if we have a single file for resetting all the extremes of a given
> channel, or power[1-*]_input_lowest_reset and
> power[1-*]_input_highest_reset if we go for a per-value reset.
The ibmpex hardware only knows how to reset all of them.
> Alternatively, we could simply make the power[1-*]_input_lowest and
> power[1-*]_input_highest files writable, and "cat power1_input >
> power1_input_lowest" (or any write?) would reset the history.
I'd prefer to leave it explicitly called out as a separate sysfs knob,
unless there are established precedents for resetting a sensor by
writing something to its sysfs file.
--D
Hi Darrick,
On Mon, 17 Sep 2007 11:43:11 -0700, Darrick J. Wong wrote:
> On Mon, Sep 17, 2007 at 07:28:08PM +0200, Jean Delvare wrote:
> > First of all, please think of a better subject line for this patch.
> > "Update Documentation/hwmon/sysfs-interface" is too vague when your
> > patch is rather specific.
>
> Will change to:
> "Add power meter spec to Documentation/hwmon/sysfs-interface"
Sounds good, thanks.
> > > +power[1-*]_average_lowest Historical average minimum power use
> > > + Unit: microWatt
> > > + RO
> >
> > How useful are historical extremes of an average?
>
> I don't know if anybody ever really requires this data, but the device
> I'm talking to records the extremes in hardware, so I put it in the driver.
OK, fine with me then.
> > > +power[1-*]_high_low_reset Reset input_highest/input_lowest.
> > > + WO
> >
> > I don't much like this name. It sounds like a name crafted for the
> > specific feature of a given chip, while we try to use generic names for
>
> Guilty as charged. Since ibmpex is the only on-board power meter hardware
> to which I have access, the interface proposal includes the various odd
> pieces that the hardware provides in addition to the meters. I
> could take the historical extreme and reset bits out of the proposal and
> put them in something like Documentation/hwmon/ibmpex.txt as extra
> chip-specific features if people prefer that.
I'm fine having them in the spec as long as the interface is made
generic enough.
> > the standard interface. I would rather go for power[1-*]_reset_history
>
> Me too.
>
> > if we have a single file for resetting all the extremes of a given
> > channel, or power[1-*]_input_lowest_reset and
> > power[1-*]_input_highest_reset if we go for a per-value reset.
>
> The ibmpex hardware only knows how to reset all of them.
This doesn't mean we do not want separate files. The same problem
exists for many other hardware monitoring features. For example most
thermal sensor chips have one high temperature limit per temperature
channel. However some of them have a single limit register for several
channels! The approach we have taken so far was to have individual
files, and it's up to the driver to keep them in sync.
Another example is alarm files. Some chips have a single alarm for
out-of-range voltage measurements. Others have separate alarms for low
voltage and high voltage limit crossing. In that case, we decided to
have either a single file (in0_alarm) or separate files (in0_min_alarm
and in0_max_alarm) depending on what the chip supports. Admittedly this
is not very consistent with what we did for the shared temperature
limits.
> > Alternatively, we could simply make the power[1-*]_input_lowest and
> > power[1-*]_input_highest files writable, and "cat power1_input >
> > power1_input_lowest" (or any write?) would reset the history.
>
> I'd prefer to leave it explicitly called out as a separate sysfs knob,
> unless there are established precedents for resetting a sensor by
> writing something to its sysfs file.
No, there's no such established precedent. This was only a suggestion
from me as it seems to make some sense. If you don't like the idea,
just ignore it.
--
Jean Delvare
Hi Darrick:
* Darrick J. Wong <[email protected]> [2007-09-14 12:33:46 -0700]:
> Here's a fourth revision where I've tried to clean up the things that
> people complained about, as well as shifted the sysfs file names to
> match the spec that we've been drifting towards.
Sorry it took me so long to get back to this. I've applied this to my hwmon
testing branch, but I would still like to get a review from an IPMI expert.
OBTW: please try to send patches which apply cleanly to some tree to which I
have access. E.g. from the Makefile - preceding and trailing context are not
present in any git tree that I know.
> ---
> ibm_pex: Driver to export IBM PowerExecutive power meter sensors.
>
> Signed-off-by: Darrick J. Wong <[email protected]>
> ---
>
> drivers/hwmon/Kconfig | 13 +
> drivers/hwmon/Makefile | 1
> drivers/hwmon/ibmpex.c | 608 ++++++++++++++++++++++++++++++++++++++++++++++++
> 3 files changed, 622 insertions(+), 0 deletions(-)
>
> diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig
> index 5ca5d95..591b666 100644
> --- a/drivers/hwmon/Kconfig
> +++ b/drivers/hwmon/Kconfig
> @@ -315,6 +315,19 @@ config SENSORS_CORETEMP
> sensor inside your CPU. Supported all are all known variants
> of Intel Core family.
>
> +config SENSORS_IBMPEX
> + tristate "IBM PowerExecutive temperature/power sensors"
> + select IPMI_SI
> + depends on IPMI_HANDLER
> + help
> + If you say yes here you get support for the temperature and
> + power sensors in various IBM System X servers that support
> + PowerExecutive. So far this includes the x3550, x3650, x3655,
> + x3755, and certain HS20 blades.
> +
> + This driver can also be built as a module. If so, the module
> + will be called ibmpex.
> +
> config SENSORS_IT87
> tristate "ITE IT87xx and compatibles"
> select HWMON_VID
> diff --git a/drivers/hwmon/Makefile b/drivers/hwmon/Makefile
> index 5070cf7..0fcbcb4 100644
> --- a/drivers/hwmon/Makefile
> +++ b/drivers/hwmon/Makefile
> @@ -38,6 +38,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_IBMPEX) += ibmpex.o
> obj-$(CONFIG_SENSORS_IPMI) += ipmisensors.o
> obj-$(CONFIG_SENSORS_IT87) += it87.o
> obj-$(CONFIG_SENSORS_K8TEMP) += k8temp.o
> diff --git a/drivers/hwmon/ibmpex.c b/drivers/hwmon/ibmpex.c
> new file mode 100644
> index 0000000..fe2c261
> --- /dev/null
> +++ b/drivers/hwmon/ibmpex.c
> @@ -0,0 +1,608 @@
> +/*
> + * A hwmon driver for the IBM PowerExecutive temperature/power sensors
> + * Copyright (C) 2007 IBM
> + *
> + * Author: Darrick J. Wong <[email protected]>
> + *
> + * 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>
> +
> +#define REFRESH_INTERVAL (2 * HZ)
> +#define DRVNAME "ibmpex"
> +
> +#define PEX_GET_VERSION 1
> +#define PEX_GET_SENSOR_COUNT 2
> +#define PEX_GET_SENSOR_NAME 3
> +#define PEX_RESET_HIGH_LOW 4
> +#define PEX_GET_SENSOR_DATA 6
> +
> +#define PEX_NET_FUNCTION 0x3A
> +#define PEX_COMMAND 0x3C
> +
> +static inline u16 extract_value(const char *data, int offset)
> +{
> + return be16_to_cpup((u16 *)&data[offset]);
> +}
> +
> +#define TEMP_SENSOR 1
> +#define POWER_SENSOR 2
> +
> +#define PEX_SENSOR_TYPE_LEN 3
> +static u8 const power_sensor_sig[] = {0x70, 0x77, 0x72};
> +static u8 const temp_sensor_sig[] = {0x74, 0x65, 0x6D};
> +
> +#define PEX_MULT_LEN 2
> +static u8 const watt_sensor_sig[] = {0x41, 0x43};
> +
> +#define PEX_NUM_SENSOR_FUNCS 3
> +static char const * const power_sensor_name_templates[] = {
> + "%s%d_average",
> + "%s%d_average_lowest",
> + "%s%d_average_highest"
> +};
> +static char const * const temp_sensor_name_templates[] = {
> + "%s%d_input",
> + "%s%d_input_lowest",
> + "%s%d_input_highest"
> +};
> +
> +static void ibmpex_msg_handler(struct ipmi_recv_msg *msg, void *user_msg_data);
> +static void ibmpex_register_bmc(int iface, struct device *dev);
> +static void ibmpex_bmc_gone(int iface);
> +
> +struct ibmpex_sensor_data {
> + int in_use;
> + s16 values[PEX_NUM_SENSOR_FUNCS];
> + int multiplier;
> +
> + struct sensor_device_attribute_2 attr[PEX_NUM_SENSOR_FUNCS];
> +};
> +
> +struct ibmpex_bmc_data {
> + struct list_head list;
> + struct device *hwmon_dev;
> + struct device *bmc_device;
> + struct mutex lock;
> + char valid;
> + unsigned long last_updated; /* In jiffies */
> +
> + struct ipmi_addr address;
> + struct completion read_complete;
> + ipmi_user_t user;
> + int interface;
> +
> + struct kernel_ipmi_msg tx_message;
> + unsigned char tx_msg_data[IPMI_MAX_MSG_LENGTH];
> + long tx_msgid;
> +
> + unsigned char rx_msg_data[IPMI_MAX_MSG_LENGTH];
> + unsigned long rx_msg_len;
> + unsigned char rx_result;
> + int rx_recv_type;
> +
> + unsigned char sensor_major;
> + unsigned char sensor_minor;
> +
> + unsigned char num_sensors;
> + struct ibmpex_sensor_data *sensors;
> +};
> +
> +struct ibmpex_driver_data {
> + struct list_head bmc_data;
> + struct ipmi_smi_watcher bmc_events;
> + struct ipmi_user_hndl ipmi_hndlrs;
> +};
> +
> +static struct ibmpex_driver_data driver_data = {
> + .bmc_data = LIST_HEAD_INIT(driver_data.bmc_data),
> + .bmc_events = {
> + .owner = THIS_MODULE,
> + .new_smi = ibmpex_register_bmc,
> + .smi_gone = ibmpex_bmc_gone,
> + },
> + .ipmi_hndlrs = {
> + .ipmi_recv_hndl = ibmpex_msg_handler,
> + },
> +};
> +
> +static int ibmpex_send_message(struct ibmpex_bmc_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:
> + printk(KERN_ERR "%s: request_settime=%x\n", __FUNCTION__, err);
> + return err;
> +out:
> + printk(KERN_ERR "%s: validate_addr=%x\n", __FUNCTION__, err);
> + return err;
> +}
> +
> +static int ibmpex_ver_check(struct ibmpex_bmc_data *data)
> +{
> + data->tx_msg_data[0] = PEX_GET_VERSION;
> + data->tx_message.data_len = 1;
> + ibmpex_send_message(data);
> +
> + wait_for_completion(&data->read_complete);
> +
> + if (data->rx_result || data->rx_msg_len != 6)
> + return -ENOENT;
> +
> + data->sensor_major = data->rx_msg_data[0];
> + data->sensor_minor = data->rx_msg_data[1];
> +
> + printk(KERN_INFO DRVNAME ": Found BMC with sensor interface "
> + "v%d.%d %d-%02d-%02d on interface %d\n",
> + data->sensor_major,
> + data->sensor_minor,
> + extract_value(data->rx_msg_data, 2),
> + data->rx_msg_data[4],
> + data->rx_msg_data[5],
> + data->interface);
> +
> + return 0;
> +}
> +
> +static int ibmpex_query_sensor_count(struct ibmpex_bmc_data *data)
> +{
> + data->tx_msg_data[0] = PEX_GET_SENSOR_COUNT;
> + data->tx_message.data_len = 1;
> + ibmpex_send_message(data);
> +
> + wait_for_completion(&data->read_complete);
> +
> + if (data->rx_result || data->rx_msg_len != 1)
> + return -ENOENT;
> +
> + return data->rx_msg_data[0];
> +}
> +
> +static int ibmpex_query_sensor_name(struct ibmpex_bmc_data *data, int sensor)
> +{
> + data->tx_msg_data[0] = PEX_GET_SENSOR_NAME;
> + data->tx_msg_data[1] = sensor;
> + data->tx_message.data_len = 2;
> + ibmpex_send_message(data);
> +
> + wait_for_completion(&data->read_complete);
> +
> + if (data->rx_result || data->rx_msg_len < 1)
> + return -ENOENT;
> +
> + return 0;
> +}
> +
> +static int ibmpex_query_sensor_data(struct ibmpex_bmc_data *data, int sensor)
> +{
> + data->tx_msg_data[0] = PEX_GET_SENSOR_DATA;
> + data->tx_msg_data[1] = sensor;
> + data->tx_message.data_len = 2;
> + ibmpex_send_message(data);
> +
> + wait_for_completion(&data->read_complete);
> +
> + if (data->rx_result || data->rx_msg_len < 26) {
> + printk(KERN_ERR "Error reading sensor %d, please check.\n",
> + sensor);
> + return -ENOENT;
> + }
> +
> + return 0;
> +}
> +
> +static int ibmpex_reset_high_low_data(struct ibmpex_bmc_data *data)
> +{
> + data->tx_msg_data[0] = PEX_RESET_HIGH_LOW;
> + data->tx_message.data_len = 1;
> + ibmpex_send_message(data);
> +
> + wait_for_completion(&data->read_complete);
> +
> + return 0;
> +}
> +
> +static void ibmpex_update_device(struct ibmpex_bmc_data *data)
> +{
> + int i, err;
> +
> + mutex_lock(&data->lock);
> + if (time_before(jiffies, data->last_updated + REFRESH_INTERVAL) &&
> + data->valid)
> + goto out;
> +
> + for (i = 0; i < data->num_sensors; i++) {
> + if (!data->sensors[i].in_use)
> + continue;
> + err = ibmpex_query_sensor_data(data, i);
> + if (err)
> + continue;
> + data->sensors[i].values[0] =
> + extract_value(data->rx_msg_data, 16);
> + data->sensors[i].values[1] =
> + extract_value(data->rx_msg_data, 18);
> + data->sensors[i].values[2] =
> + extract_value(data->rx_msg_data, 20);
> + }
> +
> + data->last_updated = jiffies;
> + data->valid = 1;
> +
> +out:
> + mutex_unlock(&data->lock);
> +}
> +
> +static struct ibmpex_bmc_data *get_bmc_data(int iface)
> +{
> + struct ibmpex_bmc_data *p, *next;
> +
> + list_for_each_entry_safe(p, next, &driver_data.bmc_data, list)
> + if (p->interface == iface)
> + return p;
> +
> + return NULL;
> +}
> +
> +static ssize_t show_name(struct device *dev, struct device_attribute *devattr,
> + char *buf)
> +{
> + return sprintf(buf, "%s\n", DRVNAME);
> +}
> +static SENSOR_DEVICE_ATTR(name, S_IRUGO, show_name, NULL, 0);
> +
> +static ssize_t ibmpex_show_sensor(struct device *dev,
> + struct device_attribute *devattr,
> + char *buf)
> +{
> + struct sensor_device_attribute_2 *attr = to_sensor_dev_attr_2(devattr);
> + struct ibmpex_bmc_data *data = dev_get_drvdata(dev);
> + int mult = data->sensors[attr->index].multiplier;
> + ibmpex_update_device(data);
> +
> + return sprintf(buf, "%d\n",
> + data->sensors[attr->index].values[attr->nr] * mult);
> +}
> +
> +static ssize_t ibmpex_reset_high_low(struct device *dev,
> + struct device_attribute *devattr,
> + const char *buf,
> + size_t count)
> +{
> + struct ibmpex_bmc_data *data = dev_get_drvdata(dev);
> +
> + ibmpex_reset_high_low_data(data);
> +
> + return count;
> +}
> +
> +static SENSOR_DEVICE_ATTR(reset_high_low, S_IWUSR, NULL,
> + ibmpex_reset_high_low, 0);
> +
> +static int is_power_sensor(const char *sensor_id, int len)
> +{
> + if (len < PEX_SENSOR_TYPE_LEN)
> + return 0;
> +
> + if (!memcmp(sensor_id, power_sensor_sig, PEX_SENSOR_TYPE_LEN))
> + return 1;
> + return 0;
> +}
> +
> +static int is_temp_sensor(const char *sensor_id, int len)
> +{
> + if (len < PEX_SENSOR_TYPE_LEN)
> + return 0;
> +
> + if (!memcmp(sensor_id, temp_sensor_sig, PEX_SENSOR_TYPE_LEN))
> + return 1;
> + return 0;
> +}
> +
> +static int power_sensor_multiplier(const char *sensor_id, int len)
> +{
> + int i;
> +
> + for (i = PEX_SENSOR_TYPE_LEN; i < len - 1; i++)
> + if (!memcmp(&sensor_id[i], watt_sensor_sig, PEX_MULT_LEN))
> + return 1000000;
> +
> + return 100000;
> +}
> +
> +static int create_sensor(struct ibmpex_bmc_data *data, int type,
> + int counter, int sensor, int func)
> +{
> + int err;
> + char *n;
> +
> + n = kmalloc(32, GFP_KERNEL);
> + if (!n)
> + return -ENOMEM;
> +
> + if (type == TEMP_SENSOR)
> + sprintf(n, temp_sensor_name_templates[func], "temp", counter);
> + else if (type == POWER_SENSOR)
> + sprintf(n, power_sensor_name_templates[func], "power", counter);
> +
> + data->sensors[sensor].attr[func].dev_attr.attr.name = n;
> + data->sensors[sensor].attr[func].dev_attr.attr.mode = S_IRUGO;
> + data->sensors[sensor].attr[func].dev_attr.show = ibmpex_show_sensor;
> + data->sensors[sensor].attr[func].index = sensor;
> + data->sensors[sensor].attr[func].nr = func;
> +
> + err = device_create_file(data->bmc_device,
> + &data->sensors[sensor].attr[func].dev_attr);
> + if (err) {
> + data->sensors[sensor].attr[func].dev_attr.attr.name = NULL;
> + kfree(n);
> + return err;
> + }
> +
> + return 0;
> +}
> +
> +static int ibmpex_find_sensors(struct ibmpex_bmc_data *data)
> +{
> + int i, j, err;
> + int sensor_type;
> + int sensor_counter;
> + int num_power = 0;
> + int num_temp = 0;
> +
> + err = ibmpex_query_sensor_count(data);
> + if (err <= 0)
> + return -ENOENT;
> + data->num_sensors = err;
> +
> + data->sensors = kzalloc(data->num_sensors * sizeof(*data->sensors),
> + GFP_KERNEL);
> + if (!data->sensors)
> + return -ENOMEM;
> +
> + for (i = 0; i < data->num_sensors; i++) {
> + err = ibmpex_query_sensor_name(data, i);
> + if (err)
> + continue;
> +
> + if (is_power_sensor(data->rx_msg_data, data->rx_msg_len)) {
> + sensor_type = POWER_SENSOR;
> + num_power++;
> + sensor_counter = num_power;
> + data->sensors[i].multiplier =
> + power_sensor_multiplier(data->rx_msg_data,
> + data->rx_msg_len);
> + } else if (is_temp_sensor(data->rx_msg_data,
> + data->rx_msg_len)) {
> + sensor_type = TEMP_SENSOR;
> + num_temp++;
> + sensor_counter = num_temp;
> + data->sensors[i].multiplier = 1;
> + } else
> + continue;
> +
> + data->sensors[i].in_use = 1;
> +
> + /* Create attributes */
> + for (j = 0; j < PEX_NUM_SENSOR_FUNCS; j++) {
> + err = create_sensor(data, sensor_type, sensor_counter,
> + i, j);
> + if (err)
> + goto exit_remove;
> + }
> + }
> +
> + err = device_create_file(data->bmc_device,
> + &sensor_dev_attr_reset_high_low.dev_attr);
> + if (err)
> + goto exit_remove;
> +
> + err = device_create_file(data->bmc_device,
> + &sensor_dev_attr_name.dev_attr);
> + if (err)
> + goto exit_remove;
> +
> + return 0;
> +
> +exit_remove:
> + device_remove_file(data->bmc_device,
> + &sensor_dev_attr_reset_high_low.dev_attr);
> + device_remove_file(data->bmc_device, &sensor_dev_attr_name.dev_attr);
> + for (i = 0; i < data->num_sensors; i++)
> + for (j = 0; j < PEX_NUM_SENSOR_FUNCS; j++) {
> + if (!data->sensors[i].attr[j].dev_attr.attr.name)
> + continue;
> + device_remove_file(data->bmc_device,
> + &data->sensors[i].attr[j].dev_attr);
> + kfree(data->sensors[i].attr[j].dev_attr.attr.name);
> + }
> +
> + kfree(data->sensors);
> + return err;
> +}
> +
> +static void ibmpex_register_bmc(int iface, struct device *dev)
> +{
> + struct ibmpex_bmc_data *data;
> + int err;
> +
> + data = kzalloc(sizeof(*data), GFP_KERNEL);
> + if (!data) {
> + printk(KERN_ERR DRVNAME ": Insufficient memory for BMC "
> + "interface %d.\n", data->interface);
> + return;
> + }
> +
> + data->address.addr_type = IPMI_SYSTEM_INTERFACE_ADDR_TYPE;
> + data->address.channel = IPMI_BMC_CHANNEL;
> + data->address.data[0] = 0;
> + data->interface = iface;
> + data->bmc_device = dev;
> +
> + /* Create IPMI messaging interface user */
> + err = ipmi_create_user(data->interface, &driver_data.ipmi_hndlrs,
> + data, &data->user);
> + if (err < 0) {
> + printk(KERN_ERR DRVNAME ": Error, unable to register user with "
> + "ipmi interface %d\n",
> + data->interface);
> + goto out;
> + }
> +
> + mutex_init(&data->lock);
> +
> + /* Initialize message */
> + data->tx_msgid = 0;
> + init_completion(&data->read_complete);
> + data->tx_message.netfn = PEX_NET_FUNCTION;
> + data->tx_message.cmd = PEX_COMMAND;
> + data->tx_message.data = data->tx_msg_data;
> +
> + /* Does this BMC support PowerExecutive? */
> + err = ibmpex_ver_check(data);
> + if (err)
> + goto out_user;
> +
> + /* Register the BMC as a HWMON class device */
> + data->hwmon_dev = hwmon_device_register(data->bmc_device);
> +
> + if (IS_ERR(data->hwmon_dev)) {
> + printk(KERN_ERR DRVNAME ": Error, unable to register hwmon "
> + "class device for interface %d\n",
> + data->interface);
> + kfree(data);
> + return;
> + }
> +
> + /* finally add the new bmc data to the bmc data list */
> + dev_set_drvdata(dev, data);
> + list_add_tail(&data->list, &driver_data.bmc_data);
> +
> + /* Now go find all the sensors */
> + err = ibmpex_find_sensors(data);
> + if (err) {
> + printk(KERN_ERR "Error %d allocating memory\n", err);
> + goto out_register;
> + }
> +
> + return;
> +
> +out_register:
> + hwmon_device_unregister(data->hwmon_dev);
> +out_user:
> + ipmi_destroy_user(data->user);
> +out:
> + kfree(data);
> +}
> +
> +static void ibmpex_bmc_delete(struct ibmpex_bmc_data *data)
> +{
> + int i, j;
> +
> + device_remove_file(data->bmc_device,
> + &sensor_dev_attr_reset_high_low.dev_attr);
> + device_remove_file(data->bmc_device, &sensor_dev_attr_name.dev_attr);
> + for (i = 0; i < data->num_sensors; i++)
> + for (j = 0; j < PEX_NUM_SENSOR_FUNCS; j++) {
> + if (!data->sensors[i].attr[j].dev_attr.attr.name)
> + continue;
> + device_remove_file(data->bmc_device,
> + &data->sensors[i].attr[j].dev_attr);
> + kfree(data->sensors[i].attr[j].dev_attr.attr.name);
> + }
> +
> + list_del(&data->list);
> + dev_set_drvdata(data->bmc_device, NULL);
> + hwmon_device_unregister(data->hwmon_dev);
> + ipmi_destroy_user(data->user);
> + kfree(data->sensors);
> + kfree(data);
> +}
> +
> +static void ibmpex_bmc_gone(int iface)
> +{
> + struct ibmpex_bmc_data *data = get_bmc_data(iface);
> +
> + if (!data)
> + return;
> +
> + ibmpex_bmc_delete(data);
> +}
> +
> +static void ibmpex_msg_handler(struct ipmi_recv_msg *msg, void *user_msg_data)
> +{
> + struct ibmpex_bmc_data *data = (struct ibmpex_bmc_data *)user_msg_data;
> +
> + if (msg->msgid != data->tx_msgid) {
> + printk(KERN_ERR "Received msgid (%02x) and transmitted "
> + "msgid (%02x) mismatch!\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) {
> + data->rx_msg_len = msg->msg.data_len - 1;
> + 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);
> +}
> +
> +static int __init ibmpex_init(void)
> +{
> + return ipmi_smi_watcher_register(&driver_data.bmc_events);
> +}
> +
> +static void __exit ibmpex_exit(void)
> +{
> + struct ibmpex_bmc_data *p, *next;
> +
> + ipmi_smi_watcher_unregister(&driver_data.bmc_events);
> + list_for_each_entry_safe(p, next, &driver_data.bmc_data, list)
> + ibmpex_bmc_delete(p);
> +}
> +
> +MODULE_AUTHOR("Darrick J. Wong <[email protected]>");
> +MODULE_DESCRIPTION("IBM PowerExecutive power/temperature sensor driver");
> +MODULE_LICENSE("GPL");
> +
> +module_init(ibmpex_init);
> +module_exit(ibmpex_exit);
Regards,
--
Mark M. Hoffman
[email protected]
Mark M. Hoffman wrote:
>> +static void ibmpex_register_bmc(int iface, struct device *dev)
>> +{
>> + struct ibmpex_bmc_data *data;
>> + int err;
>> +
>> + data = kzalloc(sizeof(*data), GFP_KERNEL);
>> + if (!data) {
>> + printk(KERN_ERR DRVNAME ": Insufficient memory for BMC "
>> + "interface %d.\n", data->interface);
>> + return;
>> + }
>> +
>> + data->address.addr_type = IPMI_SYSTEM_INTERFACE_ADDR_TYPE;
>> + data->address.channel = IPMI_BMC_CHANNEL;
>> + data->address.data[0] = 0;
>> + data->interface = iface;
>> + data->bmc_device = dev;
>> +
>> + /* Create IPMI messaging interface user */
>> + err = ipmi_create_user(data->interface, &driver_data.ipmi_hndlrs,
>> + data, &data->user);
>> + if (err < 0) {
>> + printk(KERN_ERR DRVNAME ": Error, unable to register user with "
>> + "ipmi interface %d\n",
>> + data->interface);
>> + goto out;
>> + }
>> +
>> + mutex_init(&data->lock);
>> +
>> + /* Initialize message */
>> + data->tx_msgid = 0;
>> + init_completion(&data->read_complete);
>> + data->tx_message.netfn = PEX_NET_FUNCTION;
>> + data->tx_message.cmd = PEX_COMMAND;
>> + data->tx_message.data = data->tx_msg_data;
>> +
>> + /* Does this BMC support PowerExecutive? */
>> + err = ibmpex_ver_check(data);
>> + if (err)
>> + goto out_user;
>> +
>> + /* Register the BMC as a HWMON class device */
>> + data->hwmon_dev = hwmon_device_register(data->bmc_device);
>> +
>> + if (IS_ERR(data->hwmon_dev)) {
>> + printk(KERN_ERR DRVNAME ": Error, unable to register hwmon "
>> + "class device for interface %d\n",
>> + data->interface);
>> + kfree(data);
>> + return;
don't you want to goto out_user here instead?
>> + }
>> +
>> + /* finally add the new bmc data to the bmc data list */
>> + dev_set_drvdata(dev, data);
>> + list_add_tail(&data->list, &driver_data.bmc_data);
>> +
>> + /* Now go find all the sensors */
>> + err = ibmpex_find_sensors(data);
>> + if (err) {
>> + printk(KERN_ERR "Error %d allocating memory\n", err);
>> + goto out_register;
>> + }
>> +
>> + return;
>> +
>> +out_register:
>> + hwmon_device_unregister(data->hwmon_dev);
>> +out_user:
>> + ipmi_destroy_user(data->user);
>> +out:
>> + kfree(data);
>> +}
On Tue, Oct 09, 2007 at 06:44:07PM +0200, Roel Kluin wrote:
> >> + if (IS_ERR(data->hwmon_dev)) {
> >> + printk(KERN_ERR DRVNAME ": Error, unable to register hwmon "
> >> + "class device for interface %d\n",
> >> + data->interface);
> >> + kfree(data);
> >> + return;
>
> don't you want to goto out_user here instead?
Err, yes, thank you for catching that.
--D
Roel Kluin <[email protected]> found a minor defect in the init code if
hwmon device registration fails.
Signed-off-by: Darrick J. Wong <[email protected]>
---
drivers/hwmon/ibmpex.c | 3 +--
1 files changed, 1 insertions(+), 2 deletions(-)
diff --git a/drivers/hwmon/ibmpex.c b/drivers/hwmon/ibmpex.c
index fe2c261..c462824 100644
--- a/drivers/hwmon/ibmpex.c
+++ b/drivers/hwmon/ibmpex.c
@@ -498,8 +498,7 @@ static void ibmpex_register_bmc(int iface, struct device *dev)
printk(KERN_ERR DRVNAME ": Error, unable to register hwmon "
"class device for interface %d\n",
data->interface);
- kfree(data);
- return;
+ goto out_user;
}
/* finally add the new bmc data to the bmc data list */
Hi:
* Darrick J. Wong <[email protected]> [2007-10-09 15:08:24 -0700]:
> Roel Kluin <[email protected]> found a minor defect in the init code if
> hwmon device registration fails.
>
> Signed-off-by: Darrick J. Wong <[email protected]>
> ---
>
> drivers/hwmon/ibmpex.c | 3 +--
> 1 files changed, 1 insertions(+), 2 deletions(-)
>
> diff --git a/drivers/hwmon/ibmpex.c b/drivers/hwmon/ibmpex.c
> index fe2c261..c462824 100644
> --- a/drivers/hwmon/ibmpex.c
> +++ b/drivers/hwmon/ibmpex.c
> @@ -498,8 +498,7 @@ static void ibmpex_register_bmc(int iface, struct device *dev)
> printk(KERN_ERR DRVNAME ": Error, unable to register hwmon "
> "class device for interface %d\n",
> data->interface);
> - kfree(data);
> - return;
> + goto out_user;
> }
>
> /* finally add the new bmc data to the bmc data list */
Applied to hwmon-2.6.git/testing, thanks.
--
Mark M. Hoffman
[email protected]
On Tue, Oct 09, 2007 at 08:00:44AM -0400, Mark M. Hoffman wrote:
> Sorry it took me so long to get back to this. I've applied this to my hwmon
> testing branch, but I would still like to get a review from an IPMI expert.
I'm working on finding somebody to do that.
> OBTW: please try to send patches which apply cleanly to some tree to which I
> have access. E.g. from the Makefile - preceding and trailing context are not
> present in any git tree that I know.
Oops, sorry about that.
The i5k_amb driver referenced in the Makefile diff is the same one that
I posted to lm-sensors a few weeks ago, though there hasn't been much
discussion after it was pointed out that someone had already written a
driver to achieve the same purpose. Unfortunately, nothing's happened
to either driver; any thoughts? I'm thinking that we ought to get one
of them into shape for upstream, though I don't particularly care which
one it is...
--D