mirror of https://lore.kernel.org/lkml/
 help / color / mirror / Atom feed
* [PATCH v4 0/6] Add E notation support to kstrtodec64() and IIO sysfs writes
@ 2026-10-26 11:54 Rodrigo Alencar via B4 Relay
  2026-10-26 11:54 ` [PATCH v4 1/6] lib: kstrtox: support E notation in kstrtoudec64() and kstrtodec64() Rodrigo Alencar via B4 Relay
                   ` (5 more replies)
  0 siblings, 6 replies; 12+ messages in thread
From: Rodrigo Alencar via B4 Relay @ 2026-10-26 11:54 UTC (permalink / raw)
  To: linux-kernel, linux-iio
  Cc: Andrew Morton, Jonathan Cameron, David Lechner, Andy Shevchenko,
	Rodrigo Alencar

This series lets kstrtoudec64() and kstrtodec64() accept numbers in E
notation (scientific notation), such as "2.7e9", "1.5E-3" or "1e+16".
It then moves the IIO core fixed-point parser over to kstrtodec64(), so
every IIO channel attribute written through iio_write_channel_info()
accepts that format as well.

There are two main motivations:

1. Shell usage of sysfs attributes

   Some IIO devices take values that are long to type and easy to get
   wrong in plain decimal. A frequency synthesizer or DDS might be
   programmed in the GHz range, and a scale might be a tiny fraction.
   With this series one can write:

     echo 2.7e9 > out_altvoltage0_frequency
     echo 1.5e-6 > in_voltage0_scale

   instead of counting zeros in "2700000000" or "0.0000015". It also
   works with shell tools that print in E notation by default, such as
   printf's %g conversion ("printf '%g' 2700000000" prints "2.7e+09")
   and awk, whose default output format ("%.6g") prints
   non-integral values like 1234567.5 as "1.23457e+06".

2. Userspace software that formats numbers automatically

   Many languages and libraries switch to E notation when converting
   floating point numbers to strings, without the caller asking for it:

   - Python: str() and repr() of a float use E notation for exponents
     below -4 or from 16 up, e.g. str(0.00001) == '1e-05' and
     str(1e16) == '1e+16'. Writing such a value straight to an attribute
     with open(...).write(str(value)) currently fails with -EINVAL.
     The same applies to NumPy scalars and to f-strings with the 'g'
     format spec (f"{2.7e9:g}" == '2.7e+09').
   - JavaScript/Node.js: Number.prototype.toString() uses E notation
     for magnitudes from 1e21 up or below 1e-6, e.g. String(1e-7)
     gives '1e-7'.
   - C/C++ applications and bindings that use printf("%g") or
     std::to_chars() with general formatting, as well as MATLAB/Octave
     and other numeric environments that print in a "short" form.

   Today these values have to be reformatted by the application, or are
   rejected by the kernel without warning. Accepting E notation in the
   kernel makes the IIO sysfs ABI more robust to this common
   behaviour.

The exponent is folded into the requested decimal scale, so no floating
point arithmetic is involved. Overflow is reported as -ERANGE and a
malformed exponent ("1e", "1e+") as -EINVAL.

Digits beyond the resulting precision are now rounded to nearest, ties
to even, instead of truncated (new patch 2). This follows from the
second motivation: floating point values printed by userspace often end
just below the intended decimal value. "%.17g" of 0.3 gives
"0.29999999999999999", which truncation turns into 299999 at micro
precision instead of 300000. E notation makes this more visible, since
a negative exponent can drop integer digits: with truncation, "19e-1"
written to an integer attribute gives 1. Round-half-even is the IEEE 754
default, as used by strtod() and printf(), it is unbiased and symmetric
for negative values, and inputs that fit the precision (including values
read back from sysfs) are parsed exactly as before. The rounding is done
before the IIO core conversion, so IIO fixed-point writes change
behaviour only once.

This changes existing kstrtodec64() behaviour, so feedback is welcome on
whether it should stay in this series or be sent as a follow-up.

Signed-off-by: Rodrigo Alencar <rodrigo.alencar@analog.com>
---
Changes in v4:
- Adjust iio-test-rescale.c to use kstrtodec64().
- Minor changes on making the patches more readable.
- Link to v3: https://patch.msgid.link/20261020-e-notation-v3-0-fd385c0fbf65@analog.com

Changes in v3:
- New patch: round to nearest, ties to even, instead of truncating.
- Amend kstrtox E notation tests and IIO channel write tests for rounding.
- Link to v2: https://patch.msgid.link/20261013-e-notation-v2-0-d4adb5e193fc@analog.com

Changes in v2:
- More test cases on test-kstrtox.c.
- Rework fallback logic when parsing integers.
- Add iio kunit test cases for iio_write_channel_info()
- Link to v1: https://patch.msgid.link/20261001-e-notation-v1-0-75187bf06c10@analog.com

---
Rodrigo Alencar (6):
      lib: kstrtox: support E notation in kstrtoudec64() and kstrtodec64()
      lib: kstrtox: round to nearest, ties to even in kstrto[u]dec64()
      lib: test-kstrtox: add E notation tests for kstrto[u]dec64()
      iio: test: rescale: compare scales against the precision of the output
      iio: core: use kstrtodec64() to parse fixed-point values
      iio: test: add kunit tests for channel info writes

 MAINTAINERS                               |   1 +
 drivers/iio/iio_core.h                    |   3 +
 drivers/iio/industrialio-core.c           | 171 +++++++-------
 drivers/iio/test/Kconfig                  |  14 ++
 drivers/iio/test/Makefile                 |   1 +
 drivers/iio/test/iio-test-channel-write.c | 360 ++++++++++++++++++++++++++++++
 drivers/iio/test/iio-test-rescale.c       |  59 ++---
 lib/kstrtox.c                             | 111 ++++++---
 lib/test-kstrtox.c                        | 120 ++++++++--
 9 files changed, 689 insertions(+), 151 deletions(-)
---
base-commit: 43ffda3c7f8456bbe641ae8249a077a361b97880
change-id: 20261001-e-notation-0f07757910e5

Best regards,
--  
Rodrigo Alencar <rodrigo.alencar@analog.com>



^ permalink raw reply	[flat|nested] 12+ messages in thread

* [PATCH v4 1/6] lib: kstrtox: support E notation in kstrtoudec64() and kstrtodec64()
  2026-10-26 11:54 [PATCH v4 0/6] Add E notation support to kstrtodec64() and IIO sysfs writes Rodrigo Alencar via B4 Relay
@ 2026-10-26 11:54 ` Rodrigo Alencar via B4 Relay
  2026-10-27 10:23   ` Rodrigo Alencar
  2026-10-26 11:54 ` [PATCH v4 2/6] lib: kstrtox: round to nearest, ties to even in kstrto[u]dec64() Rodrigo Alencar via B4 Relay
                   ` (4 subsequent siblings)
  5 siblings, 1 reply; 12+ messages in thread
From: Rodrigo Alencar via B4 Relay @ 2026-10-26 11:54 UTC (permalink / raw)
  To: linux-kernel, linux-iio
  Cc: Andrew Morton, Jonathan Cameron, David Lechner, Andy Shevchenko,
	Rodrigo Alencar

From: Rodrigo Alencar <rodrigo.alencar@analog.com>

Allow decimal strings to carry an exponent suffix, such as "2.7e10" or
"271828e-5", in kstrtoudec64() and kstrtodec64(). The exponent is added
to the requested scale to work out how many integer and fractional digits
are consumed. A negative effective exponent drops integer digits.

The digits are now scanned first and the string termination is checked
before any conversion. A missing or malformed exponent ("1e", "1e+")
returns -EINVAL. An exponent that overflows when added to the scale, or
that makes the result exceed 64 bits, returns -ERANGE.

Signed-off-by: Rodrigo Alencar <rodrigo.alencar@analog.com>
---
 lib/kstrtox.c | 69 +++++++++++++++++++++++++++++++++++++++++------------------
 1 file changed, 48 insertions(+), 21 deletions(-)

diff --git a/lib/kstrtox.c b/lib/kstrtox.c
index bac1c057e1b0..20d94a73f937 100644
--- a/lib/kstrtox.c
+++ b/lib/kstrtox.c
@@ -395,41 +395,68 @@ int kstrtobool(const char *s, bool *res)
 }
 EXPORT_SYMBOL(kstrtobool);
 
+static int skip_digits(const char **s)
+{
+	const char *start = *s;
+
+	while (isdigit(**s))
+		(*s)++;
+
+	return *s - start;
+}
+
 static int _kstrtoudec64(const char *s, unsigned int scale, u64 *res)
 {
-	unsigned int rv_int, rv_frac;
+	const char *s_int, *s_frac;
+	int len_int, len_frac;
+	unsigned int rv;
 	u64 _res = 0;
+	int exp = 0;
+	int ret;
 
-	rv_int = _parse_integer(s, 10, &_res);
-	if (rv_int & KSTRTOX_OVERFLOW)
-		return -ERANGE;
-	s += rv_int;
+	s_int = s;
+	len_int = skip_digits(&s);
 
 	if (*s == '.')
 		s++; /* skip decimal point */
 
-	rv_frac = _parse_integer(s, 10, &_res, scale, _res);
-	if (rv_frac & KSTRTOX_OVERFLOW)
-		return -ERANGE;
-	s += rv_frac;
+	s_frac = s;
+	len_frac = skip_digits(&s);
 
-	/*
-	 * Check input beyond rv_int and rv_frac to cover cases like ".5" with
-	 * scale 0, which is considered a valid input, being parsed as 0.
-	 */
-	if (!rv_int && !rv_frac && !isdigit(*s))
+	if (!len_int && !len_frac)
 		return -EINVAL;
 
-	while (isdigit(*s)) /* truncate digits */
+	if (_tolower(*s) == 'e') { /* E Notation */
 		s++;
+		ret = kstrtoint(s, 10, &exp);
+		if (ret)
+			return ret;
+	} else { /* verify termination */
+		if (*s == '\n')
+			s++;
+		if (*s)
+			return -EINVAL;
+	}
 
-	if (*s == '\n')
-		s++;
-	if (*s)
-		return -EINVAL;
+	if (check_add_overflow(exp, (int)scale, &exp))
+		return -ERANGE;
+
+	if (exp < 0) {
+		len_int = max(len_int + exp, 0);
+		exp = 0;
+	}
+
+	rv = _parse_integer(s_int, 10, &_res, len_int);
+	if (rv & KSTRTOX_OVERFLOW)
+		return -ERANGE;
+
+	rv = _parse_integer(s_frac, 10, &_res, exp, _res);
+	if (rv & KSTRTOX_OVERFLOW)
+		return -ERANGE;
 
-	if (_res && ((scale - rv_frac) > 19 /* log10(2^64) = 19.26 */ ||
-		     check_mul_overflow(_res, int_pow(10, scale - rv_frac), &_res)))
+	exp -= rv;
+	if (_res && (exp > 19 /* log10(2^64) = 19.26 */ ||
+		     check_mul_overflow(_res, int_pow(10, exp), &_res)))
 		return -ERANGE;
 
 	*res = _res;

-- 
2.43.0



^ permalink raw reply	[flat|nested] 12+ messages in thread

* [PATCH v4 2/6] lib: kstrtox: round to nearest, ties to even in kstrto[u]dec64()
  2026-10-26 11:54 [PATCH v4 0/6] Add E notation support to kstrtodec64() and IIO sysfs writes Rodrigo Alencar via B4 Relay
  2026-10-26 11:54 ` [PATCH v4 1/6] lib: kstrtox: support E notation in kstrtoudec64() and kstrtodec64() Rodrigo Alencar via B4 Relay
@ 2026-10-26 11:54 ` Rodrigo Alencar via B4 Relay
  2026-10-26 11:54 ` [PATCH v4 3/6] lib: test-kstrtox: add E notation tests for kstrto[u]dec64() Rodrigo Alencar via B4 Relay
                   ` (3 subsequent siblings)
  5 siblings, 0 replies; 12+ messages in thread
From: Rodrigo Alencar via B4 Relay @ 2026-10-26 11:54 UTC (permalink / raw)
  To: linux-kernel, linux-iio
  Cc: Andrew Morton, Jonathan Cameron, David Lechner, Andy Shevchenko,
	Rodrigo Alencar

From: Rodrigo Alencar <rodrigo.alencar@analog.com>

kstrtoudec64() and kstrtodec64() drop the digits beyond the requested
scale, which truncates the result toward zero. The error is up to one
unit in the last place and is always in the same direction.

This is easy to hit with values produced by userspace floating point
formatting. Printing 0.3 with "%.17g" gives "0.29999999999999999",
which is parsed as 299999 with a scale of 6 instead of 300000. With E
notation a negative exponent can also drop integer digits, so "19e-1"
with a scale of 0 gives 1 instead of 2.

Round the result to nearest instead, with ties to even. This is the
IEEE 754 default rounding mode, as used by strtod() and printf(), and it
does not bias results in either direction. The first discarded digit is
known from where parsing stopped, so no extra pass over the string is
needed: the remaining digits are only checked when the first discarded
digit is a 5. Since round-half-even is symmetric, kstrtodec64() keeps
rounding the magnitude and negates it.

Inputs that fit within the scale are parsed exactly as before. An input
that rounds up past the type limit, such as "18446744073.7095516155"
with a scale of 9, now returns -ERANGE.

Update the existing truncation test cases and add cases for ties and
for rounding at the type limits.

Signed-off-by: Rodrigo Alencar <rodrigo.alencar@analog.com>
---
 lib/kstrtox.c      | 46 ++++++++++++++++++++++++++++++++++++++++------
 lib/test-kstrtox.c | 48 +++++++++++++++++++++++++++++++++++-------------
 2 files changed, 75 insertions(+), 19 deletions(-)

diff --git a/lib/kstrtox.c b/lib/kstrtox.c
index 20d94a73f937..47c20079247a 100644
--- a/lib/kstrtox.c
+++ b/lib/kstrtox.c
@@ -405,9 +405,28 @@ static int skip_digits(const char **s)
 	return *s - start;
 }
 
