From e40f7d10d5306c075e2fc7e538ce1efc70eeb448 Mon Sep 17 00:00:00 2001 From: ZhikuiRen Date: Thu, 9 Jan 2020 10:48:00 -0800 Subject: [PATCH] Add peci-cpupower driver peci-cpupower reads CPU energy counter through peci and computes average power in mW since last read. Signed-off-by: ZhikuiRen --- Documentation/hwmon/index.rst | 1 + Documentation/hwmon/peci-cpupower.rst | 52 ++++++++ arch/arm/configs/aspeed_g5_defconfig | 1 + drivers/hwmon/Kconfig | 14 +++ drivers/hwmon/Makefile | 1 + drivers/hwmon/peci-cpupower.c | 231 ++++++++++++++++++++++++++++++++++ drivers/mfd/intel-peci-client.c | 1 + include/uapi/linux/peci-ioctl.h | 1 + 8 files changed, 302 insertions(+) create mode 100644 Documentation/hwmon/peci-cpupower.rst create mode 100644 drivers/hwmon/peci-cpupower.c diff --git a/Documentation/hwmon/index.rst b/Documentation/hwmon/index.rst index 7d894c9..0bde0ef 100644 --- a/Documentation/hwmon/index.rst +++ b/Documentation/hwmon/index.rst @@ -130,6 +130,7 @@ Hardware Monitoring Kernel Drivers pcf8591 peci-cputemp peci-dimmtemp + peci-cpupower pmbus powr1220 pxe1610 diff --git a/Documentation/hwmon/peci-cpupower.rst b/Documentation/hwmon/peci-cpupower.rst new file mode 100644 index 0000000..4d7bd61 --- /dev/null +++ b/Documentation/hwmon/peci-cpupower.rst @@ -0,0 +1,52 @@ +.. SPDX-License-Identifier: GPL-2.0 + +Kernel driver peci-cpupower +========================== + +:Copyright: |copy| 2018-2020 Intel Corporation + +Supported chips: + One of Intel server CPUs listed below which is connected to a PECI bus. + * Intel Xeon E5/E7 v3 server processors + Intel Xeon E5-14xx v3 family + Intel Xeon E5-24xx v3 family + Intel Xeon E5-16xx v3 family + Intel Xeon E5-26xx v3 family + Intel Xeon E5-46xx v3 family + Intel Xeon E7-48xx v3 family + Intel Xeon E7-88xx v3 family + * Intel Xeon E5/E7 v4 server processors + Intel Xeon E5-16xx v4 family + Intel Xeon E5-26xx v4 family + Intel Xeon E5-46xx v4 family + Intel Xeon E7-48xx v4 family + Intel Xeon E7-88xx v4 family + * Intel Xeon Scalable server processors + Intel Xeon D family + Intel Xeon Bronze family + Intel Xeon Silver family + Intel Xeon Gold family + Intel Xeon Platinum family + + Addresses scanned: PECI client address 0x30 - 0x37 + Datasheet: Available from http://www.intel.com/design/literature.htm + +Author: + Zhikui Ren + +Description +----------- + +This driver implements a generic PECI hwmon feature which provides +average power consumption readings of the CPU package based on energy counter +accessible using the PECI Client Command Suite via the processor PECI client. + +Power values are average power since last measure given in milli Watt and +will be measurable only when the target CPU is powered on. + +``sysfs`` interface +------------------- +======================= ======================================================= +power1_average Provides average power since last read in milli Watt. +power1_label Provides string "Average Power". +======================= ======================================================= diff --git a/arch/arm/configs/aspeed_g5_defconfig b/arch/arm/configs/aspeed_g5_defconfig index 1e589a0..0e50040 100644 --- a/arch/arm/configs/aspeed_g5_defconfig +++ b/arch/arm/configs/aspeed_g5_defconfig @@ -177,6 +177,7 @@ CONFIG_SENSORS_OCC_P8_I2C=y CONFIG_SENSORS_OCC_P9_SBE=y CONFIG_SENSORS_PECI_CPUTEMP=y CONFIG_SENSORS_PECI_DIMMTEMP=y +CONFIG_SENSORS_PECI_CPUPOWER=y CONFIG_PMBUS=y CONFIG_SENSORS_ADM1275=y CONFIG_SENSORS_IBM_CFFPS=y diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig index 8312b37..07d8826 100644 --- a/drivers/hwmon/Kconfig +++ b/drivers/hwmon/Kconfig @@ -1361,6 +1361,20 @@ config SENSORS_PECI_DIMMTEMP This driver can also be built as a module. If so, the module will be called peci-dimmtemp. +config SENSORS_PECI_CPUPOWER + tristate "PECI CPU power monitoring support" + depends on PECI + select MFD_INTEL_PECI_CLIENT + help + If you say yes here you get support for the generic Intel PECI + cputemp driver which provides average engergy + readings of the CPU package using + the PECI Client Command Suite via the processor PECI client. + Check Documentation/hwmon/peci-cpupower for details. + + This driver can also be built as a module. If so, the module + will be called peci-cpupower. + source "drivers/hwmon/pmbus/Kconfig" config SENSORS_PWM_FAN diff --git a/drivers/hwmon/Makefile b/drivers/hwmon/Makefile index e74ea92..fab43fd 100644 --- a/drivers/hwmon/Makefile +++ b/drivers/hwmon/Makefile @@ -144,6 +144,7 @@ obj-$(CONFIG_SENSORS_PC87427) += pc87427.o obj-$(CONFIG_SENSORS_PCF8591) += pcf8591.o obj-$(CONFIG_SENSORS_PECI_CPUTEMP) += peci-cputemp.o obj-$(CONFIG_SENSORS_PECI_DIMMTEMP) += peci-dimmtemp.o +obj-$(CONFIG_SENSORS_PECI_CPUPOWER) += peci-cpupower.o obj-$(CONFIG_SENSORS_POWR1220) += powr1220.o obj-$(CONFIG_SENSORS_PWM_FAN) += pwm-fan.o obj-$(CONFIG_SENSORS_RASPBERRYPI_HWMON) += raspberrypi-hwmon.o diff --git a/drivers/hwmon/peci-cpupower.c b/drivers/hwmon/peci-cpupower.c new file mode 100644 index 0000000..6907696 --- /dev/null +++ b/drivers/hwmon/peci-cpupower.c @@ -0,0 +1,231 @@ +// SPDX-License-Identifier: GPL-2.0 +// Copyright (c) 2018-2020 Intel Corporation + +#include +#include +#include +#include +#include +#include +#include "peci-hwmon.h" + +#define POWER_DEFAULT_CHANNEL_NUMS 1 + +struct peci_cpupower { + struct peci_client_manager *mgr; + struct device *dev; + char name[PECI_NAME_SIZE]; + const struct cpu_gen_info *gen_info; + struct peci_sensor_data energy; + long avg_power_val; + u64 core_mask; + u32 power_config[POWER_DEFAULT_CHANNEL_NUMS + 1]; + uint config_idx; + struct hwmon_channel_info power_info; + const struct hwmon_channel_info *info[2]; + struct hwmon_chip_info chip; +}; + +enum cpupower_channels { + average_power, +}; + +static const u32 config_table[POWER_DEFAULT_CHANNEL_NUMS] = { + /* average power */ + HWMON_P_LABEL | HWMON_P_AVERAGE, +}; + +static const char *cpupower_label[POWER_DEFAULT_CHANNEL_NUMS] = { + "Average Power", +}; + +static int get_average_power(struct peci_cpupower *priv) +{ + u8 pkg_cfg[4]; + int ret; + + if (!peci_sensor_need_update(&priv->energy)) + return 0; + + ret = peci_client_read_package_config(priv->mgr, + PECI_MBX_INDEX_TDP_UNITS, + PECI_PKG_ID_PKG_ENERGY_STATUS, + pkg_cfg); + + u32 power_unit = ((le32_to_cpup((__le32 *)pkg_cfg)) & 0x1f00) >> 8; + + dev_dbg(priv->dev, "cpupower units %d (1J/pow(2, unit))\n", + power_unit); + + ret = peci_client_read_package_config(priv->mgr, + PECI_MBX_INDEX_ENERGY_COUNTER, + PECI_PKG_ID_PKG_ENERGY_STATUS, + pkg_cfg); + if (!ret) { + u32 energy_cnt = le32_to_cpup((__le32 *)pkg_cfg); + ulong jif = jiffies; + ulong elapsed = (jif - priv->energy.last_updated); + long power_val = 0; + /* + * Don't calculate average power for first counter read or + * counter wrapped around or last counter read was more than + * 60 minutes ago (jiffies did not wrap and power calculation + * does not overflow or underflow + */ + if (priv->energy.last_updated > 0 && + energy_cnt > priv->energy.value && + (elapsed < (HZ * 3600))) { + power_val = (long)(energy_cnt - priv->energy.value) + / elapsed * HZ; + dev_dbg(priv->dev, "countDiff %d, jiffes elapsed %d, raw powerValue %d scale to %d mW\n", + (long)(energy_cnt - priv->energy.value), + elapsed, power_val, + power_val >> (power_unit - 10)); + } else { + dev_dbg(priv->dev, "countDiff %d, jiffes elapsed %d, skipping calculate power, try agin\n", + (long)(energy_cnt - priv->energy.value), + elapsed); + ret = -EAGAIN; + } + + priv->energy.value = energy_cnt; + priv->avg_power_val = power_val >> ((power_unit - 10)); + peci_sensor_mark_updated(&priv->energy); + + dev_dbg(priv->dev, "energy counter 0x%8x, average power %dmW, jif %u, HZ is %d jiffies\n", + priv->energy.value, priv->avg_power_val, + jif, HZ); + } + return ret; +} + +static int cpupower_read_string(struct device *dev, + enum hwmon_sensor_types type, + u32 attr, int channel, const char **str) +{ + if (attr != hwmon_power_label) + return -EOPNOTSUPP; + if (channel >= POWER_DEFAULT_CHANNEL_NUMS) + return -EOPNOTSUPP; + *str = cpupower_label[channel]; + + return 0; +} + +static int cpupower_read(struct device *dev, + enum hwmon_sensor_types type, + u32 attr, int channel, long *val) +{ + struct peci_cpupower *priv = dev_get_drvdata(dev); + int ret; + + if (channel >= POWER_DEFAULT_CHANNEL_NUMS || + !(priv->power_config[channel] & BIT(attr))) + return -EOPNOTSUPP; + + switch (attr) { + case hwmon_power_average: + switch (channel) { + case average_power: + ret = get_average_power(priv); + if (ret) + break; + + *val = priv->avg_power_val; + break; + default: + break; + } + break; + default: + ret = -EOPNOTSUPP; + break; + } + + return ret; +} + +static umode_t cpupower_is_visible(const void *data, + enum hwmon_sensor_types type, + u32 attr, int channel) +{ + const struct peci_cpupower *priv = data; + + if (channel < POWER_DEFAULT_CHANNEL_NUMS || + (priv->power_config[channel] & BIT(attr))) + return 0444; + + return 0; +} + +static const struct hwmon_ops cpupower_ops = { + .is_visible = cpupower_is_visible, + .read_string = cpupower_read_string, + .read = cpupower_read, +}; + +static int peci_cpupower_probe(struct platform_device *pdev) +{ + struct peci_client_manager *mgr = dev_get_drvdata(pdev->dev.parent); + struct device *dev = &pdev->dev; + struct peci_cpupower *priv; + struct device *hwmon_dev; + + if ((mgr->client->adapter->cmd_mask & + (BIT(PECI_CMD_RD_PKG_CFG))) != + (BIT(PECI_CMD_RD_PKG_CFG))) { + return -ENODEV; + } + + priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + dev_set_drvdata(dev, priv); + priv->mgr = mgr; + priv->dev = dev; + priv->gen_info = mgr->gen_info; + + snprintf(priv->name, PECI_NAME_SIZE, "peci_cpupower.cpu%d", + mgr->client->addr - PECI_BASE_ADDR); + + priv->power_config[priv->config_idx++] = config_table[average_power]; + + priv->chip.ops = &cpupower_ops; + priv->chip.info = priv->info; + + priv->info[0] = &priv->power_info; + + priv->power_info.type = hwmon_power; + priv->power_info.config = priv->power_config; + + hwmon_dev = devm_hwmon_device_register_with_info(priv->dev, + priv->name, + priv, + &priv->chip, + NULL); + + if (IS_ERR(hwmon_dev)) + return PTR_ERR(hwmon_dev); + + dev_dbg(dev, "%s: sensor '%s'\n", dev_name(hwmon_dev), priv->name); + + return 0; +} + +static const struct platform_device_id peci_cpupower_ids[] = { + { .name = "peci-cpupower", .driver_data = 0 }, + { } +}; +MODULE_DEVICE_TABLE(platform, peci_cpupower_ids); + +static struct platform_driver peci_cpupower_driver = { + .probe = peci_cpupower_probe, + .id_table = peci_cpupower_ids, + .driver = { .name = KBUILD_MODNAME, }, +}; +module_platform_driver(peci_cpupower_driver); + +MODULE_AUTHOR("Zhikui Ren "); +MODULE_DESCRIPTION("PECI cpupower driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/mfd/intel-peci-client.c b/drivers/mfd/intel-peci-client.c index 9751b04..0c62ad6 100644 --- a/drivers/mfd/intel-peci-client.c +++ b/drivers/mfd/intel-peci-client.c @@ -21,6 +21,7 @@ static struct mfd_cell peci_functions[] = { { .name = "peci-cputemp", }, { .name = "peci-dimmtemp", }, + { .name = "peci-cpupower", }, }; static const struct cpu_gen_info cpu_gen_info_table[] = { diff --git a/include/uapi/linux/peci-ioctl.h b/include/uapi/linux/peci-ioctl.h index 843930f..d16f7c9 100644 --- a/include/uapi/linux/peci-ioctl.h +++ b/include/uapi/linux/peci-ioctl.h @@ -231,6 +231,7 @@ struct peci_rd_pkg_cfg_msg { #define PECI_PKG_ID_MAX_THREAD_ID 0x0003 /* Max Thread ID */ #define PECI_PKG_ID_MICROCODE_REV 0x0004 /* CPU Microcode Update Revision */ #define PECI_PKG_ID_MACHINE_CHECK_STATUS 0x0005 /* Machine Check Status */ +#define PECI_PKG_ID_PKG_ENERGY_STATUS 0x00ff /* Average Energy */ __u8 rx_len; __u8 cc; -- 2.7.4