All of lore.kernel.org
 help / color / mirror / Atom feed
* [RFC linux v4 4/6] hwmon: Add OCC driver polling and parse response
@ 2016-10-13 21:42 eajames.ibm
  0 siblings, 0 replies; only message in thread
From: eajames.ibm @ 2016-10-13 21:42 UTC (permalink / raw)
  To: openbmc; +Cc: Edward A. James

From: "Edward A. James" <eajames@us.ibm.com>

Add code to poll the OCC and parse the response. Add all the necessary
structures for the P8 OCC.

Signed-off-by: Edward A. James <eajames@us.ibm.com>
---
 drivers/hwmon/occ/occ_i2c.c    |   1 +
 drivers/hwmon/occ/power8_occ.c | 492 ++++++++++++++++++++++++++++++++++++++++-
 2 files changed, 487 insertions(+), 6 deletions(-)

diff --git a/drivers/hwmon/occ/occ_i2c.c b/drivers/hwmon/occ/occ_i2c.c
index e9680eb..2e44527 100644
--- a/drivers/hwmon/occ/occ_i2c.c
+++ b/drivers/hwmon/occ/occ_i2c.c
@@ -59,6 +59,7 @@ int occ_getscom(void *bus, u32 address, u8 *data, size_t offset)
 	if (rc != sizeof(u64))
 		return -I2C_READ_ERROR;
 
+	/* TODO: is OCC be or le? */
 	*((u64 *)data) = le64_to_cpu(buf);
 
 	return 0;
diff --git a/drivers/hwmon/occ/power8_occ.c b/drivers/hwmon/occ/power8_occ.c
index a42fb30..f0f14ec 100644
--- a/drivers/hwmon/occ/power8_occ.c
+++ b/drivers/hwmon/occ/power8_occ.c
@@ -31,6 +31,8 @@
 
 #include "occ.h"
 
+#define OCC_DATA_MAX	4096
+
 /* Defined in POWER8 Processor Registers Specification */
 /* To generate attn to OCC */
 #define ATTN_DATA	0x0006B035
@@ -45,15 +47,401 @@
 #define OCC_COMMAND_ADDR	0xFFFF6000
 #define OCC_RESPONSE_ADDR	0xFFFF7000
 
+#define RESP_DATA_LENGTH	3
+#define RESP_HEADER_OFFSET	5
+#define SENSOR_STR_OFFSET	37
+#define SENSOR_BLOCK_NUM_OFFSET	43
+#define SENSOR_BLOCK_OFFSET	45
+
+enum sensor_type {
+	FREQ = 0,
+	TEMP,
+	POWER,
+	CAPS,
+	MAX_OCC_SENSOR_TYPE
+};
+
+/* OCC sensor data format */
+struct occ_sensor {
+	u16 sensor_id;
+	u16 value;
+};
+
+struct power_sensor {
+	u16 sensor_id;
+	u32 update_tag;
+	u32 accumulator;
+	u16 value;
+};
+
+struct caps_sensor {
+	u16 curr_powercap;
+	u16 curr_powerreading;
+	u16 norm_powercap;
+	u16 max_powercap;
+	u16 min_powercap;
+	u16 user_powerlimit;
+};
+
+struct sensor_data_block {
+	u8 sensor_type[4];
+	u8 reserved0;
+	u8 sensor_format;
+	u8 sensor_length;
+	u8 sensor_num;
+	struct occ_sensor *sensor;
+	struct power_sensor *power;
+	struct caps_sensor *caps;
+};
+
+struct occ_poll_header {
+	u8 status;
+	u8 ext_status;
+	u8 occs_present;
+	u8 config;
+	u8 occ_state;
+	u8 reserved0;
+	u8 reserved1;
+	u8 error_log_id;
+	u32 error_log_addr_start;
+	u16 error_log_length;
+	u8 reserved2;
+	u8 reserved3;
+	u8 occ_code_level[16];
+	u8 sensor_eye_catcher[6];
+	u8 sensor_block_num;
+	u8 sensor_data_version;
+};
+
+struct occ_response {
+	u8 sequence_num;
+	u8 cmd_type;
+	u8 rtn_status;
+	u16 data_length;
+	struct occ_poll_header header;
+	struct sensor_data_block *blocks;
+	u16 chk_sum;
+	int sensor_block_id[MAX_OCC_SENSOR_TYPE];
+};
+
 struct power8_driver {
 	struct occ_driver *driver;
 	struct device *dev;
 	unsigned long update_interval;
+	unsigned long last_updated;
 	u16 user_powercap;
+	struct mutex update_lock;
+	bool valid;
+	struct occ_response occ_response;
 };
 