+static u64 round_half_even(const char *p, const char *end, u64 res)
+{
+	if (p == end)
+		return 0;
+
+	if (*p < '5')
+		return 0;
+	if (*p > '5')
+		return 1;
+
+	/* tie: any nonzero digit after it breaks it, skipping the decimal point */
+	while (++p < end) {
+		if (*p != '0' && *p != '.')
+			return 1;
+	}
+
+	return res & 1;
+}
+
 static int _kstrtoudec64(const char *s, unsigned int scale, u64 *res)
 {
-	const char *s_int, *s_frac;
+	const char *s_int, *s_frac, *s_end, *s_round;
 	int len_int, len_frac;
 	unsigned int rv;
 	u64 _res = 0;
@@ -422,6 +441,7 @@ static int _kstrtoudec64(const char *s, unsigned int scale, u64 *res)
 
 	s_frac = s;
 	len_frac = skip_digits(&s);
+	s_end = s;
 
 	if (!len_int && !len_frac)
 		return -EINVAL;
@@ -442,8 +462,16 @@ static int _kstrtoudec64(const char *s, unsigned int scale, u64 *res)
 		return -ERANGE;
 
 	if (exp < 0) {
-		len_int = max(len_int + exp, 0);
+		len_int += exp;
 		exp = 0;
+		if (len_int < 0) {
+			s_round = s_end;
+			len_int = 0;
+		} else {
+			s_round = s_int + len_int;
+		}
+	} else {
+		s_round = s_frac;
 	}
 
 	rv = _parse_integer(s_int, 10, &_res, len_int);
@@ -454,6 +482,10 @@ static int _kstrtoudec64(const char *s, unsigned int scale, u64 *res)
 	if (rv & KSTRTOX_OVERFLOW)
 		return -ERANGE;
 
+	s_round += rv;
+	if (check_add_overflow(_res, round_half_even(s_round, s_end, _res), &_res))
+		return -ERANGE;
+
 	exp -= rv;
 	if (_res && (exp > 19 /* log10(2^64) = 19.26 */ ||
 		     check_mul_overflow(_res, int_pow(10, exp), &_res)))
@@ -473,7 +505,8 @@ static int _kstrtoudec64(const char *s, unsigned int scale, u64 *res)
  *
  * For example, a scale of 3 with input "123.45" results in 123450. Note that
  * trailing zeros in the fractional part input to match the scale are not
- * required. Also, digits beyond the specified scale are ignored.
+ * required. Also, digits beyond the specified scale are rounded to nearest,
+ * ties to even.
  *
  * Return: 0 on success, -ERANGE on overflow and -EINVAL on parsing error.
  */
@@ -494,9 +527,10 @@ EXPORT_SYMBOL(kstrtoudec64);
  * @scale: The number of digits to the right of the decimal point.
  * @res: Where to write the result of the conversion on success.
  *
- * For example, a scale of 4 with input "-3.141592" results in -31415. Note
- * that digits beyond the specified scale are ignored. Also, trailing zeros in
- * the fractional part input to match the scale are not required.
+ * For example, a scale of 4 with input "-3.141592" results in -31416. Note
+ * that digits beyond the specified scale are rounded to nearest, ties to
+ * even. Also, trailing zeros in the fractional part input to match the scale
+ * are not required.
  *
  * Return: 0 on success, -ERANGE on overflow and -EINVAL on parsing error.
  */
diff --git a/lib/test-kstrtox.c b/lib/test-kstrtox.c
index 9255234752db..1f176b232085 100644
--- a/lib/test-kstrtox.c
+++ b/lib/test-kstrtox.c
@@ -737,12 +737,23 @@ static void __init test_kstrtoudec64_ok(void)
 		{"0.01",		3,	10},
 		{"0.1",			9,	100000000ULL},
 		{"0.01",		9,	10000000},
-		/* truncation: more fractional digits than scale */
-		{"1.23456",		3,	1234},
-		{"3.1415926535",	6,	3141592},
-		{"0.999999999",		3,	999},
-		{"1.99",		1,	19},
+		/* rounding: more fractional digits than scale */
+		{"1.23456",		3,	1235},
+		{"3.1415926535",	6,	3141593},
+		{"0.999999999",		3,	1000},
+		{"1.99",		1,	20},
 		{"1.234",		0,	1},
+		/* rounding: ties to even */
+		{"0.5",			0,	0},
+		{"1.5",			0,	2},
+		{"2.5",			0,	2},
+		{"2.500000",		0,	2},
+		{"2.51",		0,	3},
+		{"2.5000001",		0,	3},
+		{"0.05",		1,	0},
+		{"0.15",		1,	2},
+		{"0.25",		1,	2},
+		{"99.5",		0,	100},
 		/* trailing newline */
 		{"1.5\n",		1,	15},
 		{"42\n",		3,	42000},
@@ -760,15 +771,16 @@ static void __init test_kstrtoudec64_ok(void)
 		{"18446744073.709551615",	9,	ULLONG_MAX},
 		{"18446744073709.551615",	6,	ULLONG_MAX},
 		{"0.18446744073709551615",	20,	ULLONG_MAX},
+		{"18446744073.7095516154",	9,	ULLONG_MAX},
 		/* scale > 19: representable when integer part is small */
 		{"0.00000000000000000001",	20,	1},
 		{"0.1",				20,	10000000000000000000ULL},
 		{"0.00000000000000000000001",	23,	1},
-		/* truncation with scale > 19 */
+		/* rounding with scale > 19 */
 		{"0.0000000000000000000012345",	23,	123},
-		/* truncation with many excess digits */
+		/* rounding with many excess digits */
 		{"0.00000000000000000000000000000000423",	34,	42},
-		{"1.99999999999999999999999999999999999",	3,	1999},
+		{"1.99999999999999999999999999999999999",	3,	2000},
 	};
 	TEST_OK(kstrtoudec64, u64, "%llu", test_udec64_ok);
 }
@@ -795,6 +807,7 @@ static void __init test_kstrtoudec64_fail(void)
 		{"1.5 ",	1},
 		/* overflow */
 		{"18446744073710.551615",	6},
+		{"18446744073.7095516155",	9},
 		{"99999999999999999999",	1},
 		/* overflow with scale > 19 */
 		{"1.0",				21},
@@ -833,11 +846,16 @@ static void __init test_kstrtodec64_ok(void)
 		{"-1.2",		3,	-1200},
 		{"0.01",		3,	10},
 		{"-0.01",		3,	-10},
-		/* truncation */
-		{"1.23456",		3,	1234},
-		{"-1.23456",		3,	-1234},
-		{"0.999999999",		3,	999},
-		{"-0.999999999",	3,	-999},
+		/* rounding */
+		{"1.23456",		3,	1235},
+		{"-1.23456",		3,	-1235},
+		{"0.999999999",		3,	1000},
+		{"-0.999999999",	3,	-1000},
+		/* rounding: ties to even */
+		{"2.5",			0,	2},
+		{"-2.5",		0,	-2},
+		{"-3.5",		0,	-4},
+		{"-3.141592",		4,	-31416},
 		/* trailing newline */
 		{"1.5\n",		1,	15},
 		{"-1.5\n",		1,	-15},
@@ -846,6 +864,8 @@ static void __init test_kstrtodec64_ok(void)
 		/* limits */
 		{"9223372036.854775807",	9,	LLONG_MAX},
 		{"-9223372036.854775808",	9,	LLONG_MIN},
+		{"9223372036.8547758074",	9,	LLONG_MAX},
+		{"-9223372036.8547758085",	9,	LLONG_MIN},
 		/* scale > 19 */
 		{"0.0",				20,	0},
 		{"-0.0",			20,	0},
@@ -875,6 +895,8 @@ static void __init test_kstrtodec64_fail(void)
 		/* signed overflow */
 		{"9223372036.854775808",	9},
 		{"-9223372036.854775809",	9},
+		{"9223372036.8547758075",	9},
+		{"-9223372036.8547758085001",	9},
 		{"99999999999999999999",	1},
 		/* signed overflow with scale > 19 */
 		{"0.1",				20},

-- 
2.43.0



^ permalink raw reply	[flat|nested] 12+ messages in thread

* [PATCH v4 3/6] lib: test-kstrtox: add E notation tests for kstrto[u]dec64()
  2026-10-26 11:54 [PATCH v4 0/6] Add E notation support to kstrtodec64() and IIO sysfs writes Rodrigo Alencar via B4 Relay
  2026-10-26 11:54 ` [PATCH v4 1/6] lib: kstrtox: support E notation in kstrtoudec64() and kstrtodec64() Rodrigo Alencar via B4 Relay
  2026-10-26 11:54 ` [PATCH v4 2/6] lib: kstrtox: round to nearest, ties to even in kstrto[u]dec64() Rodrigo Alencar via B4 Relay
