Re: [PATCH v2] hwmon: (corsair-psu) Fix linear11 calculation

Wilken Gottwalt <[email protected]>
Newsgroups org.kernel.vger.linux-hwmon
Message-ID <[email protected]>
On Mon,  3 Aug 2026 20:48:11 -0700
Guenter Roeck <[email protected]> wrote:

> In corsairpsu_linear11_to_int(), the mantissa is extracted using bitwise
> operations and cast to s16 before being shifted left:
> 
> static int corsairpsu_linear11_to_int(const u16 val, const int scale)
> {
>     ...
>     const int mant = (((s16)(val & 0x7ff)) << 5) >> 5;
>     ...
> }
> 
> Due to C integer promotion rules, the masked value (which is always
> positive) is promoted to a 32-bit integer before the left shift. As a
> result, the sign bit is never extended to bit 31 of the promoted integer.
> 
> When the device hardware reports a negative temperature in Linear11 format
> (such as an ambient temperature probe reporting sub-zero), the negative
> mantissa is parsed incorrectly as a massive positive value. For example,
> -1 becomes 2047, which scales to 2047 degrees Celsius.
> 
> Fix the problem by type casting the result of the left shift operation
> to s16.
> 
> Another problem is left-shifting of negative values. In C, the result of
> left-shifting negative values is undefined. Use a multiplication instead
> to avoid the problem.
> 
> Also use a local s64 variable to store temporary results, change
> the return value type from int to long, and clamp the final value
> to LONG_MIN and LONG_MAX to avoid under- and overflow issues while
> retaining as much information as possible.
> 
> Reported-by: Sashiko <[email protected]>
> Cc: Wilken Gottwalt <[email protected]>
> Signed-off-by: Guenter Roeck <[email protected]>
> ---
> v2: Skip handling right-shift of negative values
>     (since it is widely used in the kernel)
> 
>  drivers/hwmon/corsair-psu.c | 23 ++++++++++++++---------
>  1 file changed, 14 insertions(+), 9 deletions(-)
> 
> diff --git a/drivers/hwmon/corsair-psu.c b/drivers/hwmon/corsair-psu.c
> index 24100519cd83..c437b469de5c 100644
> --- a/drivers/hwmon/corsair-psu.c
> +++ b/drivers/hwmon/corsair-psu.c
> @@ -137,13 +137,18 @@ struct corsairpsu_data {
>  };
>  
>  /* some values are SMBus LINEAR11 data which need a conversion */
> -static int corsairpsu_linear11_to_int(const u16 val, const int scale)
> +static long corsairpsu_linear11_to_long(const u16 val, const int scale)
>  {
>  	const int exp = ((s16)val) >> 11;
> -	const int mant = (((s16)(val & 0x7ff)) << 5) >> 5;
> -	const int result = mant * scale;
> +	const int mant = ((s16)((val & 0x7ff) << 5)) >> 5;


> +	s64 result = mant * scale;

Uhm, this is a 32bit multiplicaiton, actully a C gotcha I explain the beginners
in our company. https://godbolt.org/z/eM6TbGG5E

> -	return (exp >= 0) ? (result << exp) : (result >> -exp);
> +	if (exp >= 0)
> +		result *= (int)(1UL << exp);
> +	else
> +		result >>= -exp;
> +
> +	return clamp(result, LONG_MIN, LONG_MAX);
>  }
>  
>  /* the micro-controller uses percentage values to control pwm */
> @@ -263,13 +268,13 @@ static int corsairpsu_get_value(struct corsairpsu_data *priv, u8 cmd, u8
> rail, l case PSU_CMD_RAIL_AMPS:
>  	case PSU_CMD_TEMP0:
>  	case PSU_CMD_TEMP1:
> -		*val = corsairpsu_linear11_to_int(tmp & 0xFFFF, 1000);
> +		*val = corsairpsu_linear11_to_long(tmp & 0xFFFF, 1000);
>  		break;
>  	case PSU_CMD_FAN:
> -		*val = corsairpsu_linear11_to_int(tmp & 0xFFFF, 1);
> +		*val = corsairpsu_linear11_to_long(tmp & 0xFFFF, 1);
>  		break;
>  	case PSU_CMD_FAN_PWM_ENABLE:
> -		*val = corsairpsu_linear11_to_int(tmp & 0xFFFF, 1);
> +		*val = corsairpsu_linear11_to_long(tmp & 0xFFFF, 1);
>  		/*
>  		 * 0 = automatic mode, means the micro-controller controls the fan using a plan
>  		 *     which can be modified, but changing this plan is not supported by this
> @@ -283,12 +288,12 @@ static int corsairpsu_get_value(struct corsairpsu_data *priv, u8 cmd, u8
> rail, l *val = 2;
>  		break;
>  	case PSU_CMD_FAN_PWM:
> -		*val = corsairpsu_linear11_to_int(tmp & 0xFFFF, 1);
> +		*val = corsairpsu_linear11_to_long(tmp & 0xFFFF, 1);
>  		*val = corsairpsu_dutycycle_to_pwm(*val);
>  		break;
>  	case PSU_CMD_RAIL_WATTS:
>  	case PSU_CMD_TOTAL_WATTS:
> -		*val = corsairpsu_linear11_to_int(tmp & 0xFFFF, 1000000);
> +		*val = corsairpsu_linear11_to_long(tmp & 0xFFFF, 1000000);
>  		break;
>  	case PSU_CMD_TOTAL_UPTIME:
>  	case PSU_CMD_UPTIME:
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.