kernel_optimize_test/drivers/hwmon/mlxreg-fan.c
Vadim Pasternak 76bbb482d3 hwmon: (mlxreg-fan) Return non-zero value when fan current state is enforced from sysfs
[ Upstream commit e6fab7af6ba1bc77c78713a83876f60ca7a4a064 ]

Fan speed minimum can be enforced from sysfs. For example, setting
current fan speed to 20 is used to enforce fan speed to be at 100%
speed, 19 - to be not below 90% speed, etcetera. This feature provides
ability to limit fan speed according to some system wise
considerations, like absence of some replaceable units or high system
ambient temperature.

Request for changing fan minimum speed is configuration request and can
be set only through 'sysfs' write procedure. In this situation value of
argument 'state' is above nominal fan speed maximum.

Return non-zero code in this case to avoid
thermal_cooling_device_stats_update() call, because in this case
statistics update violates thermal statistics table range.
The issues is observed in case kernel is configured with option
CONFIG_THERMAL_STATISTICS.

Here is the trace from KASAN:
[  159.506659] BUG: KASAN: slab-out-of-bounds in thermal_cooling_device_stats_update+0x7d/0xb0
[  159.516016] Read of size 4 at addr ffff888116163840 by task hw-management.s/7444
[  159.545625] Call Trace:
[  159.548366]  dump_stack+0x92/0xc1
[  159.552084]  ? thermal_cooling_device_stats_update+0x7d/0xb0
[  159.635869]  thermal_zone_device_update+0x345/0x780
[  159.688711]  thermal_zone_device_set_mode+0x7d/0xc0
[  159.694174]  mlxsw_thermal_modules_init+0x48f/0x590 [mlxsw_core]
[  159.700972]  ? mlxsw_thermal_set_cur_state+0x5a0/0x5a0 [mlxsw_core]
[  159.731827]  mlxsw_thermal_init+0x763/0x880 [mlxsw_core]
[  160.070233] RIP: 0033:0x7fd995909970
[  160.074239] Code: 73 01 c3 48 8b 0d 28 d5 2b 00 f7 d8 64 89 01 48 83 c8 ff c3 66 0f 1f 44 00 00 83 3d 99 2d 2c 00 00 75 10 b8 01 00 00 00 0f 05 <48> 3d 01 f0 ff ..
[  160.095242] RSP: 002b:00007fff54f5d938 EFLAGS: 00000246 ORIG_RAX: 0000000000000001
[  160.103722] RAX: ffffffffffffffda RBX: 0000000000000013 RCX: 00007fd995909970
[  160.111710] RDX: 0000000000000013 RSI: 0000000001906008 RDI: 0000000000000001
[  160.119699] RBP: 0000000001906008 R08: 00007fd995bc9760 R09: 00007fd996210700
[  160.127687] R10: 0000000000000073 R11: 0000000000000246 R12: 0000000000000013
[  160.135673] R13: 0000000000000001 R14: 00007fd995bc8600 R15: 0000000000000013
[  160.143671]
[  160.145338] Allocated by task 2924:
[  160.149242]  kasan_save_stack+0x19/0x40
[  160.153541]  __kasan_kmalloc+0x7f/0xa0
[  160.157743]  __kmalloc+0x1a2/0x2b0
[  160.161552]  thermal_cooling_device_setup_sysfs+0xf9/0x1a0
[  160.167687]  __thermal_cooling_device_register+0x1b5/0x500
[  160.173833]  devm_thermal_of_cooling_device_register+0x60/0xa0
[  160.180356]  mlxreg_fan_probe+0x474/0x5e0 [mlxreg_fan]
[  160.248140]
[  160.249807] The buggy address belongs to the object at ffff888116163400
[  160.249807]  which belongs to the cache kmalloc-1k of size 1024
[  160.263814] The buggy address is located 64 bytes to the right of
[  160.263814]  1024-byte region [ffff888116163400, ffff888116163800)
[  160.277536] The buggy address belongs to the page:
[  160.282898] page:0000000012275840 refcount:1 mapcount:0 mapping:0000000000000000 index:0xffff888116167000 pfn:0x116160
[  160.294872] head:0000000012275840 order:3 compound_mapcount:0 compound_pincount:0
[  160.303251] flags: 0x200000000010200(slab|head|node=0|zone=2)
[  160.309694] raw: 0200000000010200 ffffea00046f7208 ffffea0004928208 ffff88810004dbc0
[  160.318367] raw: ffff888116167000 00000000000a0006 00000001ffffffff 0000000000000000
[  160.327033] page dumped because: kasan: bad access detected
[  160.333270]
[  160.334937] Memory state around the buggy address:
[  160.356469] >ffff888116163800: fc ..