@ 2026-10-26 11:54 ` Rodrigo Alencar via B4 Relay
  2026-10-26 11:54 ` [PATCH v4 4/6] iio: test: rescale: compare scales against the precision of the output Rodrigo Alencar via B4 Relay
                   ` (2 subsequent siblings)
  5 siblings, 0 replies; 12+ messages in thread
From: Rodrigo Alencar via B4 Relay @ 2026-10-26 11:54 UTC (permalink / raw)
  To: linux-kernel, linux-iio
  Cc: Andrew Morton, Jonathan Cameron, David Lechner, Andy Shevchenko,
	Rodrigo Alencar

From: Rodrigo Alencar <rodrigo.alencar@analog.com>

Cover E notation parsing in kstrtoudec64() and kstrtodec64(): positive,
zero and negative exponents, upper case 'E', an explicit '+' sign,
rounding (ties to even) of integer digits dropped by negative exponents,
large values, and failures for overflowing and invalid inputs.

Signed-off-by: Rodrigo Alencar <rodrigo.alencar@analog.com>
---
 lib/test-kstrtox.c | 72 ++++++++++++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 72 insertions(+)

diff --git a/lib/test-kstrtox.c b/lib/test-kstrtox.c
index 1f176b232085..a0c1e4286fd4 100644
--- a/lib/test-kstrtox.c
+++ b/lib/test-kstrtox.c
@@ -781,6 +781,36 @@ static void __init test_kstrtoudec64_ok(void)
 		/* rounding with many excess digits */
 		{"0.00000000000000000000000000000000423",	34,	42},
 		{"1.99999999999999999999999999999999999",	3,	2000},
+		/* E notation: positive exponent, no fraction */
+		{"1e3",			3,	1000000ULL},
+		{"42e2",		3,	4200000ULL},
+		/* E notation: positive exponent with fraction */
+		{"2.718e3",		3,	2718000ULL},
+		{"1.5e2",		1,	1500ULL},
+		/* E notation: uppercase E */
+		{"1E3",			3,	1000000ULL},
+		/* E notation: explicit positive exponent sign */
+		{"1e+3",		3,	1000000ULL},
+		/* E notation: zero exponent */
+		{"1.5e0",		1,	15ULL},
+		/* E notation: negative exponent shifts decimal left */
+		{"271828e-5",		3,	2718ULL},
+		{"2.71828e-1",		3,	272ULL},
+		{"270e-2",		3,	2700ULL},
+		/* E notation: negative exponent that rounds integer digits */
+		{"27e-4",		3,	3ULL},
+		{"19e-1",		0,	2ULL},
+		/* E notation: ties to even on integer digits */
+		{"125e-1",		0,	12ULL},
+		{"135e-1",		0,	14ULL},
+		{"25.0001e-1",		0,	3ULL},
+		/* E notation: negative exponent below 1 ULP rounds to 0 */
+		{"1e-5",		3,	0ULL},
+		{"5e-1",		0,	0ULL},
+		{"5e-2",		0,	0ULL},
+		{"6e-1",		0,	1ULL},
+		/* E notation: large value (27 GHz in micro Hz) */
+		{"2.7e10",		6,	27000000000000000ULL},
 	};
 	TEST_OK(kstrtoudec64, u64, "%llu", test_udec64_ok);
 }
@@ -814,6 +844,17 @@ static void __init test_kstrtoudec64_fail(void)
 		{"0.2",				20},
 		{"0.18446744073709551616",	20},
 		{"1",				20},
+		/* E notation: exponent causes overflow */
+		{"1e21",			1},
+		{"2e20",			1},
+		/* E notation: no digits after exponent marker */
+		{"1e",			3},
+		/* E notation: sign only after exponent marker */
+		{"1e+",			3},
+		{"1e-",			3},
+		/* E notation: misc */
+		{"1ee",			3}, /* double 'e' */
+		{"1e+0b",		3}, /* hex character after exponent */
 	};
 	TEST_FAIL(kstrtoudec64, u64, "%llu", test_udec64_fail);
 }
@@ -873,6 +914,27 @@ static void __init test_kstrtodec64_ok(void)
 		{"-0.00000000000000000001",	20,	-1},
 		{"0.009223372036854775807",	21,	LLONG_MAX},
 		{"-0.009223372036854775808",	21,	LLONG_MIN},
+		/* E notation: positive number */
+		{"1e3",			3,	1000000LL},
+		{"2.7e3",		3,	2700000LL},
+		{"1.5e0",		1,	15LL},
+		/* E notation: negative number with positive exponent */
+		{"-1e3",		3,	-1000000LL},
+		{"-2.7e3",		3,	-2700000LL},
+		/* E notation: negative exponent */
+		{"27e-1",		3,	2700LL},
+		{"-27e-1",		3,	-2700LL},
+		{"2.7e-1",		3,	270LL},
+		{"-2.7e-1",		3,	-270LL},
+		/* E notation: negative exponent rounds integer digits */
+		{"-27e-4",		3,	-3LL},
+		{"-125e-1",		0,	-12LL},
+		{"-135e-1",		0,	-14LL},
+		/* E notation: negative exponent below 1 ULP rounds to 0 */
+		{"1e-5",		3,	0LL},
+		{"-1e-5",		3,	0LL},
+		{"-5e-1",		0,	0LL},
+		{"-6e-1",		0,	-1LL},
 	};
 	TEST_OK(kstrtodec64, s64, "%lld", test_dec64_ok);
 }
@@ -903,6 +965,16 @@ static void __init test_kstrtodec64_fail(void)
 		{"-0.1",			20},
 		{"0.09223372036854775808",	20},
 		{"-0.09223372036854775809",	20},
+		/* E notation: exponent causes overflow */
+		{"1e21",		1},
+		{"-1e21",		1},
+		/* E notation: no digits after exponent marker */
+		{"1e",			3},
+		{"1e+",			3},
+		{"1e-",			3},
+		/* E notation: misc */
+		{"1Ee+3",		3}, /* double 'e' with mixed case */
+		{"1e+0xa",		3}, /* hex number exponent */
 	};
 	TEST_FAIL(kstrtodec64, s64, "%lld", test_dec64_fail);
 }

-- 
2.43.0



^ permalink raw reply	[flat|nested] 12+ messages in thread

* [PATCH v4 4/6] iio: test: rescale: compare scales against the precision of the output
  2026-10-26 11:54 [PATCH v4 0/6] Add E notation support to kstrtodec64() and IIO sysfs writes Rodrigo Alencar via B4 Relay
                   ` (2 preceding siblings ...)
  2026-10-26 11:54 ` [PATCH v4 3/6] lib: test-kstrtox: add E notation tests for kstrto[u]dec64() Rodrigo Alencar via B4 Relay
@ 2026-10-26 11:54 ` Rodrigo Alencar via B4 Relay
  2026-10-26 14:40   ` Andy Shevchenko
  2026-10-26 11:54 ` [PATCH v4 5/6] iio: core: use kstrtodec64() to parse fixed-point values Rodrigo Alencar via B4 Relay
  2026-10-26 11:54 ` [PATCH v4 6/6] iio: test: add kunit tests for channel info writes Rodrigo Alencar via B4 Relay
  5 siblings, 1 reply; 12+ messages in thread
From: Rodrigo Alencar via B4 Relay @ 2026-10-26 11:54 UTC (permalink / raw)
  To: linux-kernel, linux-iio
  Cc: Andrew Morton, Jonathan Cameron, David Lechner, Andy Shevchenko,
	Rodrigo Alencar

From: Rodrigo Alencar <rodrigo.alencar@analog.com>

The scale tests format the rescaled value with iio_format_value(), which
prints at most nano precision, and require the relative error to the
expected value to be below 1 ppm. Both strings are parsed back with
iio_str_to_fixpoint() at nano precision, so for the small scale cases the
check only passes because the expected value, like the formatted one, is
truncated to nano. For example, "0.000000033594" is parsed as 33 nano,
equal to the formatted "0.000000033", although the actual error is about
1.8%. If the expected value were rounded instead, it would be parsed as
34 nano and the test would fail even though the output is as accurate as
the format allows.

Parse both values with kstrtodec64() at a scale of 10, one digit more
than the output precision, so the expected value keeps the digits lost
when formatting. Scale 10 is the largest that still fits the overflow
cases (about 2.1e8) in an s64. Compute the relative error with
mul_u64_u64_div_u64(), as the product with 10^6 no longer fits in 64 bits
for those cases.

Then allow a relative error up to the relative size of one nano of the
expected value, the precision of the formatted output. This is 0 ppm for
values of 0.01 and above, so those cases are checked as before, and it
accepts the formatting error for the small scale cases.

Signed-off-by: Rodrigo Alencar <rodrigo.alencar@analog.com>
---
 drivers/iio/test/iio-test-rescale.c | 59 ++++++++++++++-----------------------
 1 file changed, 22 insertions(+), 37 deletions(-)

diff --git a/drivers/iio/test/iio-test-rescale.c b/drivers/iio/test/iio-test-rescale.c
index de4a09f432c0..1347a9650355 100644
--- a/drivers/iio/test/iio-test-rescale.c
+++ b/drivers/iio/test/iio-test-rescale.c
@@ -6,6 +6,8 @@
  */
 
 #include <linux/gcd.h>
+#include <linux/kstrtox.h>
+#include <linux/math64.h>
 #include <linux/overflow.h>
 
 #include <linux/iio/afe/rescale.h>
@@ -581,55 +583,36 @@ static void case_to_desc(const struct rescale_tc_data *t, char *desc)
 KUNIT_ARRAY_PARAM(iio_rescale_scale, scale_cases, case_to_desc);
 KUNIT_ARRAY_PARAM(iio_rescale_offset, offset_cases, case_to_desc);
 
-/**
- * iio_str_to_nano() - Parse a fixed-point string to get an
- *                      IIO_VAL_INT_PLUS_NANO value
- * @str: The string to parse
- * @nano: The number as an integer
- *
- * Returns 0 on success, or a negative error code if the string could not be
- * parsed.
+/*
+ * iio_format_value() prints scales with up to nano precision. Parse values
+ * with one more digit, so the expected values keep the precision lost there.
+ * Scale 10 is the largest that fits the overflow test cases in an s64.
  */
-static int iio_str_to_nano(const char *str, s64 *nano)
-{
-	int tmp, tmp2;
-	int ret = 0;
-
-	/*
-	 * iio_str_to_fixpoint() uses 10^8 here instead of 10^9 as fract_mult is
-	 * the multiplier for the first decimal place.
-	 */
-	ret = iio_str_to_fixpoint(str, 100000000, &tmp, &tmp2);
-	if (ret < 0)
-		return ret;
-
-	if (tmp < 0)
-		tmp2 *= -1;
-
-	*nano = (s64)tmp * 1000000000UL + tmp2;
-
-	return ret;
-}
+#define IIO_TEST_DEC_SCALE	10
+#define IIO_TEST_NANO		10	/* one nano at IIO_TEST_DEC_SCALE */
 
 /**
  * iio_test_relative_error_ppm() - Compute relative error (in parts-per-million)
  *                                 between two fixed-point strings
  * @real_str: The real value as a string
  * @exp_str: The expected value as a string
+ * @max_ppm: The relative size (in parts-per-million) of one nano of the
+ *           expected value, the precision of iio_format_value()
  *
  * Returns a negative error code if the strings could not be parsed, or the
  * relative error in parts-per-million.
  */
-static int iio_test_relative_error_ppm(const char *real_str, const char *exp_str)
+static int iio_test_relative_error_ppm(const char *real_str, const char *exp_str,
+				       int *max_ppm)
 {
-	s64 real, exp, err;
+	s64 real, exp;
 	int ret;
 
-	ret = iio_str_to_nano(real_str, &real);
+	ret = kstrtodec64(real_str, IIO_TEST_DEC_SCALE, &real);
 	if (ret < 0)
 		return ret;
 
-	ret = iio_str_to_nano(exp_str, &exp);
+	ret = kstrtodec64(exp_str, IIO_TEST_DEC_SCALE, &exp);
 	if (ret < 0)
 		return ret;
 
@@ -638,9 +621,9 @@ static int iio_test_relative_error_ppm(const char *real_str, const char *exp_str
 		return -EINVAL;
 	}
 
-	err = 1000000UL * abs(exp - real);
+	*max_ppm = (int)div64_u64(IIO_TEST_NANO * 1000000ULL, abs(exp));
 
-	return (int)div64_u64(err, abs(exp));
+	return (int)mul_u64_u64_div_u64(abs(exp - real), 1000000, abs(exp));
 }
 
 static void iio_rescale_test_scale(struct kunit *test)
@@ -650,6 +633,7 @@ static void iio_rescale_test_scale(struct kunit *test)
 	struct rescale rescale;
 	int values[2];
 	int rel_ppm;
+	int max_ppm;
 	int ret;
 
 	KUNIT_ASSERT_NOT_ERR_OR_NULL(test, buff);
@@ -666,10 +650,11 @@ static void iio_rescale_test_scale(struct kunit *test)
 	ret = iio_format_value(buff, ret, 2, values);
 	KUNIT_EXPECT_EQ(test, (int)strlen(buff), ret);
 
-	rel_ppm = iio_test_relative_error_ppm(buff, t->expected);
-	KUNIT_EXPECT_GE_MSG(test, rel_ppm, 0, "failed to compute ppm\n");
+	rel_ppm = iio_test_relative_error_ppm(buff, t->expected, &max_ppm);
+	KUNIT_ASSERT_GE_MSG(test, rel_ppm, 0, "failed to compute ppm\n");
 
-	KUNIT_EXPECT_EQ_MSG(test, rel_ppm, 0,
+	/* allow the error of the nano precision used to format the value */
+	KUNIT_EXPECT_LE_MSG(test, rel_ppm, max_ppm,
 			    "\t    real=%s"
 			    "\texpected=%s\n",
 			    buff, t->expected);

-- 
2.43.0



^ permalink raw reply	[flat|nested] 12+ messages in thread

* [PATCH v4 5/6] iio: core: use kstrtodec64() to parse fixed-point values
  2026-10-26 11:54 [PATCH v4 0/6] Add E notation support to kstrtodec64() and IIO sysfs writes Rodrigo Alencar via B4 Relay
                   ` (3 preceding siblings ...)
  2026-10-26 11:54 ` [PATCH v4 4/6] iio: test: rescale: compare scales against the precision of the output Rodrigo Alencar via B4 Relay
@ 2026-10-26 11:54 ` Rodrigo Alencar via B4 Relay
  2026-10-26 14:54   ` Andy Shevchenko
  2026-10-26 11:54 ` [PATCH v4 6/6] iio: test: add kunit tests for channel info writes Rodrigo Alencar via B4 Relay
  5 siblings, 1 reply; 12+ messages in thread
