lkml.org 
[lkml]   [2017]   [Jan]   [26]   [last100]   RSS Feed
Views: [wrap][no wrap]   [headers]  [forward] 
 
Messages in this thread
    Patch in this message
    /
    From
    Subject[PATCH linux v4 2/6] hwmon: occ: Add sysfs interface
    Date
    From: "Edward A. James" <eajames@us.ibm.com>

    Add a generic mechanism to expose the sensors provided by the OCC in
    sysfs.

    Signed-off-by: Edward A. James <eajames@us.ibm.com>
    Signed-off-by: Andrew Jeffery <andrew@aj.id.au>
    ---
    Documentation/hwmon/occ | 62 ++++++++++
    drivers/hwmon/occ/Makefile | 2 +-
    drivers/hwmon/occ/occ_sysfs.c | 259 ++++++++++++++++++++++++++++++++++++++++++
    drivers/hwmon/occ/occ_sysfs.h | 30 +++++
    4 files changed, 352 insertions(+), 1 deletion(-)
    create mode 100644 drivers/hwmon/occ/occ_sysfs.c
    create mode 100644 drivers/hwmon/occ/occ_sysfs.h

    diff --git a/Documentation/hwmon/occ b/Documentation/hwmon/occ
    index 79d1642..d0bdf06 100644
    --- a/Documentation/hwmon/occ
    +++ b/Documentation/hwmon/occ
    @@ -25,6 +25,68 @@ Currently, all versions of the OCC support four types of sensor data: power,
    temperature, frequency, and "caps," which indicate limits and thresholds used
    internally on the OCC.

    +sysfs Entries
    +-------------
    +
    +The OCC driver uses the hwmon sysfs framework to provide data to userspace.
    +
    +The driver exports a number of sysfs files for each type of sensor. The
    +sensor-specific files vary depending on the processor type, though many of the
    +attributes are common for both the POWER8 and POWER9.
    +
    +The hwmon interface cannot define every type of sensor that may be used.
    +Therefore, the frequency sensor on the OCC uses the "input" type sensor defined
    +by the hwmon interface, rather than defining a new type of custom sensor.
    +
    +Below are detailed the names and meaning of each sensor file for both types of
    +processors. All sensors are read-only unless otherwise specified. <x> indicates
    +the hwmon index. sensor id indicates the unique internal OCC identifer. Please
    +see the POWER OCC specification for details on all these sensor values.
    +
    +frequency:
    + all processors:
    + in<x>_input - frequency value
    + in<x>_label - sensor id
    +temperature:
    + POWER8:
    + temp<x>_input - temperature value
    + temp<x>_label - sensor id
    + POWER9 (in addition to above):
    + temp<x>_type - FRU type
    +
    +power:
    + POWER8:
    + power<x>_input - power value
    + power<x>_label - sensor id
    + power<x>_average - accumulator
    + power<x>_average_interval - update tag (number of samples in
    + accumulator)
    + POWER9:
    + power<x>_input - power value
    + power<x>_label - sensor id
    + power<x>_average_min - accumulator[0]
    + power<x>_average_max - accumulator[1] (64 bits total)
    + power<x>_average_interval - update tag
    + power<x>_reset_history - (function_id | (apss_channel << 8)
    +
    +caps:
    + POWER8:
    + power<x>_cap - current powercap
    + power<x>_cap_max - max powercap
    + power<x>_cap_min - min powercap
    + power<x>_max - normal powercap
    + power<x>_alarm - user powercap, r/w
    + POWER9:
    + power<x>_cap_alarm - user powercap source
    +
    +The driver also provides two sysfs entries through hwmon to better
    +control the driver and monitor the master OCC. Though there may be multiple
    +OCCs present on the system, these two files are only present for the "master"
    +OCC.
    + name - read the name of the driver
    + update_interval - read or write the minimum interval for polling the
    + OCC.
    +
    BMC - Host Communications
    -------------------------

    diff --git a/drivers/hwmon/occ/Makefile b/drivers/hwmon/occ/Makefile
    index 93cb52f..a6881f9 100644
    --- a/drivers/hwmon/occ/Makefile
    +++ b/drivers/hwmon/occ/Makefile
    @@ -1 +1 @@
    -obj-$(CONFIG_SENSORS_PPC_OCC) += occ.o
    +obj-$(CONFIG_SENSORS_PPC_OCC) += occ.o occ_sysfs.o
    diff --git a/drivers/hwmon/occ/occ_sysfs.c b/drivers/hwmon/occ/occ_sysfs.c
    new file mode 100644
    index 0000000..ea2aa04
    --- /dev/null
    +++ b/drivers/hwmon/occ/occ_sysfs.c
    @@ -0,0 +1,259 @@
    +/*
    + * occ_sysfs.c - OCC sysfs interface
    + *
    + * This file contains the methods and data structures for implementing the OCC
    + * hwmon sysfs entries.
    + *
    + * Copyright 2016 IBM Corp.
    + *
    + * 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.
    + */
    +
    +#include <linux/delay.h>
    +#include <linux/device.h>
    +#include <linux/err.h>
    +#include <linux/hwmon.h>
    +#include <linux/hwmon-sysfs.h>
    +#include <linux/init.h>
    +#include <linux/jiffies.h>
    +#include <linux/kernel.h>
    +#include <linux/module.h>
    +#include <linux/mutex.h>
    +#include <linux/slab.h>
    +#include "occ.h"
    +#include "occ_sysfs.h"
    +
    +#define RESP_RETURN_CMD_INVAL 0x13
    +#define OCC_HWMON_NAME_LENGTH 32
    +
    +struct occ_sysfs {
    + struct device *dev;
    + struct occ *occ;
    +
    + char hwmon_name[OCC_HWMON_NAME_LENGTH + 1];
    + const u32 *sensor_hwmon_configs;
    + struct hwmon_channel_info **occ_sensors;
    + struct hwmon_chip_info occ_info;
    + u16 user_powercap;
    +};
    +
    +static int occ_hwmon_read(struct device *dev, enum hwmon_sensor_types type,
    + u32 attr, int channel, long *val)
    +{
    + int rc;
    + struct occ_sysfs *driver = dev_get_drvdata(dev);
    + struct occ *occ = driver->occ;
    +
    + switch (type) {
    + case hwmon_in:
    + rc = occ_get_sensor_field(occ, FREQ, channel, attr, val);
    + break;
    + case hwmon_temp:
    + rc = occ_get_sensor_field(occ, TEMP, channel, attr, val);
    + break;
    + case hwmon_power:
    + rc = occ_get_sensor_field(occ, POWER, channel, attr, val);
    + break;
    + default:
    + rc = -EOPNOTSUPP;
    + }
    +
    + return rc;
    +}
    +
    +static int occ_hwmon_read_string(struct device *dev,
    + enum hwmon_sensor_types type, u32 attr,
    + int channel, char **str)
    +{
    + int rc;
    + unsigned long val = 0;
    +
    + if (!((type == hwmon_in && attr == hwmon_in_label) ||
    + (type == hwmon_temp && attr == hwmon_temp_label) ||
    + (type == hwmon_power && attr == hwmon_power_label)))
    + return -EOPNOTSUPP;
    +
    + /* will fetch the "label", the sensor_id */
    + rc = occ_hwmon_read(dev, type, attr, channel, &val);
    + if (rc < 0)
    + return rc;
    +
    + rc = snprintf(*str, PAGE_SIZE - 1, "%lu", val);
    + if (rc > 0)
    + rc = 0;
    +
    + return rc;
    +}
    +
    +static int occ_hwmon_write(struct device *dev, enum hwmon_sensor_types type,
    + u32 attr, int channel, long val)
    +{
    + int rc = 0;
    + struct occ_sysfs *driver = dev_get_drvdata(dev);
    +
    + if (type == hwmon_power && attr == hwmon_power_alarm) {
    + rc = occ_set_user_powercap(driver->occ, val);
    + if (rc) {
    + if (rc == RESP_RETURN_CMD_INVAL) {
    + dev_err(dev,
    + "set invalid powercap value: %ld\n",
    + val);
    + return -EINVAL;
    + }
    +
    + dev_err(dev, "set user powercap failed: 0x:%x\n", rc);
    + return rc;
    + }
    +
    + driver->user_powercap = val;
    +
    + return rc;
    + }
    +
    + return -EOPNOTSUPP;
    +}
    +
    +static umode_t occ_is_visible(const void *data, enum hwmon_sensor_types type,
    + u32 attr, int channel)
    +{
    + const struct occ_sysfs *driver = data;
    +
    + switch (type) {
    + case hwmon_chip:
    + if (attr == hwmon_chip_update_interval)
    + return S_IRUGO | S_IWUSR;
    + break;
    + case hwmon_in:
    + if (BIT(attr) & driver->sensor_hwmon_configs[0])
    + return S_IRUGO;
    + break;
    + case hwmon_temp:
    + if (BIT(attr) & driver->sensor_hwmon_configs[1])
    + return S_IRUGO;
    + break;
    + case hwmon_power:
    + /* user power limit */
    + if (attr == hwmon_power_alarm)
    + return S_IRUGO | S_IWUSR;
    + else if ((BIT(attr) & driver->sensor_hwmon_configs[2]) ||
    + (BIT(attr) & driver->sensor_hwmon_configs[3]))
    + return S_IRUGO;
    + break;
    + default:
    + return 0;
    + }
    +
    + return 0;
    +}
    +
    +static const struct hwmon_ops occ_hwmon_ops = {
    + .is_visible = occ_is_visible,
    + .read = occ_hwmon_read,
    + .read_string = occ_hwmon_read_string,
    + .write = occ_hwmon_write,
    +};
    +
    +static const enum hwmon_sensor_types occ_sensor_types[MAX_OCC_SENSOR_TYPE] = {
    + hwmon_in,
    + hwmon_temp,
    + hwmon_power,
    + hwmon_power
    +};
    +
    +struct occ_sysfs *occ_sysfs_start(struct device *dev, struct occ *occ,
    + const u32 *sensor_hwmon_configs,
    + const char *name)
    +{
    + int rc, i, j, num_sensors, index = 0, id;
    + char *brk;
    + struct occ_blocks *resp = NULL;
    + u32 *sensor_config;
    + struct occ_sysfs *hwmon = devm_kzalloc(dev, sizeof(struct occ_sysfs),
    + GFP_KERNEL);
    + if (!hwmon)
    + return ERR_PTR(-ENOMEM);
    +
    + /* need space for null-termination */
    + hwmon->occ_sensors =
    + devm_kzalloc(dev, sizeof(struct hwmon_channel_info *) *
    + (MAX_OCC_SENSOR_TYPE + 1), GFP_KERNEL);
    + if (!hwmon->occ_sensors)
    + return ERR_PTR(-ENOMEM);
    +
    + hwmon->occ = occ;
    + hwmon->sensor_hwmon_configs = sensor_hwmon_configs;
    + hwmon->occ_info.ops = &occ_hwmon_ops;
    + hwmon->occ_info.info =
    + (const struct hwmon_channel_info **)hwmon->occ_sensors;
    +
    + occ_get_response_blocks(occ, &resp);
    +
    + for (i = 0; i < MAX_OCC_SENSOR_TYPE; ++i)
    + resp->sensor_block_id[i] = -1;
    +
    + /* read sensor data from occ */
    + rc = occ_update_device(occ);
    + if (rc)
    + return ERR_PTR(rc);
    +
    + for (i = 0; i < MAX_OCC_SENSOR_TYPE; i++) {
    + id = resp->sensor_block_id[i];
    + if (id < 0)
    + continue;
    +
    + num_sensors = resp->blocks[id].header.num_sensors;
    + /* need null-termination */
    + sensor_config = devm_kzalloc(dev,
    + sizeof(u32) * (num_sensors + 1),
    + GFP_KERNEL);
    + if (!sensor_config)
    + return ERR_PTR(-ENOMEM);
    +
    + for (j = 0; j < num_sensors; j++)
    + sensor_config[j] = sensor_hwmon_configs[i];
    +
    + hwmon->occ_sensors[index] =
    + devm_kzalloc(dev, sizeof(struct hwmon_channel_info),
    + GFP_KERNEL);
    + if (!hwmon->occ_sensors[index])
    + return ERR_PTR(-ENOMEM);
    +
    + hwmon->occ_sensors[index]->type = occ_sensor_types[i];
    + hwmon->occ_sensors[index]->config = sensor_config;
    + index++;
    + }
    +
    + /* search for bad chars */
    + strncpy(hwmon->hwmon_name, name, OCC_HWMON_NAME_LENGTH);
    + brk = strpbrk(hwmon->hwmon_name, "-* \t\n");
    + while (brk) {
    + *brk = '_';
    + brk = strpbrk(brk, "-* \t\n");
    + }
    +
    + hwmon->dev = devm_hwmon_device_register_with_info(dev,
    + hwmon->hwmon_name,
    + hwmon,
    + &hwmon->occ_info,
    + NULL);
    + if (IS_ERR(hwmon->dev)) {
    + dev_err(dev, "cannot register hwmon device %s: %ld\n",
    + hwmon->hwmon_name, PTR_ERR(hwmon->dev));
    + return ERR_CAST(hwmon->dev);
    + }
    +
    + return hwmon;
    +}
    +EXPORT_SYMBOL(occ_sysfs_start);
    +
    +MODULE_AUTHOR("Eddie James <eajames@us.ibm.com>");
    +MODULE_DESCRIPTION("OCC sysfs driver");
    +MODULE_LICENSE("GPL");
    diff --git a/drivers/hwmon/occ/occ_sysfs.h b/drivers/hwmon/occ/occ_sysfs.h
    new file mode 100644
    index 0000000..462f9d2
    --- /dev/null
    +++ b/drivers/hwmon/occ/occ_sysfs.h
    @@ -0,0 +1,30 @@
    +/*
    + * occ_sysfs.h - OCC sysfs interface
    + *
    + * This file contains the data structures and function prototypes for the OCC
    + * hwmon sysfs entries.
    + *
    + * Copyright 2016 IBM Corp.
    + *
    + * 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.
    + */
    +
    +#ifndef __OCC_SYSFS_H__
    +#define __OCC_SYSFS_H__
    +
    +struct occ;
    +struct device;
    +struct occ_sysfs;
    +
    +struct occ_sysfs *occ_sysfs_start(struct device *dev, struct occ *occ,
    + const u32 *sensor_hwmon_configs,
    + const char *name);
    +#endif /* __OCC_SYSFS_H__ */
    --
    1.9.1
    \
     
     \ /
      Last update: 2017-01-27 00:20    [W:2.284 / U:0.028 seconds]
    ©2003-2020 Jasper Spaans|hosted at Digital Ocean and TransIP|Read the blog|Advertise on this site