[RFC linux v3 4/4] hwmon: Add OCC driver polling and parse response
eajames.ibm at gmail.com
eajames.ibm at gmail.com
Thu Oct 13 09:43:05 AEDT 2016
From: "Edward A. James" <eajames at 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 at us.ibm.com>
---
drivers/hwmon/occ/occ_i2c.c | 1 +
drivers/hwmon/occ/power8_occ.c | 497 ++++++++++++++++++++++++++++++++++++++++-
2 files changed, 492 insertions(+), 6 deletions(-)
diff --git a/drivers/hwmon/occ/occ_i2c.c b/drivers/hwmon/occ/occ_i2c.c
index 40ed75d..deb8dc6 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 fbfaf08..1a5195e 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,402 @@
#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 */
+ /* TODO: investigate memory management */
+ 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 +486,85 @@ 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;
+
+ /*
+ * TODO: fetch header, and then allocate the rest of the buffer based
+ * on the header size. Assuming the OCC has a fixed sized header
+ */
+ 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 +648,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 +679,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
More information about the openbmc
mailing list