From: Rodrigo Alencar via B4 Relay @ 2026-10-26 11:54 UTC (permalink / raw)
  To: linux-kernel, linux-iio
  Cc: Andrew Morton, Jonathan Cameron, David Lechner, Andy Shevchenko,
	Rodrigo Alencar

From: Rodrigo Alencar <rodrigo.alencar@analog.com>

Replace the open-coded __iio_str_to_fixpoint() parser with kstrtodec64(),
so sysfs writes of fixed-point values also accept E notation.

The fractional part only keeps the sign when the integer part is zero,
as before. The dB suffix for scale attributes is now handled by a separate
__iio_str_to_fixpoint_units() helper. It strips an optional trailing
newline and the "dB" or " dB" suffix before parsing.

When there is no fractional part (fract_mult == 0, or dec_scale == 0 for
64-bit values), try base-autodetecting kstrtoll() first so hexadecimal
and octal input keeps its meaning ("010" is still 8). Fall back to
kstrtodec64() on -EINVAL to accept E notation.

As a side effect, inputs that used to be rejected for integer-only
attributes are now accepted. An invalid octal number such as "08" is
parsed as decimal 8, and a fractional value such as "1.5" is rounded
to 2.

Signed-off-by: Rodrigo Alencar <rodrigo.alencar@analog.com>
---
 drivers/iio/industrialio-core.c | 162 ++++++++++++++++++++++------------------
 1 file changed, 90 insertions(+), 72 deletions(-)

diff --git a/drivers/iio/industrialio-core.c b/drivers/iio/industrialio-core.c
index d73f2541375f..3f5190ddddef 100644
--- a/drivers/iio/industrialio-core.c
+++ b/drivers/iio/industrialio-core.c
@@ -994,75 +994,21 @@ static ssize_t iio_read_channel_info_avail(struct device *dev,
 	}
 }
 
-/**
- * __iio_str_to_fixpoint() - Parse a fixed-point number from a string
- * @str: The string to parse
- * @fract_mult: Multiplier for the first decimal place, should be a power of 10
- * @integer: The integer part of the number
- * @fract: The fractional part of the number
- * @scale_db: True if this should parse as dB
- *
- * Returns:
- * 0 on success, or a negative error code if the string could not be parsed.
- */
-static int __iio_str_to_fixpoint(const char *str, int fract_mult,
-				 int *integer, int *fract, bool scale_db)
+static int iio_str_to_s64(const char *str, s64 *val)
 {
-	int i = 0, f = 0;
-	bool integer_part = true, negative = false;
-
-	if (fract_mult == 0) {
-		*fract = 0;
-
-		return kstrtoint(str, 0, integer);
-	}
-
-	if (str[0] == '-') {
-		negative = true;
-		str++;
-	} else if (str[0] == '+') {
-		str++;
-	}
-
-	while (*str) {
-		if ('0' <= *str && *str <= '9') {
-			if (integer_part) {
-				i = i * 10 + *str - '0';
-			} else {
-				f += fract_mult * (*str - '0');
-				fract_mult /= 10;
-			}
-		} else if (*str == '\n') {
-			if (*(str + 1) == '\0')
-				break;
-			return -EINVAL;
-		} else if (!strncmp(str, " dB", sizeof(" dB") - 1) && scale_db) {
-			/* Ignore the dB suffix */
-			str += sizeof(" dB") - 1;
-			continue;
-		} else if (!strncmp(str, "dB", sizeof("dB") - 1) && scale_db) {
-			/* Ignore the dB suffix */
-			str += sizeof("dB") - 1;
-			continue;
-		} else if (*str == '.' && integer_part) {
-			integer_part = false;
-		} else {
-			return -EINVAL;
-		}
-		str++;
-	}
-
-	if (negative) {
-		if (i)
-			i = -i;
-		else
-			f = -f;
-	}
-
-	*integer = i;
-	*fract = f;
-
-	return 0;
+	int ret;
+
+	/*
+	 * Keep hex/octal support for integers. E notation and decimal values
+	 * result in -EINVAL from kstrtoll, due to unexpected termination on '.'
+	 * or 'e'. It falls back to kstrtodec64 for decimal fixed-point numbers
+	 * with zero scale.
+	 */
+	ret = kstrtoll(str, 0, val);
+	if (ret == -EINVAL)
+		ret = kstrtodec64(str, 0, val);
+
+	return ret;
 }
 
 /**
@@ -1078,10 +1024,78 @@ static int __iio_str_to_fixpoint(const char *str, int fract_mult,
 int iio_str_to_fixpoint(const char *str, int fract_mult,
 			int *integer, int *fract)
 {
-	return __iio_str_to_fixpoint(str, fract_mult, integer, fract, false);
+	s64 dec64;
+	int ret;
+
+	if (fract_mult) {
+		/* scale = ffs(fract_mult) = ffs(10^n) = ffs(2^n * 5^n) = n + 1 */
+		ret = kstrtodec64(str, ffs(fract_mult), &dec64);
+		if (ret)
+			return ret;
+
+		dec64 = div_s64_rem(dec64, fract_mult * 10, fract);
+		/* only carry the sign in fract if the integer is zero */
+		if (dec64)
+			*fract = abs(*fract);
+	} else {
+		ret = iio_str_to_s64(str, &dec64);
+		if (ret)
+			return ret;
+
+		*fract = 0;
+	}
+
+	if (dec64 > INT_MAX || dec64 < INT_MIN)
+		return -ERANGE;
+
+	*integer = dec64;
+
+	return 0;
 }
 EXPORT_SYMBOL_GPL(iio_str_to_fixpoint);
 
