]> git.ipfire.org Git - thirdparty/kernel/stable.git/commitdiff
iio: consumers: Fix handling of negative channel scale in iio_convert_raw_to_processed()
authorHans de Goede <hansg@kernel.org>
Sun, 31 Aug 2025 10:48:20 +0000 (12:48 +0200)
committerJonathan Cameron <Jonathan.Cameron@huawei.com>
Wed, 10 Sep 2025 18:47:04 +0000 (19:47 +0100)
There is an issue with the handling of negative channel scales
in iio_convert_raw_to_processed_unlocked() when the channel-scale
is of the IIO_VAL_INT_PLUS_[MICRO|NANO] type:

Things work for channel-scale values > -1.0 and < 0.0 because of
the use of signed values in:

*processed += div_s64(raw64 * (s64)scale_val2 * scale, 1000000LL);

Things will break however for scale values < -1.0. Lets for example say
that raw = 2, (caller-provided)scale = 10 and (channel)scale_val = -1.5.

The result should then be 2 * 10 * -1.5 = -30.

channel-scale = -1.5 means scale_val = -1 and scale_val2 = 500000,
now lets see what gets stored in processed:

1. *processed = raw64 * scale_val * scale;
2. *processed += raw64 * scale_val2 * scale / 1000000LL;

1. Sets processed to 2 * -1 * 10 = -20
2. Adds 2 * 500000 * 10 / 1000000 = 10 to processed

And the end result is processed = -20 + 10 = -10, which is not correct.

Fix this by always using the abs value of both scale_val and scale_val2
and if either is negative multiply the end-result by -1.

Note there seems to be an unwritten rule about negative
IIO_VAL_INT_PLUS_[MICRO|NANO] values that:

i.   values > -1.0 and < 0.0 are written as val=0 val2=-xxx
ii.  values <= -1.0 are written as val=-xxx val2=xxx

But iio_format_value() will also correctly display a third option:

iii. values <= -1.0 written as val=-xxx val2=-xxx

Since iio_format_value() uses abs(val) when val2 < 0.

This fix also makes iio_convert_raw_to_processed() properly handle
channel-scales using this third option.

Fixes: 48e44ce0f881 ("iio:inkern: Add function to read the processed value")
Cc: Matteo Martelli <matteomartelli3@gmail.com>
Reviewed-by: Andy Shevchenko <andy@kernel.org>
Signed-off-by: Hans de Goede <hansg@kernel.org>
Link: https://patch.msgid.link/20250831104825.15097-2-hansg@kernel.org
Signed-off-by: Jonathan Cameron <Jonathan.Cameron@huawei.com>
drivers/iio/inkern.c

index c174ebb7d5e6d183674b7ffb15c4ce0f65fa3aed..d36a80a7b8a93089a30e078e45aea233c1fa1f40 100644 (file)
@@ -11,6 +11,7 @@
 #include <linux/mutex.h>
 #include <linux/property.h>
 #include <linux/slab.h>
+#include <linux/units.h>
 
 #include <linux/iio/iio.h>
 #include <linux/iio/iio-opaque.h>
@@ -604,7 +605,7 @@ static int iio_convert_raw_to_processed_unlocked(struct iio_channel *chan,
 {
        int scale_type, scale_val, scale_val2;
        int offset_type, offset_val, offset_val2;
-       s64 raw64 = raw;
+       s64 denominator, raw64 = raw;
 
        offset_type = iio_channel_read(chan, &offset_val, &offset_val2,
                                       IIO_CHAN_INFO_OFFSET);
@@ -648,20 +649,19 @@ static int iio_convert_raw_to_processed_unlocked(struct iio_channel *chan,
                *processed = raw64 * scale_val * scale;
                break;
        case IIO_VAL_INT_PLUS_MICRO:
-               if (scale_val2 < 0)
-                       *processed = -raw64 * scale_val * scale;
-               else
-                       *processed = raw64 * scale_val * scale;
-               *processed += div_s64(raw64 * (s64)scale_val2 * scale,
-                                     1000000LL);
-               break;
        case IIO_VAL_INT_PLUS_NANO:
-               if (scale_val2 < 0)
-                       *processed = -raw64 * scale_val * scale;
-               else
-                       *processed = raw64 * scale_val * scale;
-               *processed += div_s64(raw64 * (s64)scale_val2 * scale,
-                                     1000000000LL);
+               switch (scale_type) {
+               case IIO_VAL_INT_PLUS_MICRO:
+                       denominator = MICRO;
+                       break;
+               case IIO_VAL_INT_PLUS_NANO:
+                       denominator = NANO;
+                       break;
+               }
+               *processed = raw64 * scale * abs(scale_val);
+               *processed += div_s64(raw64 * scale * abs(scale_val2), denominator);
+               if (scale_val < 0 || scale_val2 < 0)
+                       *processed *= -1;
                break;
        case IIO_VAL_FRACTIONAL:
                *processed = div_s64(raw64 * (s64)scale_val * scale,