-static u8 occ_send_cmd(struct occ_driver *occ, u8 seq, u8 type,
-			    u16 length, u8 *data, u8 *resp)
+static void deinit_occ_resp_buf(struct occ_response *resp)
+{
+	int i;
+
+	if (!resp)
+		return;
+
+	if (!resp->blocks)
+		return;
+
+	for (i = 0; i < resp->header.sensor_block_num; ++i) {
+		kfree(resp->blocks[i].sensor);
+		kfree(resp->blocks[i].power);
+		kfree(resp->blocks[i].caps);
+	}
+
+	kfree(resp->blocks);
+
+	memset(resp, 0, sizeof(struct occ_response));
+
+	for (i = 0; i < MAX_OCC_SENSOR_TYPE; ++i)
+		resp->sensor_block_id[i] = -1;
+}
+
+static void *occ_get_sensor_by_type(struct occ_response *resp,
+				    enum sensor_type t)
+{
+	void *sensor;
+
+	if (!resp->blocks)
+		return NULL;
+
+	if (resp->sensor_block_id[t] == -1)
+		return NULL;
+
+	switch (t) {
+	case TEMP:
+	case FREQ:
+		sensor = resp->blocks[resp->sensor_block_id[t]].sensor;
+		break;
+	case POWER:
+		sensor = resp->blocks[resp->sensor_block_id[t]].power;
+		break;
+	case CAPS:
+		sensor = resp->blocks[resp->sensor_block_id[t]].caps;
+		break;
+	default:
+		sensor = NULL;
+	}
+
+	return sensor;
+}
+
+static int occ_renew_sensor(struct occ_response *resp, u8 sensor_length,
+			    u8 sensor_num, enum sensor_type t, int block)
+{
+	void *sensor;
+	int rc;
+
+	sensor = occ_get_sensor_by_type(resp, t);
+
+	/* empty sensor block, release older sensor data */
+	if (sensor_num == 0 || sensor_length == 0) {
+		kfree(sensor);
+		return -1;
+	}
+
+	if (!sensor || sensor_num !=
+	    resp->blocks[resp->sensor_block_id[t]].sensor_num) {
+		kfree(sensor);
+		switch (t) {
+		case TEMP:
+		case FREQ:
+			resp->blocks[block].sensor =
+				kcalloc(sensor_num, sizeof(struct occ_sensor),
+					GFP_KERNEL);
+			if (!resp->blocks[block].sensor) {
+				rc = -ENOMEM;
+				goto err;
+			}
+			break;
+		case POWER:
+			resp->blocks[block].power =
+				kcalloc(sensor_num,
+					sizeof(struct power_sensor),
+					GFP_KERNEL);
+			if (!resp->blocks[block].power) {
+				rc = -ENOMEM;
+				goto err;
+			}
+			break;
+		case CAPS:
+			resp->blocks[block].caps =
+				kcalloc(sensor_num, sizeof(struct caps_sensor),
+					GFP_KERNEL);
+			if (!resp->blocks[block].caps) {
+				rc = -ENOMEM;
+				goto err;
+			}
+			break;
+		default:
+			rc = -ENOMEM;
+			goto err;
+		}
+	}
+
+	return 0;
+err:
+	deinit_occ_resp_buf(resp);
+	return rc;
+}
+
+static int parse_occ_response(struct power8_driver *driver, u8 *data,
+			      struct occ_response *resp)
+{
+	int b;
+	int s;
+	int rc;
+	int dnum = SENSOR_BLOCK_OFFSET;
+	struct occ_sensor *f_sensor;
+	struct occ_sensor *t_sensor;
+	struct power_sensor *p_sensor;
+	struct caps_sensor *c_sensor;
+	u8 sensor_block_num;
+	u8 sensor_type[4];
+	u8 sensor_format;
+	u8 sensor_length;
+	u8 sensor_num;
+	struct device *dev = driver->dev;
+
+	/* check if the data is valid */
+	if (strncmp(&data[SENSOR_STR_OFFSET], "SENSOR", 6) != 0) {
+		dev_dbg(dev, "ERROR: no SENSOR String in response\n");
+		rc = -1;
+		goto err;
+	}
+
+	sensor_block_num = data[SENSOR_BLOCK_NUM_OFFSET];
+	if (sensor_block_num == 0) {
+		dev_dbg(dev, "ERROR: SENSOR block num is 0\n");
+		rc = -1;
+		goto err;
+	}
+
+	/* if sensor block has changed, re-malloc */
+	if (sensor_block_num != resp->header.sensor_block_num) {
+		deinit_occ_resp_buf(resp);
+		resp->blocks = kcalloc(sensor_block_num,
+				       sizeof(struct sensor_data_block),
+				       GFP_KERNEL);
+		if (!resp->blocks)
+			return -ENOMEM;
+	}
+
+	memcpy(&resp->header, &data[RESP_HEADER_OFFSET],
+	       sizeof(struct occ_poll_header));
+	resp->header.error_log_addr_start =
+		be32_to_cpu(resp->header.error_log_addr_start);
+	resp->header.error_log_length =
+		be16_to_cpu(resp->header.error_log_length);
+
+	dev_dbg(dev, "Reading %d sensor blocks\n",
+		resp->header.sensor_block_num);
+	for (b = 0; b < sensor_block_num; b++) {
+		/* 8-byte sensor block head */
+		strncpy(sensor_type, &data[dnum], 4);
+		sensor_format = data[dnum + 5];
+		sensor_length = data[dnum + 6];
+		sensor_num = data[dnum + 7];
+		dnum = dnum + 8;
+
+		dev_dbg(dev, "sensor block[%d]: type: %s, sensor_num: %d\n", b,
+			sensor_type, sensor_num);
+
+		if (strncmp(sensor_type, "FREQ", 4) == 0) {
+			rc = occ_renew_sensor(resp, sensor_length, sensor_num,
+					      FREQ, b);
+			if (rc)
+				continue;
+
+			resp->sensor_block_id[FREQ] = b;
+			for (s = 0; s < sensor_num; s++) {
+				f_sensor = &resp->blocks[b].sensor[s];
+				f_sensor->sensor_id =
+					be16_to_cpup((const __be16 *)
+						     &data[dnum]);
+				f_sensor->value = be16_to_cpup((const __be16 *)
+							      &data[dnum + 2]);
+				dev_dbg(dev,
+					"sensor[%d]-[%d]: id: %u, value: %u\n",
+					b, s, f_sensor->sensor_id,
+					f_sensor->value);
+				dnum = dnum + sensor_length;
+			}
+		} else if (strncmp(sensor_type, "TEMP", 4) == 0) {
+			rc = occ_renew_sensor(resp, sensor_length,
+					      sensor_num, TEMP, b);
+			if (rc)
+				continue;
+
+			resp->sensor_block_id[TEMP] = b;
+			for (s = 0; s < sensor_num; s++) {
+				t_sensor = &resp->blocks[b].sensor[s];
+				t_sensor->sensor_id =
+					be16_to_cpup((const __be16 *)
+						     &data[dnum]);
+				t_sensor->value = be16_to_cpup((const __be16 *)
+							      &data[dnum + 2]);
+				dev_dbg(dev,
+					"sensor[%d]-[%d]: id: %u, value: %u\n",
+					b, s, t_sensor->sensor_id,
+					t_sensor->value);
+				dnum = dnum + sensor_length;
+			}
+		} else if (strncmp(sensor_type, "POWR", 4) == 0) {
+			rc = occ_renew_sensor(resp, sensor_length,
+				sensor_num, POWER, b);
+			if (rc)
+				continue;
+
+			resp->sensor_block_id[POWER] = b;
+			for (s = 0; s < sensor_num; s++) {
+				p_sensor = &resp->blocks[b].power[s];
+				p_sensor->sensor_id =
+					be16_to_cpup((const __be16 *)
+						     &data[dnum]);
+				p_sensor->update_tag =
+					be32_to_cpup((const __be32 *)
+						     &data[dnum + 2]);
+				p_sensor->accumulator =
+					be32_to_cpup((const __be32 *)
+						     &data[dnum + 6]);
+				p_sensor->value = be16_to_cpup((const __be16 *)
+							     &data[dnum + 10]);
+
+				dev_dbg(dev,
+					"sensor[%d]-[%d]: id: %u, value: %u\n",
+					b, s, p_sensor->sensor_id,
+					p_sensor->value);
+
+				dnum = dnum + sensor_length;
+			}
+		} else if (strncmp(sensor_type, "CAPS", 4) == 0) {
+			rc = occ_renew_sensor(resp, sensor_length,
+				sensor_num, CAPS, b);
+			if (rc)
+				continue;
+
+			resp->sensor_block_id[CAPS] = b;
+			for (s = 0; s < sensor_num; s++) {
+				c_sensor = &resp->blocks[b].caps[s];
+				c_sensor->curr_powercap =
+					be16_to_cpup((const __be16 *)
+						     &data[dnum]);
+				c_sensor->curr_powerreading =
+					be16_to_cpup((const __be16 *)
+						     &data[dnum + 2]);
+				c_sensor->norm_powercap =
+					be16_to_cpup((const __be16 *)
+						     &data[dnum + 4]);
+				c_sensor->max_powercap =
+					be16_to_cpup((const __be16 *)
+						     &data[dnum + 6]);
+				c_sensor->min_powercap =
+					be16_to_cpup((const __be16 *)
+						     &data[dnum + 8]);
+				c_sensor->user_powerlimit =
+					be16_to_cpup((const __be16 *)
+						     &data[dnum + 10]);
+
+				dnum = dnum + sensor_length;
+				dev_dbg(dev, "CAPS sensor #%d:\n", s);
+				dev_dbg(dev, "curr_powercap is %x\n",
+					c_sensor->curr_powercap);
+				dev_dbg(dev, "curr_powerreading is %x\n",
+					c_sensor->curr_powerreading);
+				dev_dbg(dev, "norm_powercap is %x\n",
+					c_sensor->norm_powercap);
+				dev_dbg(dev, "max_powercap is %x\n",
+					c_sensor->max_powercap);
+				dev_dbg(dev, "min_powercap is %x\n",
+					c_sensor->min_powercap);
+				dev_dbg(dev, "user_powerlimit is %x\n",
+					c_sensor->user_powerlimit);
+			}
+
+		} else {
+			dev_dbg(dev, "ERROR: sensor type %s not supported\n",
+				resp->blocks[b].sensor_type);
+			rc = -1;
+			goto err;
+		}
+
+		strncpy(resp->blocks[b].sensor_type, sensor_type, 4);
+		resp->blocks[b].sensor_format = sensor_format;
+		resp->blocks[b].sensor_length = sensor_length;
+		resp->blocks[b].sensor_num = sensor_num;
+	}
+
+	return 0;
+err:
+	deinit_occ_resp_buf(resp);
+	return rc;
+}
+
+static u8 occ_send_cmd(struct occ_driver *occ, u8 seq, u8 type, u16 length,
+		       u8 *data, u8 *resp)
 {
 	u32 cmd1, cmd2;
 	u16 checksum;
@@ -97,6 +485,81 @@ static u8 occ_send_cmd(struct occ_driver *occ, u8 seq, u8 type,
 	return resp[2];
 }
 
+static inline u16 get_occdata_length(u8 *data)
+{
+	return be16_to_cpup((const __be16 *)&data[RESP_DATA_LENGTH]);
+}
+
+static int occ_get_all(struct power8_driver *driver)
+{
+	int i, rc;
+	u8 *occ_data;
+	u16 num_bytes;
+	u8 poll_cmd_data = 0x10;
+	struct device *dev = driver->dev;
+	struct occ_driver *occ = driver->driver;
+	struct occ_response *resp = &driver->occ_response;
+
+	occ_data = devm_kzalloc(dev, OCC_DATA_MAX, GFP_KERNEL);
+	if (!occ_data)
+		return -ENOMEM;
+
+	rc = occ_send_cmd(occ, 0, 0, 1, &poll_cmd_data, occ_data);
+	if (rc) {
+		dev_err(dev, "ERROR: OCC Poll: 0x%x\n", rc);
+		rc = -EINVAL;
+		goto out;
+	}
+
+	num_bytes = get_occdata_length(occ_data);
+
+	dev_dbg(dev, "OCC data length: %d\n", num_bytes);
+
+	if (num_bytes > OCC_DATA_MAX) {
+		dev_dbg(dev, "ERROR: OCC data length must be < 4KB\n");
+		rc = -EINVAL;
+		goto out;
+	}
+
+	if (num_bytes <= 0) {
+		dev_dbg(dev, "ERROR: OCC data length is zero\n");
+		rc = -EINVAL;
+		goto out;
+	}
+
+	/* read remaining data */
+	for (i = 8; i < num_bytes + 8; i += 8)
+		occ->bus_ops.getscom(occ->bus, OCB_DATA, occ_data, i);
+
+	rc = parse_occ_response(driver, occ_data, resp);
+
+out:
+	devm_kfree(dev, occ_data);
+	return rc;
+}
+
+static int occ_update_device(struct power8_driver *driver)
+{
+	int rc = 0;
+
+	mutex_lock(&driver->update_lock);
+
+	if (time_after(jiffies, driver->last_updated + driver->update_interval)
+	    || !driver->valid) {
+		driver->valid = 1;
+
+		rc = occ_get_all(driver);
+		if (rc)
+			driver->valid = 0;
+
+		driver->last_updated = jiffies;
+	}
+
+	mutex_unlock(&driver->update_lock);
+
+	return rc;
+}
+
 static ssize_t show_update_interval(struct device *dev,
 				    struct device_attribute *attr, char *buf)
 {
@@ -180,14 +643,28 @@ static void occ_remove_hwmon_attrs(struct power8_driver *driver)
 {
 	struct device *dev = driver->dev;
 
+	device_remove_file(dev, &dev_attr_user_powercap);
 	device_remove_file(dev, &dev_attr_update_interval);
 	device_remove_file(dev, &dev_attr_name);
 }
 
 static int occ_create_hwmon_attrs(struct power8_driver *driver)
 {
-	int rc;
+	int i, rc;
 	struct device *dev = driver->dev;
+	struct occ_response *resp = &driver->occ_response;
+
+	for (i = 0; i < MAX_OCC_SENSOR_TYPE; ++i)
+		resp->sensor_block_id[i] = -1;
+
+	/* read sensor data from occ. */
+	rc = occ_update_device(driver);
+	if (rc != 0) {
+		dev_err(dev, "ERROR: cannot get occ sensor data: %d\n", rc);
+		return rc;
+	}
+	if (!resp->blocks)
+		return -1;
 
 	rc = device_create_file(dev, &dev_attr_name);
 	if (rc)
@@ -197,9 +674,12 @@ static int occ_create_hwmon_attrs(struct power8_driver *driver)
 	if (rc)
 		goto error;
 
-	/* skip powercap for now; we need to determine if CAPS response id
-	 * is present first, which requires polling the OCC
-	 */
+	if (resp->sensor_block_id[CAPS] >= 0) {
+		/* user powercap: only for master OCC */
+		rc = device_create_file(dev, &dev_attr_user_powercap);
+		if (rc)
+			goto error;
+	}
 
 	return 0;
 
-- 
1.9.1

^ permalink raw reply related	[flat|nested] only message in thread

only message in thread, other threads:[~2016-10-13 21:43 UTC | newest]

Thread overview: (only message) (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2016-10-13 21:42 [RFC linux v4 4/6] hwmon: Add OCC driver polling and parse response eajames.ibm

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