+/**
+ * iio_str_to_fixpoint_units() - Parse a fixed-point number with units from a string
+ * @str: The string to parse
+ * @units: The units to expect at the end of the string
+ * @fract_mult: Multiplier for the first decimal place, should be a power of 10
+ * @integer: The integer part of the number
+ * @fract: The fractional part of the number
+ *
+ * Parses a fixed-point/decimal number from a string, optionally with units at
+ * the end. The string should be in the format "<number>[ <units>]". For example,
+ * The input "7.2 dB" would be parsed as the number 7200000 with units = "dB"
+ * and frac_mult = 100000.
+ *
+ * Returns:
+ * 0 on success, or a negative error code if the string could not be parsed.
+ */
+static int iio_str_to_fixpoint_units(const char *str, const char *units,
+				     int fract_mult, int *integer, int *fract)
+{
+	size_t units_len = strlen(units);
+	size_t s_len = strlen(str);
+	const char *dec_str = str;
+	char buf[64];
+
+	if (s_len && str[s_len - 1] == '\n')
+		s_len--;
+
+	if (s_len > units_len &&
+	    !strncmp(str + s_len - units_len, units, units_len)) {
+		s_len -= units_len;
+		if (str[s_len - 1] == ' ')
+			s_len--;
+		if (s_len >= sizeof(buf))
+			return -EINVAL;
+		memcpy(buf, str, s_len);
+		buf[s_len] = '\0';
+		dec_str = buf;
+	}
+
+	return iio_str_to_fixpoint(dec_str, fract_mult, integer, fract);
+}
+
 static ssize_t iio_write_channel_info(struct device *dev,
 				      struct device_attribute *attr,
 				      const char *buf,
@@ -1146,14 +1160,18 @@ static ssize_t iio_write_channel_info(struct device *dev,
 		if (dec_scale)
 			ret = kstrtodec64(buf, dec_scale, &integer64);
 		else
-			ret = kstrtoll(buf, 0, &integer64);
+			ret = iio_str_to_s64(buf, &integer64);
 		if (ret)
 			return ret;
 
 		iio_val_s64_decompose(integer64, &integer, &fract);
 	} else {
-		ret = __iio_str_to_fixpoint(buf, fract_mult, &integer, &fract,
-					    scale_db);
+		if (scale_db)
+			ret = iio_str_to_fixpoint_units(buf, "dB", fract_mult,
+							&integer, &fract);
+		else
+			ret = iio_str_to_fixpoint(buf, fract_mult, &integer,
+						  &fract);
 		if (ret)
 			return ret;
 	}

-- 
2.43.0



^ permalink raw reply	[flat|nested] 12+ messages in thread

* [PATCH v4 6/6] iio: test: add kunit tests for channel info writes
  2026-10-26 11:54 [PATCH v4 0/6] Add E notation support to kstrtodec64() and IIO sysfs writes Rodrigo Alencar via B4 Relay
                   ` (4 preceding siblings ...)
  2026-10-26 11:54 ` [PATCH v4 5/6] iio: core: use kstrtodec64() to parse fixed-point values Rodrigo Alencar via B4 Relay
@ 2026-10-26 11:54 ` Rodrigo Alencar via B4 Relay
  5 siblings, 0 replies; 12+ messages in thread
From: Rodrigo Alencar via B4 Relay @ 2026-10-26 11:54 UTC (permalink / raw)
  To: linux-kernel, linux-iio
  Cc: Andrew Morton, Jonathan Cameron, David Lechner, Andy Shevchenko,
	Rodrigo Alencar

From: Rodrigo Alencar <rodrigo.alencar@analog.com>

Add a KUnit test suite covering iio_write_channel_info(), the sysfs
store handler that parses a channel info value according to the format
reported by write_raw_get_fmt() and hands it to write_raw(). The
function is made visible to KUnit through VISIBLE_IF_KUNIT and
EXPORT_SYMBOL_IF_KUNIT, so it stays static in regular builds.

The suite uses an unregistered iio_dev allocated on a KUnit device,
with stub write_raw_get_fmt() and write_raw() callbacks that select
the format under test and capture the parsed values. Cases are grouped
by format:

  - IIO_VAL_INT: decimal, hex and octal input, decimal fallback with
    rounding and E notation, and 32-bit range checks.
  - IIO_VAL_INT_PLUS_MICRO / IIO_VAL_INT_PLUS_NANO: sign handling,
    rounding of extra digits (ties to even), E notation and malformed
    input.
  - IIO_VAL_INT_PLUS_MICRO_DB: optional "dB" / " dB" suffix.
  - IIO_VAL_CHAR: single character input.
  - IIO_VAL_INT_64 and IIO_VAL_DECIMAL64_*: 64-bit values split into
    val/val2, including the S64_MAX / S64_MIN boundaries.

Further cases cover unsupported formats, the default micro precision
used when write_raw_get_fmt() is absent, a missing write_raw(), and
error propagation from write_raw().

Signed-off-by: Rodrigo Alencar <rodrigo.alencar@analog.com>
---
 MAINTAINERS                               |   1 +
 drivers/iio/iio_core.h                    |   3 +
 drivers/iio/industrialio-core.c           |   9 +-
 drivers/iio/test/Kconfig                  |  14 ++
 drivers/iio/test/Makefile                 |   1 +
 drivers/iio/test/iio-test-channel-write.c | 360 ++++++++++++++++++++++++++++++
 6 files changed, 384 insertions(+), 4 deletions(-)

diff --git a/MAINTAINERS b/MAINTAINERS
index 52024397dc78..ef8b912f593b 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -12758,6 +12758,7 @@ M:	Rodrigo Alencar <rodrigo.alencar@analog.com>
 L:	linux-iio@vger.kernel.org
 S:	Maintained
 F:	drivers/iio/test/iio-test-channel-prefix.c
+F:	drivers/iio/test/iio-test-channel-write.c
 F:	drivers/iio/test/iio-test-format.c
 
 IIO UNIT CONVERTER
diff --git a/drivers/iio/iio_core.h b/drivers/iio/iio_core.h
index 59fa1bd9924b..08f0cc28ad8c 100644
--- a/drivers/iio/iio_core.h
+++ b/drivers/iio/iio_core.h
@@ -114,6 +114,9 @@ ssize_t __iio_chan_prefix_emit(struct device *dev,
 			       const struct iio_chan_spec *chan,
 			       enum iio_shared_by shared_by,
 			       char *buf, size_t len);
+ssize_t iio_write_channel_info(struct device *dev,
+			       struct device_attribute *attr,
+			       const char *buf, size_t len);
 #endif
 
 #endif
diff --git a/drivers/iio/industrialio-core.c b/drivers/iio/industrialio-core.c
index 3f5190ddddef..1e74816ae14a 100644
--- a/drivers/iio/industrialio-core.c
+++ b/drivers/iio/industrialio-core.c
@@ -1096,10 +1096,10 @@ static int iio_str_to_fixpoint_units(const char *str, const char *units,
 	return iio_str_to_fixpoint(dec_str, fract_mult, integer, fract);
 }
 
-static ssize_t iio_write_channel_info(struct device *dev,
-				      struct device_attribute *attr,
-				      const char *buf,
-				      size_t len)
+VISIBLE_IF_KUNIT
+ssize_t iio_write_channel_info(struct device *dev,
+			       struct device_attribute *attr,
+			       const char *buf, size_t len)
 {
 	struct iio_dev *indio_dev = dev_to_iio_dev(dev);
 	struct iio_dev_attr *this_attr = to_iio_dev_attr(attr);
@@ -1183,6 +1183,7 @@ static ssize_t iio_write_channel_info(struct device *dev,
 
 	return len;
 }
+EXPORT_SYMBOL_IF_KUNIT(iio_write_channel_info);
 
 static
 int __iio_device_attr_init(struct device *dev,
diff --git a/drivers/iio/test/Kconfig b/drivers/iio/test/Kconfig
index 04930b4fdd5d..af75c032a330 100644
--- a/drivers/iio/test/Kconfig
+++ b/drivers/iio/test/Kconfig
@@ -17,6 +17,20 @@ config IIO_CHANNEL_PREFIX_KUNIT_TEST
 
 	  If unsure, say N.
 
+config IIO_CHANNEL_WRITE_KUNIT_TEST
+	tristate "Test IIO channel info write" if !KUNIT_ALL_TESTS
+	depends on KUNIT && IIO
+	default KUNIT_ALL_TESTS
+	help
+	  Build unit tests for iio_write_channel_info(), the sysfs store
+	  handler that parses channel info values according to the format
+	  reported by write_raw_get_fmt() and passes them to write_raw().
+
+	  For more information on KUnit and unit tests in general, please refer
+	  to the KUnit documentation in Documentation/dev-tools/kunit/.
+
+	  If unsure, say N.
+
 config IIO_GTS_KUNIT_TEST
 	tristate "Test IIO gain-time-scale helpers" if !KUNIT_ALL_TESTS
 	depends on KUNIT
diff --git a/drivers/iio/test/Makefile b/drivers/iio/test/Makefile
index ba08afa7f3f2..bcaf82ffd9c1 100644
--- a/drivers/iio/test/Makefile
+++ b/drivers/iio/test/Makefile
@@ -5,6 +5,7 @@
 
 # Keep in alphabetical order
 obj-$(CONFIG_IIO_CHANNEL_PREFIX_KUNIT_TEST) += iio-test-channel-prefix.o
+obj-$(CONFIG_IIO_CHANNEL_WRITE_KUNIT_TEST) += iio-test-channel-write.o
 obj-$(CONFIG_IIO_RESCALE_KUNIT_TEST) += iio-test-rescale.o
 obj-$(CONFIG_IIO_FORMAT_KUNIT_TEST) += iio-test-format.o
 obj-$(CONFIG_IIO_GTS_KUNIT_TEST) += iio-test-gts.o
diff --git a/drivers/iio/test/iio-test-channel-write.c b/drivers/iio/test/iio-test-channel-write.c
new file mode 100644
index 000000000000..9205a5674d0e
--- /dev/null
+++ b/drivers/iio/test/iio-test-channel-write.c
@@ -0,0 +1,360 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Unit tests for IIO channel info writes.
+ */
+
+#include <kunit/device.h>
+#include <kunit/test.h>
+#include <kunit/visibility.h>
+
+#include <linux/iio/iio.h>
+#include <linux/iio/sysfs.h>
+#include <linux/iio/types.h>
+#include <linux/string.h>
+
+#include "../iio_core.h"
+
+#define IIO_TEST_UNSUPPORTED_FMT	IIO_VAL_FRACTIONAL
+
+struct iio_test_write_priv {
+	int fmt;
+	int ret;
+	int val;
+	int val2;
+	bool called;
+};
+
+struct iio_test_write_ctx {
+	struct iio_dev *indio_dev;
+	struct iio_dev_attr attr;
+};
+
+static int iio_test_write_raw_get_fmt(struct iio_dev *indio_dev,
+				      struct iio_chan_spec const *chan,
+				      long mask)
+{
+	struct iio_test_write_priv *priv = iio_priv(indio_dev);
+
+	return priv->fmt;
+}
+
+static int iio_test_write_raw(struct iio_dev *indio_dev,
+			      struct iio_chan_spec const *chan,
+			      int val, int val2, long mask)
+{
+	struct iio_test_write_priv *priv = iio_priv(indio_dev);
+
+	priv->val = val;
+	priv->val2 = val2;
+	priv->called = true;
+
+	return priv->ret;
+}
+
+static const struct iio_info iio_test_write_info = {
+	.write_raw = iio_test_write_raw,
+	.write_raw_get_fmt = iio_test_write_raw_get_fmt,
+};
+
+static const struct iio_info iio_test_write_info_no_fmt = {
+	.write_raw = iio_test_write_raw,
+};
+
+static const struct iio_info iio_test_write_info_no_write = {
+};
+
+static const struct iio_chan_spec iio_test_write_chan = {
+	.type = IIO_VOLTAGE,
+	.indexed = 1,
+	.info_mask_separate = BIT(IIO_CHAN_INFO_SCALE),
+};
+
+static int iio_test_write_init(struct kunit *test)
+{
+	struct iio_test_write_ctx *ctx;
+	struct device *dev;
+
+	ctx = kunit_kzalloc(test, sizeof(*ctx), GFP_KERNEL);
+	KUNIT_ASSERT_NOT_NULL(test, ctx);
+
+	dev = kunit_device_register(test, "iio-test-channel-write");
+	KUNIT_ASSERT_NOT_ERR_OR_NULL(test, dev);
+
+	ctx->indio_dev = devm_iio_device_alloc(dev,
+					       sizeof(struct iio_test_write_priv));
+	KUNIT_ASSERT_NOT_NULL(test, ctx->indio_dev);
+	ctx->indio_dev->info = &iio_test_write_info;
+
+	ctx->attr.c = &iio_test_write_chan;
+	ctx->attr.address = IIO_CHAN_INFO_SCALE;
+
+	test->priv = ctx;
+
+	return 0;
+}
+
+static struct iio_test_write_priv *iio_test_write_priv(struct kunit *test)
+{
+	struct iio_test_write_ctx *ctx = test->priv;
+
+	return iio_priv(ctx->indio_dev);
+}
+
+static ssize_t iio_test_write(struct kunit *test, int fmt, const char *str)
+{
+	struct iio_test_write_ctx *ctx = test->priv;
+	struct iio_test_write_priv *priv = iio_priv(ctx->indio_dev);
+
+	priv->fmt = fmt;
+	priv->called = false;
+	priv->val = 0xdead;
+	priv->val2 = 0xbeef;
+
+	return iio_write_channel_info(&ctx->indio_dev->dev, &ctx->attr.dev_attr,
+				      str, strlen(str));
+}
+
+#define EXPECT_WRITE(_test, _fmt, _str, _val, _val2) do {			\
+		struct kunit *__test = (_test);					\
+		struct iio_test_write_priv *__priv;				\
+		const char *__str = (_str);					\
+		ssize_t __len = strlen(__str);					\
+		ssize_t __ret;							\
+										\
+		__ret = iio_test_write(__test, (_fmt), __str);			\
+		__priv = iio_test_write_priv(__test);				\
+		KUNIT_EXPECT_EQ_MSG(__test, __ret, __len,			\
+				    "input: \"%s\"", __str);			\
+		KUNIT_EXPECT_TRUE_MSG(__test, __priv->called,			\
+				      "input: \"%s\"", __str);			\
+		KUNIT_EXPECT_EQ_MSG(__test, __priv->val, (_val),		\
+				    "input: \"%s\"", __str);			\
+		KUNIT_EXPECT_EQ_MSG(__test, __priv->val2, (_val2),		\
+				    "input: \"%s\"", __str);			\
+	} while (0)
+
+#define EXPECT_WRITE_S64(_test, _fmt, _str, _expected) do {			\
+		s32 __lo, __hi;							\
+										\
+		iio_val_s64_decompose((_expected), &__lo, &__hi);		\
+		EXPECT_WRITE(_test, _fmt, _str, __lo, __hi);			\
+	} while (0)
+
+#define EXPECT_WRITE_ERR(_test, _fmt, _str, _err) do {				\
+		struct kunit *__test = (_test);					\
+		const char *__str = (_str);					\
+		ssize_t __ret;							\
+										\
+		__ret = iio_test_write(__test, (_fmt), __str);			\
+		KUNIT_EXPECT_EQ_MSG(__test, __ret, (ssize_t)(_err),		\
+				    "input: \"%s\"", __str);			\
+		KUNIT_EXPECT_FALSE_MSG(__test,					\
+				       iio_test_write_priv(__test)->called,	\
+				       "input: \"%s\"", __str);			\
+	} while (0)
+
+static void iio_test_write_int(struct kunit *test)
+{
+	EXPECT_WRITE(test, IIO_VAL_INT, "0", 0, 0);
+	EXPECT_WRITE(test, IIO_VAL_INT, "42\n", 42, 0);
+	EXPECT_WRITE(test, IIO_VAL_INT, "-7", -7, 0);
+	EXPECT_WRITE(test, IIO_VAL_INT, "+7", 7, 0);
+	EXPECT_WRITE(test, IIO_VAL_INT, "2147483647", INT_MAX, 0);
+	EXPECT_WRITE(test, IIO_VAL_INT, "-2147483648", INT_MIN, 0);
+
+	/* hex and octal are still accepted for integers */
+	EXPECT_WRITE(test, IIO_VAL_INT, "0x10", 16, 0);
+	EXPECT_WRITE(test, IIO_VAL_INT, "010", 8, 0);
+
+	/* decimal fallback: fractional digits are rounded, ties to even */
+	EXPECT_WRITE(test, IIO_VAL_INT, "1.9", 2, 0);
+	EXPECT_WRITE(test, IIO_VAL_INT, "-1.9", -2, 0);
+	EXPECT_WRITE(test, IIO_VAL_INT, "1e3", 1000, 0);
+	EXPECT_WRITE(test, IIO_VAL_INT, "15e-1", 2, 0);
+	EXPECT_WRITE(test, IIO_VAL_INT, "25e-1", 2, 0);
+
+	EXPECT_WRITE_ERR(test, IIO_VAL_INT, "2147483648", -ERANGE);
+	EXPECT_WRITE_ERR(test, IIO_VAL_INT, "-2147483649", -ERANGE);
+	EXPECT_WRITE_ERR(test, IIO_VAL_INT, "", -EINVAL);
+	EXPECT_WRITE_ERR(test, IIO_VAL_INT, "abc", -EINVAL);
+	EXPECT_WRITE_ERR(test, IIO_VAL_INT, "12abc", -EINVAL);
+}
+
+static void iio_test_write_int_plus_micro(struct kunit *test)
+{
+	EXPECT_WRITE(test, IIO_VAL_INT_PLUS_MICRO, "0", 0, 0);
+	EXPECT_WRITE(test, IIO_VAL_INT_PLUS_MICRO, "1.5\n", 1, 500000);
+	EXPECT_WRITE(test, IIO_VAL_INT_PLUS_MICRO, "-1.5", -1, 500000);
+	EXPECT_WRITE(test, IIO_VAL_INT_PLUS_MICRO, "-0.5", 0, -500000);
+	EXPECT_WRITE(test, IIO_VAL_INT_PLUS_MICRO, ".25", 0, 250000);
+	EXPECT_WRITE(test, IIO_VAL_INT_PLUS_MICRO, "3.", 3, 0);
+	EXPECT_WRITE(test, IIO_VAL_INT_PLUS_MICRO, "0.000001", 0, 1);
+	EXPECT_WRITE(test, IIO_VAL_INT_PLUS_MICRO, "-0.000001", 0, -1);
+
+	/* digits beyond the precision are rounded, ties to even */
+	EXPECT_WRITE(test, IIO_VAL_INT_PLUS_MICRO, "1.2345678", 1, 234568);
+	EXPECT_WRITE(test, IIO_VAL_INT_PLUS_MICRO, "0.0000009", 0, 1);
+	EXPECT_WRITE(test, IIO_VAL_INT_PLUS_MICRO, "0.0000005", 0, 0);
+	EXPECT_WRITE(test, IIO_VAL_INT_PLUS_MICRO, "0.0000015", 0, 2);
+	EXPECT_WRITE(test, IIO_VAL_INT_PLUS_MICRO, "-0.0000015", 0, -2);
+	EXPECT_WRITE(test, IIO_VAL_INT_PLUS_MICRO, "0.9999995", 1, 0);
+
+	/* E notation */
+	EXPECT_WRITE(test, IIO_VAL_INT_PLUS_MICRO, "1.5e3", 1500, 0);
+	EXPECT_WRITE(test, IIO_VAL_INT_PLUS_MICRO, "1e-3", 0, 1000);
+	EXPECT_WRITE(test, IIO_VAL_INT_PLUS_MICRO, "-25E-1\n", -2, 500000);
+
+	EXPECT_WRITE_ERR(test, IIO_VAL_INT_PLUS_MICRO, "2147483648", -ERANGE);
+	EXPECT_WRITE_ERR(test, IIO_VAL_INT_PLUS_MICRO, "", -EINVAL);
+	EXPECT_WRITE_ERR(test, IIO_VAL_INT_PLUS_MICRO, ".", -EINVAL);
+	EXPECT_WRITE_ERR(test, IIO_VAL_INT_PLUS_MICRO, "1.5x", -EINVAL);
+	EXPECT_WRITE_ERR(test, IIO_VAL_INT_PLUS_MICRO, "1.2.3", -EINVAL);
+	EXPECT_WRITE_ERR(test, IIO_VAL_INT_PLUS_MICRO, "0x10", -EINVAL);
+}
+
+static void iio_test_write_int_plus_nano(struct kunit *test)
+{
+	EXPECT_WRITE(test, IIO_VAL_INT_PLUS_NANO, "1.5", 1, 500000000);
+	EXPECT_WRITE(test, IIO_VAL_INT_PLUS_NANO, "0.000000001", 0, 1);
+	EXPECT_WRITE(test, IIO_VAL_INT_PLUS_NANO, "-0.000000001", 0, -1);
+	EXPECT_WRITE(test, IIO_VAL_INT_PLUS_NANO, "-2.000000001", -2, 1);
+	EXPECT_WRITE(test, IIO_VAL_INT_PLUS_NANO, "1.0000000019", 1, 2);
+	EXPECT_WRITE(test, IIO_VAL_INT_PLUS_NANO, "25e-10", 0, 2);
+}
+
+static void iio_test_write_int_plus_micro_db(struct kunit *test)
+{
+	EXPECT_WRITE(test, IIO_VAL_INT_PLUS_MICRO_DB, "-3.5 dB\n", -3, 500000);
+	EXPECT_WRITE(test, IIO_VAL_INT_PLUS_MICRO_DB, "-3.5dB", -3, 500000);
+	EXPECT_WRITE(test, IIO_VAL_INT_PLUS_MICRO_DB, "2dB", 2, 0);
+	EXPECT_WRITE(test, IIO_VAL_INT_PLUS_MICRO_DB, "-0.25 dB", 0, -250000);
+
+	/* the unit suffix is optional */
+	EXPECT_WRITE(test, IIO_VAL_INT_PLUS_MICRO_DB, "1.25", 1, 250000);
+	EXPECT_WRITE(test, IIO_VAL_INT_PLUS_MICRO_DB, "1.25\n", 1, 250000);
+
+	EXPECT_WRITE_ERR(test, IIO_VAL_INT_PLUS_MICRO_DB, "dB", -EINVAL);
+	EXPECT_WRITE_ERR(test, IIO_VAL_INT_PLUS_MICRO_DB, "1.5 db", -EINVAL);
+	EXPECT_WRITE_ERR(test, IIO_VAL_INT_PLUS_MICRO_DB, "2.5 dBm", -EINVAL);
+	EXPECT_WRITE_ERR(test, IIO_VAL_INT_PLUS_MICRO_DB, "7.3dBm", -EINVAL);
+	EXPECT_WRITE_ERR(test, IIO_VAL_INT_PLUS_MICRO_DB, "10 ddB", -EINVAL);
+	EXPECT_WRITE_ERR(test, IIO_VAL_INT_PLUS_MICRO_DB, "1.5 V", -EINVAL);
+}
+
+static void iio_test_write_char(struct kunit *test)
+{
+	EXPECT_WRITE(test, IIO_VAL_CHAR, "x", 'x', 0);
+	EXPECT_WRITE(test, IIO_VAL_CHAR, "K\n", 'K', 0);
+
+	EXPECT_WRITE_ERR(test, IIO_VAL_CHAR, "", -EINVAL);
+}
+
+static void iio_test_write_int_64(struct kunit *test)
+{
+	EXPECT_WRITE_S64(test, IIO_VAL_INT_64, "0", 0);
+	EXPECT_WRITE_S64(test, IIO_VAL_INT_64, "-1", -1);
+	EXPECT_WRITE_S64(test, IIO_VAL_INT_64, "4294967296\n", 4294967296LL);
+	EXPECT_WRITE_S64(test, IIO_VAL_INT_64, "0x100000000", 0x100000000LL);
+	EXPECT_WRITE_S64(test, IIO_VAL_INT_64, "9223372036854775807", S64_MAX);
+	EXPECT_WRITE_S64(test, IIO_VAL_INT_64, "-9223372036854775808", S64_MIN);
+
+	/* decimal fallback */
+	EXPECT_WRITE_S64(test, IIO_VAL_INT_64, "1.9", 2);
+	EXPECT_WRITE_S64(test, IIO_VAL_INT_64, "5e9", 5000000000LL);
+
+	EXPECT_WRITE_ERR(test, IIO_VAL_INT_64, "9223372036854775808", -ERANGE);
+	EXPECT_WRITE_ERR(test, IIO_VAL_INT_64, "abc", -EINVAL);
+}
+
+static void iio_test_write_decimal64(struct kunit *test)
+{
+	EXPECT_WRITE_S64(test, IIO_VAL_DECIMAL64_MILLI, "1.5", 1500);
+	EXPECT_WRITE_S64(test, IIO_VAL_DECIMAL64_MILLI, "-0.001", -1);
+	EXPECT_WRITE_S64(test, IIO_VAL_DECIMAL64_MILLI, "0.0009", 1);
+
+	EXPECT_WRITE_S64(test, IIO_VAL_DECIMAL64_MICRO, "1.5e3", 1500000000LL);
+	EXPECT_WRITE_S64(test, IIO_VAL_DECIMAL64_MICRO, "-0.000001\n", -1);
+	EXPECT_WRITE_S64(test, IIO_VAL_DECIMAL64_MICRO, "5000", 5000000000LL);
+
+	EXPECT_WRITE_S64(test, IIO_VAL_DECIMAL64_NANO, "1.000000001",
+			 1000000001LL);
+	EXPECT_WRITE_S64(test, IIO_VAL_DECIMAL64_PICO, "1e-12", 1);
+	EXPECT_WRITE_S64(test, IIO_VAL_DECIMAL64_PICO, "-2.5", -2500000000000LL);
+	EXPECT_WRITE_S64(test, IIO_VAL_DECIMAL64_FEMTO, "9223.372036854775807",
+			 S64_MAX);
+
+	/* no hex support when a decimal scale is set */
+	EXPECT_WRITE_ERR(test, IIO_VAL_DECIMAL64_MICRO, "0x10", -EINVAL);
+	EXPECT_WRITE_ERR(test, IIO_VAL_DECIMAL64_FEMTO, "9223.372036854775808",
+			 -ERANGE);
+	EXPECT_WRITE_ERR(test, IIO_VAL_DECIMAL64_MICRO, "1e20", -ERANGE);
+	EXPECT_WRITE_ERR(test, IIO_VAL_DECIMAL64_MICRO, "1.5 Hz", -EINVAL);
+}
+
+static void iio_test_write_unsupported_fmt(struct kunit *test)
+{
+	EXPECT_WRITE_ERR(test, IIO_TEST_UNSUPPORTED_FMT, "1", -EINVAL);
+	EXPECT_WRITE_ERR(test, -EBUSY, "1", -EINVAL);
+}
+
+static void iio_test_write_no_get_fmt(struct kunit *test)
+{
+	struct iio_test_write_ctx *ctx = test->priv;
+
+	/* without write_raw_get_fmt() values are parsed as IIO_VAL_INT_PLUS_MICRO */
+	ctx->indio_dev->info = &iio_test_write_info_no_fmt;
+
+	EXPECT_WRITE(test, 0, "1.5", 1, 500000);
+	EXPECT_WRITE(test, 0, "-0.000001", 0, -1);
+	EXPECT_WRITE(test, 0, "1.2345678", 1, 234568);
+	EXPECT_WRITE_ERR(test, 0, "0x10", -EINVAL);
+}
+
+static void iio_test_write_no_write_raw(struct kunit *test)
+{
+	struct iio_test_write_ctx *ctx = test->priv;
+
+	ctx->indio_dev->info = &iio_test_write_info_no_write;
+
+	EXPECT_WRITE_ERR(test, IIO_VAL_INT, "1", -EINVAL);
+}
+
+static void iio_test_write_raw_error(struct kunit *test)
+{
+	struct iio_test_write_priv *priv = iio_test_write_priv(test);
+	ssize_t ret;
+
+	priv->ret = -EBUSY;
+	ret = iio_test_write(test, IIO_VAL_INT, "1");
+	KUNIT_EXPECT_EQ(test, ret, (ssize_t)-EBUSY);
+	KUNIT_EXPECT_TRUE(test, priv->called);
+	KUNIT_EXPECT_EQ(test, priv->val, 1);
+}
+
+static struct kunit_case iio_channel_write_test_cases[] = {
+	KUNIT_CASE(iio_test_write_int),
+	KUNIT_CASE(iio_test_write_int_plus_micro),
+	KUNIT_CASE(iio_test_write_int_plus_nano),
+	KUNIT_CASE(iio_test_write_int_plus_micro_db),
+	KUNIT_CASE(iio_test_write_char),
+	KUNIT_CASE(iio_test_write_int_64),
+	KUNIT_CASE(iio_test_write_decimal64),
+	KUNIT_CASE(iio_test_write_unsupported_fmt),
+	KUNIT_CASE(iio_test_write_no_get_fmt),
+	KUNIT_CASE(iio_test_write_no_write_raw),
+	KUNIT_CASE(iio_test_write_raw_error),
+	{ }
+};
+
+static struct kunit_suite iio_channel_write_test_suite = {
+	.name = "iio-channel-write",
+	.init = iio_test_write_init,
+	.test_cases = iio_channel_write_test_cases,
+};
+
+kunit_test_suite(iio_channel_write_test_suite);
+
+MODULE_AUTHOR("Rodrigo Alencar <rodrigo.alencar@analog.com>");
+MODULE_DESCRIPTION("Test IIO channel info write parsing");
+MODULE_LICENSE("GPL");
+MODULE_IMPORT_NS("EXPORTED_FOR_KUNIT_TESTING");

-- 
2.43.0



^ permalink raw reply	[flat|nested] 12+ messages in thread

* Re: [PATCH v4 4/6] iio: test: rescale: compare scales against the precision of the output
  2026-10-26 11:54 ` [PATCH v4 4/6] iio: test: rescale: compare scales against the precision of the output Rodrigo Alencar via B4 Relay
@ 2026-10-26 14:40   ` Andy Shevchenko
  2026-10-26 15:29     ` Rodrigo Alencar
  0 siblings, 1 reply; 12+ messages in thread
From: Andy Shevchenko @ 2026-10-26 14:40 UTC (permalink / raw)
  To: rodrigo.alencar
  Cc: linux-kernel, linux-iio, Andrew Morton, Jonathan Cameron,
	David Lechner, Andy Shevchenko

On Mon, Oct 26, 2026 at 08:54:08AM -0300, Rodrigo Alencar via B4 Relay wrote:
> 
> The scale tests format the rescaled value with iio_format_value(), which
> prints at most nano precision, and require the relative error to the
> expected value to be below 1 ppm. Both strings are parsed back with
> iio_str_to_fixpoint() at nano precision, so for the small scale cases the
> check only passes because the expected value, like the formatted one, is
> truncated to nano. For example, "0.000000033594" is parsed as 33 nano,
> equal to the formatted "0.000000033", although the actual error is about
> 1.8%. If the expected value were rounded instead, it would be parsed as
> 34 nano and the test would fail even though the output is as accurate as
> the format allows.
> 
> Parse both values with kstrtodec64() at a scale of 10, one digit more
> than the output precision, so the expected value keeps the digits lost
> when formatting. Scale 10 is the largest that still fits the overflow
> cases (about 2.1e8) in an s64. Compute the relative error with
> mul_u64_u64_div_u64(), as the product with 10^6 no longer fits in 64 bits
> for those cases.
> 
> Then allow a relative error up to the relative size of one nano of the
> expected value, the precision of the formatted output. This is 0 ppm for
> values of 0.01 and above, so those cases are checked as before, and it
> accepts the formatting error for the small scale cases.

...

> +/*
> + * iio_format_value() prints scales with up to nano precision. Parse values
> + * with one more digit, so the expected values keep the precision lost there.
> + * Scale 10 is the largest that fits the overflow test cases in an s64.
>   */
> +#define IIO_TEST_DEC_SCALE	10
> +#define IIO_TEST_NANO		10	/* one nano at IIO_TEST_DEC_SCALE */

I would put them as (9 + 1) to show that actually show that "with one more digit".
I also don't follow the comment near to the last definition. What does it mean?

-- 
With Best Regards,
Andy Shevchenko



^ permalink raw reply	[flat|nested] 12+ messages in thread

* Re: [PATCH v4 5/6] iio: core: use kstrtodec64() to parse fixed-point values
  2026-10-26 11:54 ` [PATCH v4 5/6] iio: core: use kstrtodec64() to parse fixed-point values Rodrigo Alencar via B4 Relay
@ 2026-10-26 14:54   ` Andy Shevchenko
  2026-10-26 15:53     ` Rodrigo Alencar
  0 siblings, 1 reply; 12+ messages in thread
From: Andy Shevchenko @ 2026-10-26 14:54 UTC (permalink / raw)
  To: rodrigo.alencar
  Cc: linux-kernel, linux-iio, Andrew Morton, Jonathan Cameron,
	David Lechner, Andy Shevchenko

On Mon, Oct 26, 2026 at 08:54:09AM -0300, Rodrigo Alencar via B4 Relay wrote:

> Replace the open-coded __iio_str_to_fixpoint() parser with kstrtodec64(),
> so sysfs writes of fixed-point values also accept E notation.
> 
> The fractional part only keeps the sign when the integer part is zero,
> as before. The dB suffix for scale attributes is now handled by a separate
> __iio_str_to_fixpoint_units() helper. It strips an optional trailing
> newline and the "dB" or " dB" suffix before parsing.
> 
> When there is no fractional part (fract_mult == 0, or dec_scale == 0 for
> 64-bit values), try base-autodetecting kstrtoll() first so hexadecimal
> and octal input keeps its meaning ("010" is still 8). Fall back to
> kstrtodec64() on -EINVAL to accept E notation.

> As a side effect, inputs that used to be rejected for integer-only
> attributes are now accepted. An invalid octal number such as "08" is
> parsed as decimal 8, and a fractional value such as "1.5" is rounded
> to 2.

The main question here is: Do we want these side effects?
And follow up question is: Where is it going to be documented?
I consider this paragraph is very important for the users.

...

> +	/*
> +	 * Keep hex/octal support for integers. E notation and decimal values
> +	 * result in -EINVAL from kstrtoll, due to unexpected termination on '.'

kstrtoll()

> +	 * or 'e'. It falls back to kstrtodec64 for decimal fixed-point numbers
> +	 * with zero scale.
> +	 */
> +	ret = kstrtoll(str, 0, val);
> +	if (ret == -EINVAL)

> +		ret = kstrtodec64(str, 0, val);

		return kstrtodec64(str, 0, val);

?

> +	return ret;

...

>  int iio_str_to_fixpoint(const char *str, int fract_mult,
>  			int *integer, int *fract)
>  {
> -	return __iio_str_to_fixpoint(str, fract_mult, integer, fract, false);
> +	s64 dec64;
> +	int ret;
> +
> +	if (fract_mult) {
> +		/* scale = ffs(fract_mult) = ffs(10^n) = ffs(2^n * 5^n) = n + 1 */
> +		ret = kstrtodec64(str, ffs(fract_mult), &dec64);
> +		if (ret)
> +			return ret;
> +
> +		dec64 = div_s64_rem(dec64, fract_mult * 10, fract);
> +		/* only carry the sign in fract if the integer is zero */
> +		if (dec64)
> +			*fract = abs(*fract);

Should we actually switch to UB-free abs() implementation?

> +	} else {
> +		ret = iio_str_to_s64(str, &dec64);
> +		if (ret)
> +			return ret;
> +
> +		*fract = 0;
> +	}
> +
> +	if (dec64 > INT_MAX || dec64 < INT_MIN)
> +		return -ERANGE;
> +
> +	*integer = dec64;
> +
> +	return 0;
>  }

...

> +		if (scale_db)
> +			ret = iio_str_to_fixpoint_units(buf, "dB", fract_mult,
> +							&integer, &fract);

Can we actually make units to be the last argument? In this case we might join
two calls together, it also might be beneficial from code generation point of
view (as the units version calls the basic one at the end).

> +		else
> +			ret = iio_str_to_fixpoint(buf, fract_mult, &integer,
> +						  &fract);

-- 
With Best Regards,
Andy Shevchenko



^ permalink raw reply	[flat|nested] 12+ messages in thread

* Re: [PATCH v4 4/6] iio: test: rescale: compare scales against the precision of the output
  2026-10-26 14:40   ` Andy Shevchenko
@ 2026-10-26 15:29     ` Rodrigo Alencar
  0 siblings, 0 replies; 12+ messages in thread
From: Rodrigo Alencar @ 2026-10-26 15:29 UTC (permalink / raw)
  To: Andy Shevchenko
  Cc: linux-kernel, linux-iio, Andrew Morton, Jonathan Cameron,
	David Lechner, Andy Shevchenko

On 26/10/26 16:40, Andy Shevchenko wrote:
> On Mon, Oct 26, 2026 at 08:54:08AM -0300, Rodrigo Alencar via B4 Relay wrote:
> > 
> > The scale tests format the rescaled value with iio_format_value(), which
> > prints at most nano precision, and require the relative error to the
> > expected value to be below 1 ppm. Both strings are parsed back with
> > iio_str_to_fixpoint() at nano precision, so for the small scale cases the
> > check only passes because the expected value, like the formatted one, is
> > truncated to nano. For example, "0.000000033594" is parsed as 33 nano,
> > equal to the formatted "0.000000033", although the actual error is about
> > 1.8%. If the expected value were rounded instead, it would be parsed as
> > 34 nano and the test would fail even though the output is as accurate as
> > the format allows.
> > 
> > Parse both values with kstrtodec64() at a scale of 10, one digit more
> > than the output precision, so the expected value keeps the digits lost
> > when formatting. Scale 10 is the largest that still fits the overflow
> > cases (about 2.1e8) in an s64. Compute the relative error with
> > mul_u64_u64_div_u64(), as the product with 10^6 no longer fits in 64 bits
> > for those cases.
> > 
> > Then allow a relative error up to the relative size of one nano of the
> > expected value, the precision of the formatted output. This is 0 ppm for
> > values of 0.01 and above, so those cases are checked as before, and it
> > accepts the formatting error for the small scale cases.
> 
> ...
> 
> > +/*
> > + * iio_format_value() prints scales with up to nano precision. Parse values
> > + * with one more digit, so the expected values keep the precision lost there.
> > + * Scale 10 is the largest that fits the overflow test cases in an s64.
> >   */
> > +#define IIO_TEST_DEC_SCALE	10
> > +#define IIO_TEST_NANO		10	/* one nano at IIO_TEST_DEC_SCALE */
> 
> I would put them as (9 + 1) to show that actually show that "with one more digit".
> I also don't follow the comment near to the last definition. What does it mean?

With decimal scale of 10, a unit is 10^-10, 10 units is 10^-9 = NANO, which is
the scale used in iio_format_value().

-- 
Kind regards,

Rodrigo Alencar

^ permalink raw reply	[flat|nested] 12+ messages in thread

* Re: [PATCH v4 5/6] iio: core: use kstrtodec64() to parse fixed-point values
  2026-10-26 14:54   ` Andy Shevchenko
@ 2026-10-26 15:53     ` Rodrigo Alencar
  0 siblings, 0 replies; 12+ messages in thread
From: Rodrigo Alencar @ 2026-10-26 15:53 UTC (permalink / raw)
  To: Andy Shevchenko
  Cc: linux-kernel, linux-iio, Andrew Morton, Jonathan Cameron,
	David Lechner, Andy Shevchenko

On 26/10/26 16:54, Andy Shevchenko wrote:
> On Mon, Oct 26, 2026 at 08:54:09AM -0300, Rodrigo Alencar via B4 Relay wrote:
> 
> > Replace the open-coded __iio_str_to_fixpoint() parser with kstrtodec64(),
> > so sysfs writes of fixed-point values also accept E notation.
> > 
> > The fractional part only keeps the sign when the integer part is zero,
> > as before. The dB suffix for scale attributes is now handled by a separate
> > __iio_str_to_fixpoint_units() helper. It strips an optional trailing
> > newline and the "dB" or " dB" suffix before parsing.
> > 
> > When there is no fractional part (fract_mult == 0, or dec_scale == 0 for
> > 64-bit values), try base-autodetecting kstrtoll() first so hexadecimal
> > and octal input keeps its meaning ("010" is still 8). Fall back to
> > kstrtodec64() on -EINVAL to accept E notation.
> 
> > As a side effect, inputs that used to be rejected for integer-only
> > attributes are now accepted. An invalid octal number such as "08" is
> > parsed as decimal 8, and a fractional value such as "1.5" is rounded
> > to 2.
> 
> The main question here is: Do we want these side effects?
> And follow up question is: Where is it going to be documented?
> I consider this paragraph is very important for the users.

I see that those side effects turn the sysfs interface more robust to user input,
not an interface change, but indeed a behavior change on edge cases. To answer
the first question, yes, I would want those...

Userspace can do floating point math on physical values and scales and write the
result to raw integer attributes as is. Rounding to nearest acceptable values is
something that drivers often do themselves.

I am not really concerned about the invalid octal case input.

Also, no valid input being used so far is being invalidated with this.

As for the follow up question, I can add some details to iio_str_to_fixpoint() doc
header, which is the exported symbol here.

> 
> ...
> 
> > +	/*
> > +	 * Keep hex/octal support for integers. E notation and decimal values
> > +	 * result in -EINVAL from kstrtoll, due to unexpected termination on '.'
> 
> kstrtoll()
> 
> > +	 * or 'e'. It falls back to kstrtodec64 for decimal fixed-point numbers
> > +	 * with zero scale.
> > +	 */
> > +	ret = kstrtoll(str, 0, val);
> > +	if (ret == -EINVAL)
> 
> > +		ret = kstrtodec64(str, 0, val);
> 
> 		return kstrtodec64(str, 0, val);
> 
> ?
> 
> > +	return ret;
> 
> ...
> 
> >  int iio_str_to_fixpoint(const char *str, int fract_mult,
> >  			int *integer, int *fract)
> >  {
> > -	return __iio_str_to_fixpoint(str, fract_mult, integer, fract, false);
> > +	s64 dec64;
> > +	int ret;
> > +
> > +	if (fract_mult) {
> > +		/* scale = ffs(fract_mult) = ffs(10^n) = ffs(2^n * 5^n) = n + 1 */
> > +		ret = kstrtodec64(str, ffs(fract_mult), &dec64);
> > +		if (ret)
> > +			return ret;
> > +
> > +		dec64 = div_s64_rem(dec64, fract_mult * 10, fract);
> > +		/* only carry the sign in fract if the integer is zero */
> > +		if (dec64)
> > +			*fract = abs(*fract);
> 
> Should we actually switch to UB-free abs() implementation?

abs(fract) here is always less than 10^9 (largest power of 10 that fits 32-bits),
I'd say that this is not a concern. 

> 
> > +	} else {
> > +		ret = iio_str_to_s64(str, &dec64);
> > +		if (ret)
> > +			return ret;
> > +
> > +		*fract = 0;
> > +	}
> > +
> > +	if (dec64 > INT_MAX || dec64 < INT_MIN)
> > +		return -ERANGE;
> > +
> > +	*integer = dec64;
> > +
> > +	return 0;
> >  }

...

-- 
Kind regards,

Rodrigo Alencar

^ permalink raw reply	[flat|nested] 12+ messages in thread

* Re: [PATCH v4 1/6] lib: kstrtox: support E notation in kstrtoudec64() and kstrtodec64()
  2026-10-26 11:54 ` [PATCH v4 1/6] lib: kstrtox: support E notation in kstrtoudec64() and kstrtodec64() Rodrigo Alencar via B4 Relay
@ 2026-10-27 10:23   ` Rodrigo Alencar
  0 siblings, 0 replies; 12+ messages in thread
From: Rodrigo Alencar @ 2026-10-27 10:23 UTC (permalink / raw)
  To: linux-kernel, linux-iio
  Cc: Andrew Morton, Jonathan Cameron, David Lechner, Andy Shevchenko

On 26/10/26 08:54, Rodrigo Alencar via B4 Relay wrote:
> From: Rodrigo Alencar <rodrigo.alencar@analog.com>
> 
> Allow decimal strings to carry an exponent suffix, such as "2.7e10" or
> "271828e-5", in kstrtoudec64() and kstrtodec64(). The exponent is added
> to the requested scale to work out how many integer and fractional digits
> are consumed. A negative effective exponent drops integer digits.
> 
> The digits are now scanned first and the string termination is checked
> before any conversion. A missing or malformed exponent ("1e", "1e+")
> returns -EINVAL. An exponent that overflows when added to the scale, or
> that makes the result exceed 64 bits, returns -ERANGE.

...

>  static int _kstrtoudec64(const char *s, unsigned int scale, u64 *res)
>  {
> -	unsigned int rv_int, rv_frac;
> +	const char *s_int, *s_frac;
> +	int len_int, len_frac;
> +	unsigned int rv;
>  	u64 _res = 0;
> +	int exp = 0;
> +	int ret;
>  
> -	rv_int = _parse_integer(s, 10, &_res);
> -	if (rv_int & KSTRTOX_OVERFLOW)
> -		return -ERANGE;
> -	s += rv_int;
> +	s_int = s;
> +	len_int = skip_digits(&s);
>  
>  	if (*s == '.')
>  		s++; /* skip decimal point */
>  
> -	rv_frac = _parse_integer(s, 10, &_res, scale, _res);

previous implementation required scale >= 0.

> -	if (rv_frac & KSTRTOX_OVERFLOW)
> -		return -ERANGE;
> -	s += rv_frac;
> +	s_frac = s;
> +	len_frac = skip_digits(&s);
>  
> -	/*
> -	 * Check input beyond rv_int and rv_frac to cover cases like ".5" with
> -	 * scale 0, which is considered a valid input, being parsed as 0.
> -	 */
> -	if (!rv_int && !rv_frac && !isdigit(*s))
> +	if (!len_int && !len_frac)
>  		return -EINVAL;
>  
> -	while (isdigit(*s)) /* truncate digits */
> +	if (_tolower(*s) == 'e') { /* E Notation */
>  		s++;
> +		ret = kstrtoint(s, 10, &exp);
> +		if (ret)
> +			return ret;
> +	} else { /* verify termination */
> +		if (*s == '\n')
> +			s++;
> +		if (*s)
> +			return -EINVAL;
> +	}
>  
> -	if (*s == '\n')
> -		s++;
> -	if (*s)
> -		return -EINVAL;
> +	if (check_add_overflow(exp, (int)scale, &exp))

I was wondering, and I figured that the 'scale' parameter can be made
as a signed int. This E notation implementation allows for that as
the scale now folds into the exponent value. A negative scale would 
make sense to allow the representation of values greater than 2^64,
still limitted to the 64-bit precision, i.e., 10^24 would fit 64-bit with
a scale of -6, which would have least significant digits ignored. The code
is ready as is, that would be an small change on a interface internal to
the kernel that wont change consumers code. More test cases would be added.

Although the absolute value of scale needs to be reasonable, the int casting
here is also not ok. big unsigned integers here might lead to negative values
without warning.

> +		return -ERANGE;
> +
> +	if (exp < 0) {
> +		len_int = max(len_int + exp, 0);
> +		exp = 0;
> +	}
> +
> +	rv = _parse_integer(s_int, 10, &_res, len_int);
> +	if (rv & KSTRTOX_OVERFLOW)
> +		return -ERANGE;
> +
> +	rv = _parse_integer(s_frac, 10, &_res, exp, _res);
> +	if (rv & KSTRTOX_OVERFLOW)
> +		return -ERANGE;
>  
> -	if (_res && ((scale - rv_frac) > 19 /* log10(2^64) = 19.26 */ ||
> -		     check_mul_overflow(_res, int_pow(10, scale - rv_frac), &_res)))
> +	exp -= rv;
> +	if (_res && (exp > 19 /* log10(2^64) = 19.26 */ ||
> +		     check_mul_overflow(_res, int_pow(10, exp), &_res)))
>  		return -ERANGE;
>  
>  	*res = _res;

-- 
Kind regards,

Rodrigo Alencar

^ permalink raw reply	[flat|nested] 12+ messages in thread

end of thread, other threads:[~2026-10-27 10:23 UTC | newest]

Thread overview: 12+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2026-10-26 11:54 [PATCH v4 0/6] Add E notation support to kstrtodec64() and IIO sysfs writes Rodrigo Alencar via B4 Relay
2026-10-26 11:54 ` [PATCH v4 1/6] lib: kstrtox: support E notation in kstrtoudec64() and kstrtodec64() Rodrigo Alencar via B4 Relay
2026-10-27 10:23   ` Rodrigo Alencar
2026-10-26 11:54 ` [PATCH v4 2/6] lib: kstrtox: round to nearest, ties to even in kstrto[u]dec64() Rodrigo Alencar via B4 Relay
2026-10-26 11:54 ` [PATCH v4 3/6] lib: test-kstrtox: add E notation tests for kstrto[u]dec64() Rodrigo Alencar via B4 Relay
2026-10-26 11:54 ` [PATCH v4 4/6] iio: test: rescale: compare scales against the precision of the output Rodrigo Alencar via B4 Relay
2026-10-26 14:40   ` Andy Shevchenko
2026-10-26 15:29     ` Rodrigo Alencar
2026-10-26 11:54 ` [PATCH v4 5/6] iio: core: use kstrtodec64() to parse fixed-point values Rodrigo Alencar via B4 Relay
2026-10-26 14:54   ` Andy Shevchenko
2026-10-26 15:53     ` Rodrigo Alencar
2026-10-26 11:54 ` [PATCH v4 6/6] iio: test: add kunit tests for channel info writes Rodrigo Alencar via B4 Relay

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox

all inboxes | Powered by JetHome®