Fixes: 65afb4c8e7 ("hwmon: (mlxreg-fan) Add support for Mellanox FAN driver")
Signed-off-by: Vadim Pasternak <vadimp@nvidia.com>
Link: https://lore.kernel.org/r/20210916183151.869427-1-vadimp@nvidia.com
Signed-off-by: Guenter Roeck <linux@roeck-us.net>
Signed-off-by: Sasha Levin <sashal@kernel.org>
2021-10-06 15:55:51 +02:00

530 lines
14 KiB
C

// SPDX-License-Identifier: (GPL-2.0 OR BSD-3-Clause)
//
// Copyright (c) 2018 Mellanox Technologies. All rights reserved.
// Copyright (c) 2018 Vadim Pasternak <vadimp@mellanox.com>
#include <linux/bitops.h>
#include <linux/device.h>
#include <linux/hwmon.h>
#include <linux/module.h>
#include <linux/platform_data/mlxreg.h>
#include <linux/platform_device.h>
#include <linux/regmap.h>
#include <linux/thermal.h>
#define MLXREG_FAN_MAX_TACHO 12
#define MLXREG_FAN_MAX_STATE 10
#define MLXREG_FAN_MIN_DUTY 51 /* 20% */
#define MLXREG_FAN_MAX_DUTY 255 /* 100% */
/*
* Minimum and maximum FAN allowed speed in percent: from 20% to 100%. Values
* MLXREG_FAN_MAX_STATE + x, where x is between 2 and 10 are used for
* setting FAN speed dynamic minimum. For example, if value is set to 14 (40%)
* cooling levels vector will be set to 4, 4, 4, 4, 4, 5, 6, 7, 8, 9, 10 to
* introduce PWM speed in percent: 40, 40, 40, 40, 40, 50, 60. 70, 80, 90, 100.
*/
#define MLXREG_FAN_SPEED_MIN (MLXREG_FAN_MAX_STATE + 2)
#define MLXREG_FAN_SPEED_MAX (MLXREG_FAN_MAX_STATE * 2)
#define MLXREG_FAN_SPEED_MIN_LEVEL 2 /* 20 percent */
#define MLXREG_FAN_TACHO_SAMPLES_PER_PULSE_DEF 44
#define MLXREG_FAN_TACHO_DIV_MIN 283
#define MLXREG_FAN_TACHO_DIV_DEF (MLXREG_FAN_TACHO_DIV_MIN * 4)
#define MLXREG_FAN_TACHO_DIV_SCALE_MAX 64
/*
* FAN datasheet defines the formula for RPM calculations as RPM = 15/t-high.
* The logic in a programmable device measures the time t-high by sampling the
* tachometer every t-sample (with the default value 11.32 uS) and increment
* a counter (N) as long as the pulse has not change:
* RPM = 15 / (t-sample * (K + Regval)), where:
* Regval: is the value read from the programmable device register;
* - 0xff - represents tachometer fault;
* - 0xfe - represents tachometer minimum value , which is 4444 RPM;
* - 0x00 - represents tachometer maximum value , which is 300000 RPM;
* K: is 44 and it represents the minimum allowed samples per pulse;
* N: is equal K + Regval;
* In order to calculate RPM from the register value the following formula is
* used: RPM = 15 / ((Regval + K) * 11.32) * 10^(-6)), which in the
* default case is modified to:
* RPM = 15000000 * 100 / ((Regval + 44) * 1132);
* - for Regval 0x00, RPM will be 15000000 * 100 / (44 * 1132) = 30115;
* - for Regval 0xfe, RPM will be 15000000 * 100 / ((254 + 44) * 1132) = 4446;
* In common case the formula is modified to:
* RPM = 15000000 * 100 / ((Regval + samples) * divider).
*/
#define MLXREG_FAN_GET_RPM(rval, d, s) (DIV_ROUND_CLOSEST(15000000 * 100, \
((rval) + (s)) * (d)))
#define MLXREG_FAN_GET_FAULT(val, mask) ((val) == (mask))
#define MLXREG_FAN_PWM_DUTY2STATE(duty) (DIV_ROUND_CLOSEST((duty) * \
MLXREG_FAN_MAX_STATE, \
MLXREG_FAN_MAX_DUTY))
#define MLXREG_FAN_PWM_STATE2DUTY(stat) (DIV_ROUND_CLOSEST((stat) * \
MLXREG_FAN_MAX_DUTY, \
MLXREG_FAN_MAX_STATE))
/*
* struct mlxreg_fan_tacho - tachometer data (internal use):
*
* @connected: indicates if tachometer is connected;
* @reg: register offset;
* @mask: fault mask;
*/
struct mlxreg_fan_tacho {
bool connected;
u32 reg;
u32 mask;
};
/*
* struct mlxreg_fan_pwm - PWM data (internal use):
*
* @connected: indicates if PWM is connected;
* @reg: register offset;
*/
struct mlxreg_fan_pwm {
bool connected;
u32 reg;
};
/*
* struct mlxreg_fan - private data (internal use):
*
* @dev: basic device;
* @regmap: register map of parent device;
* @tacho: tachometer data;
* @pwm: PWM data;
* @samples: minimum allowed samples per pulse;
* @divider: divider value for tachometer RPM calculation;
* @cooling: cooling device levels;
* @cdev: cooling device;
*/
struct mlxreg_fan {
struct device *dev;
void *regmap;
struct mlxreg_core_platform_data *pdata;
struct mlxreg_fan_tacho tacho[MLXREG_FAN_MAX_TACHO];
struct mlxreg_fan_pwm pwm;
int samples;
int divider;
u8 cooling_levels[MLXREG_FAN_MAX_STATE + 1];
struct thermal_cooling_device *cdev;
};
static int
mlxreg_fan_read(struct device *dev, enum hwmon_sensor_types type, u32 attr,
int channel, long *val)
{
struct mlxreg_fan *fan = dev_get_drvdata(dev);
struct mlxreg_fan_tacho *tacho;
u32 regval;
int err;
switch (type) {
case hwmon_fan:
tacho = &fan->tacho[channel];
switch (attr) {
case hwmon_fan_input:
err = regmap_read(fan->regmap, tacho->reg, &regval);
if (err)
return err;
*val = MLXREG_FAN_GET_RPM(regval, fan->divider,
fan->samples);
break;
case hwmon_fan_fault:
err = regmap_read(fan->regmap, tacho->reg, &regval);
if (err)
return err;
*val = MLXREG_FAN_GET_FAULT(regval, tacho->mask);
break;
default:
return -EOPNOTSUPP;
}
break;
case hwmon_pwm:
switch (attr) {
case hwmon_pwm_input:
err = regmap_read(fan->regmap, fan->pwm.reg, &regval);
if (err)
return err;
*val = regval;
break;
default:
return -EOPNOTSUPP;
}
break;
default:
return -EOPNOTSUPP;
}
return 0;
}
static int
mlxreg_fan_write(struct device *dev, enum hwmon_sensor_types type, u32 attr,
int channel, long val)
{
struct mlxreg_fan *fan = dev_get_drvdata(dev);
switch (type) {
case hwmon_pwm:
switch (attr) {
case hwmon_pwm_input:
if (val < MLXREG_FAN_MIN_DUTY ||
val > MLXREG_FAN_MAX_DUTY)
return -EINVAL;
return regmap_write(fan->regmap, fan->pwm.reg, val);
default:
return -EOPNOTSUPP;
}
break;
default:
return -EOPNOTSUPP;
}
return -EOPNOTSUPP;
}
static umode_t
mlxreg_fan_is_visible(const void *data, enum hwmon_sensor_types type, u32 attr,
int channel)
{
switch (type) {
case hwmon_fan:
if (!(((struct mlxreg_fan *)data)->tacho[channel].connected))
return 0;
switch (attr) {
case hwmon_fan_input:
case hwmon_fan_fault:
return 0444;
default:
break;
}
break;
case hwmon_pwm:
if (!(((struct mlxreg_fan *)data)->pwm.connected))
return 0;
switch (attr) {
case hwmon_pwm_input:
return 0644;
default:
break;
}
break;
default:
break;
}
return 0;
}
static const struct hwmon_channel_info *mlxreg_fan_hwmon_info[] = {
HWMON_CHANNEL_INFO(fan,
HWMON_F_INPUT | HWMON_F_FAULT,
HWMON_F_INPUT | HWMON_F_FAULT,
HWMON_F_INPUT | HWMON_F_FAULT,
HWMON_F_INPUT | HWMON_F_FAULT,
HWMON_F_INPUT | HWMON_F_FAULT,
HWMON_F_INPUT | HWMON_F_FAULT,
HWMON_F_INPUT | HWMON_F_FAULT,
HWMON_F_INPUT | HWMON_F_FAULT,
HWMON_F_INPUT | HWMON_F_FAULT,
HWMON_F_INPUT | HWMON_F_FAULT,
HWMON_F_INPUT | HWMON_F_FAULT,
HWMON_F_INPUT | HWMON_F_FAULT),
HWMON_CHANNEL_INFO(pwm,
HWMON_PWM_INPUT),
NULL
};
static const struct hwmon_ops mlxreg_fan_hwmon_hwmon_ops = {
.is_visible = mlxreg_fan_is_visible,
.read = mlxreg_fan_read,
.write = mlxreg_fan_write,
};
static const struct hwmon_chip_info mlxreg_fan_hwmon_chip_info = {
.ops = &mlxreg_fan_hwmon_hwmon_ops,
.info = mlxreg_fan_hwmon_info,
};
static int mlxreg_fan_get_max_state(struct thermal_cooling_device *cdev,
unsigned long *state)
{
*state = MLXREG_FAN_MAX_STATE;
return 0;
}
static int mlxreg_fan_get_cur_state(struct thermal_cooling_device *cdev,
unsigned long *state)
{
struct mlxreg_fan *fan = cdev->devdata;
u32 regval;
int err;
err = regmap_read(fan->regmap, fan->pwm.reg, &regval);
if (err) {
dev_err(fan->dev, "Failed to query PWM duty\n");
return err;
}
*state = MLXREG_FAN_PWM_DUTY2STATE(regval);
return 0;
}
static int mlxreg_fan_set_cur_state(struct thermal_cooling_device *cdev,
unsigned long state)
{
struct mlxreg_fan *fan = cdev->devdata;
unsigned long cur_state;
int i, config = 0;
u32 regval;
int err;
/*
* Verify if this request is for changing allowed FAN dynamical
* minimum. If it is - update cooling levels accordingly and update
* state, if current state is below the newly requested minimum state.
* For example, if current state is 5, and minimal state is to be
* changed from 4 to 6, fan->cooling_levels[0 to 5] will be changed all
* from 4 to 6. And state 5 (fan->cooling_levels[4]) should be
* overwritten.
*/
if (state >= MLXREG_FAN_SPEED_MIN && state <= MLXREG_FAN_SPEED_MAX) {
/*
* This is configuration change, which is only supported through sysfs.
* For configuration non-zero value is to be returned to avoid thermal
* statistics update.
*/
config = 1;
state -= MLXREG_FAN_MAX_STATE;
for (i = 0; i < state; i++)
fan->cooling_levels[i] = state;
for (i = state; i <= MLXREG_FAN_MAX_STATE; i++)
fan->cooling_levels[i] = i;
err = regmap_read(fan->regmap, fan->pwm.reg, &regval);
if (err) {
dev_err(fan->dev, "Failed to query PWM duty\n");
return err;
}
cur_state = MLXREG_FAN_PWM_DUTY2STATE(regval);
if (state < cur_state)
return config;
state = cur_state;
}
if (state > MLXREG_FAN_MAX_STATE)
return -EINVAL;
/* Normalize the state to the valid speed range. */
state = fan->cooling_levels[state];
err = regmap_write(fan->regmap, fan->pwm.reg,
MLXREG_FAN_PWM_STATE2DUTY(state));
if (err) {
dev_err(fan->dev, "Failed to write PWM duty\n");
return err;
}
return config;
}
static const struct thermal_cooling_device_ops mlxreg_fan_cooling_ops = {
.get_max_state = mlxreg_fan_get_max_state,
.get_cur_state = mlxreg_fan_get_cur_state,
.set_cur_state = mlxreg_fan_set_cur_state,
};
static int mlxreg_fan_connect_verify(struct mlxreg_fan *fan,
struct mlxreg_core_data *data)
{
u32 regval;
int err;
err = regmap_read(fan->regmap, data->capability, &regval);
if (err) {
dev_err(fan->dev, "Failed to query capability register 0x%08x\n",
data->capability);
return err;
}
return !!(regval & data->bit);
}
static int mlxreg_fan_speed_divider_get(struct mlxreg_fan *fan,
struct mlxreg_core_data *data)
{
u32 regval;
int err;
err = regmap_read(fan->regmap, data->capability, &regval);
if (err) {
dev_err(fan->dev, "Failed to query capability register 0x%08x\n",
data->capability);
return err;
}
/*
* Set divider value according to the capability register, in case it
* contains valid value. Otherwise use default value. The purpose of
* this validation is to protect against the old hardware, in which
* this register can return zero.
*/
if (regval > 0 && regval <= MLXREG_FAN_TACHO_DIV_SCALE_MAX)
fan->divider = regval * MLXREG_FAN_TACHO_DIV_MIN;
return 0;
}
static int mlxreg_fan_config(struct mlxreg_fan *fan,
struct mlxreg_core_platform_data *pdata)
{
struct mlxreg_core_data *data = pdata->data;
bool configured = false;
int tacho_num = 0, i;
int err;
fan->samples = MLXREG_FAN_TACHO_SAMPLES_PER_PULSE_DEF;
fan->divider = MLXREG_FAN_TACHO_DIV_DEF;
for (i = 0; i < pdata->counter; i++, data++) {
if (strnstr(data->label, "tacho", sizeof(data->label))) {
if (tacho_num == MLXREG_FAN_MAX_TACHO) {
dev_err(fan->dev, "too many tacho entries: %s\n",
data->label);
return -EINVAL;
}
if (data->capability) {
err = mlxreg_fan_connect_verify(fan, data);
if (err < 0)
return err;
else if (!err) {
tacho_num++;
continue;
}
}
fan->tacho[tacho_num].reg = data->reg;
fan->tacho[tacho_num].mask = data->mask;
fan->tacho[tacho_num++].connected = true;
} else if (strnstr(data->label, "pwm", sizeof(data->label))) {
if (fan->pwm.connected) {
dev_err(fan->dev, "duplicate pwm entry: %s\n",
data->label);
return -EINVAL;
}
fan->pwm.reg = data->reg;
fan->pwm.connected = true;
} else if (strnstr(data->label, "conf", sizeof(data->label))) {
if (configured) {
dev_err(fan->dev, "duplicate conf entry: %s\n",
data->label);
return -EINVAL;
}
/* Validate that conf parameters are not zeros. */
if (!data->mask && !data->bit && !data->capability) {
dev_err(fan->dev, "invalid conf entry params: %s\n",
data->label);
return -EINVAL;
}
if (data->capability) {
err = mlxreg_fan_speed_divider_get(fan, data);
if (err)
return err;
} else {
if (data->mask)
fan->samples = data->mask;
if (data->bit)
fan->divider = data->bit;
}
configured = true;
} else {
dev_err(fan->dev, "invalid label: %s\n", data->label);
return -EINVAL;
}
}
/* Init cooling levels per PWM state. */
for (i = 0; i < MLXREG_FAN_SPEED_MIN_LEVEL; i++)
fan->cooling_levels[i] = MLXREG_FAN_SPEED_MIN_LEVEL;
for (i = MLXREG_FAN_SPEED_MIN_LEVEL; i <= MLXREG_FAN_MAX_STATE; i++)
fan->cooling_levels[i] = i;
return 0;
}
static int mlxreg_fan_probe(struct platform_device *pdev)
{
struct mlxreg_core_platform_data *pdata;
struct device *dev = &pdev->dev;
struct mlxreg_fan *fan;
struct device *hwm;
int err;
pdata = dev_get_platdata(dev);
if (!pdata) {
dev_err(dev, "Failed to get platform data.\n");
return -EINVAL;
}
fan = devm_kzalloc(dev, sizeof(*fan), GFP_KERNEL);
if (!fan)
return -ENOMEM;
fan->dev = dev;
fan->regmap = pdata->regmap;
err = mlxreg_fan_config(fan, pdata);
if (err)
return err;
hwm = devm_hwmon_device_register_with_info(dev, "mlxreg_fan",
fan,
&mlxreg_fan_hwmon_chip_info,
NULL);
if (IS_ERR(hwm)) {
dev_err(dev, "Failed to register hwmon device\n");
return PTR_ERR(hwm);
}
if (IS_REACHABLE(CONFIG_THERMAL)) {
fan->cdev = devm_thermal_of_cooling_device_register(dev,
NULL, "mlxreg_fan", fan, &mlxreg_fan_cooling_ops);
if (IS_ERR(fan->cdev)) {
dev_err(dev, "Failed to register cooling device\n");
return PTR_ERR(fan->cdev);
}
}
return 0;
}
static struct platform_driver mlxreg_fan_driver = {
.driver = {
.name = "mlxreg-fan",
},
.probe = mlxreg_fan_probe,
};
module_platform_driver(mlxreg_fan_driver);
MODULE_AUTHOR("Vadim Pasternak <vadimp@mellanox.com>");
MODULE_DESCRIPTION("Mellanox FAN driver");
MODULE_LICENSE("GPL");
MODULE_ALIAS("platform:mlxreg-fan");