* [PATCH RFC v2 0/4] Add E notation support to kstrtodec64() and IIO sysfs writes
@ 2026-10-13 13:36 ` Rodrigo Alencar
0 siblings, 0 replies; 12+ messages in thread
From: Rodrigo Alencar via B4 Relay @ 2026-10-13 13:36 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. Digits beyond the resulting precision are
truncated, matching the existing kstrtodec64() behaviour. Overflow is
reported as -ERANGE and a malformed exponent ("1e", "1e+") as -EINVAL.
Signed-off-by: Rodrigo Alencar <rodrigo.alencar@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 (4):
lib: kstrtox: support E notation in kstrtoudec64() and kstrtodec64()
lib: test-kstrtox: add E notation tests for kstrto[u]dec64()
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 | 168 +++++++-------
drivers/iio/test/Kconfig | 14 ++
drivers/iio/test/Makefile | 1 +
drivers/iio/test/iio-test-channel-write.c | 352 ++++++++++++++++++++++++++++++
lib/kstrtox.c | 64 ++++--
lib/test-kstrtox.c | 58 +++++
8 files changed, 557 insertions(+), 104 deletions(-)
---
base-commit: a22dc7ebb21a6bd4c8b63bc2c1dd46a24945aae7
change-id: 20261001-e-notation-0f07757910e5
Best regards,
--
Rodrigo Alencar <rodrigo.alencar@analog.com>
^ permalink raw reply [flat|nested] 12+ messages in thread* [PATCH RFC v2 0/4] Add E notation support to kstrtodec64() and IIO sysfs writes @ 2026-10-13 13:36 ` Rodrigo Alencar 0 siblings, 0 replies; 12+ messages in thread From: Rodrigo Alencar @ 2026-10-13 13:36 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. Digits beyond the resulting precision are truncated, matching the existing kstrtodec64() behaviour. Overflow is reported as -ERANGE and a malformed exponent ("1e", "1e+") as -EINVAL. Signed-off-by: Rodrigo Alencar <rodrigo.alencar@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 (4): lib: kstrtox: support E notation in kstrtoudec64() and kstrtodec64() lib: test-kstrtox: add E notation tests for kstrto[u]dec64() 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 | 168 +++++++------- drivers/iio/test/Kconfig | 14 ++ drivers/iio/test/Makefile | 1 + drivers/iio/test/iio-test-channel-write.c | 352 ++++++++++++++++++++++++++++++ lib/kstrtox.c | 64 ++++-- lib/test-kstrtox.c | 58 +++++ 8 files changed, 557 insertions(+), 104 deletions(-) --- base-commit: a22dc7ebb21a6bd4c8b63bc2c1dd46a24945aae7 change-id: 20261001-e-notation-0f07757910e5 Best regards, -- Rodrigo Alencar <rodrigo.alencar@analog.com> ^ permalink raw reply [flat|nested] 12+ messages in thread
* [PATCH RFC v2 1/4] lib: kstrtox: support E notation in kstrtoudec64() and kstrtodec64() 2026-10-13 13:36 ` Rodrigo Alencar @ 2026-10-13 13:36 ` Rodrigo Alencar -1 siblings, 0 replies; 12+ messages in thread From: Rodrigo Alencar via B4 Relay @ 2026-10-13 13:36 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. Digits beyond the resulting precision are truncated, as before. 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 | 64 +++++++++++++++++++++++++++++++++++++++-------------------- 1 file changed, 42 insertions(+), 22 deletions(-) diff --git a/lib/kstrtox.c b/lib/kstrtox.c index bac1c057e1b0..4755fc78a62c 100644 --- a/lib/kstrtox.c +++ b/lib/kstrtox.c @@ -397,39 +397,59 @@ EXPORT_SYMBOL(kstrtobool); static int _kstrtoudec64(const char *s, unsigned int scale, u64 *res) { - unsigned int rv_int, rv_frac; + const char *s_int, *s_frac; + int exp = 0, len_int = 0; + unsigned int rv; u64 _res = 0; + int ret; - rv_int = _parse_integer(s, 10, &_res); - if (rv_int & KSTRTOX_OVERFLOW) - return -ERANGE; - s += rv_int; + s_int = s; + while (isdigit(*s)) { + s++; + len_int++; + } if (*s == '.') s++; /* skip decimal point */ - rv_frac = _parse_integer(s, 10, &_res, scale, _res); - if (rv_frac & KSTRTOX_OVERFLOW) + s_frac = s; + while (isdigit(*s)) + s++; + + if (!len_int && s_frac == s) + return -EINVAL; + + if (*s == 'e' || *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 (check_add_overflow(exp, (int)scale, &exp)) return -ERANGE; - s += rv_frac; - /* - * 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)) - return -EINVAL; + if (exp < 0) { + len_int = max(len_int + exp, 0); + exp = 0; + } - while (isdigit(*s)) /* truncate digits */ - s++; + rv = _parse_integer(s_int, 10, &_res, len_int); + if (rv & KSTRTOX_OVERFLOW) + return -ERANGE; - if (*s == '\n') - s++; - if (*s) - return -EINVAL; + 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 related [flat|nested] 12+ messages in thread
* [PATCH RFC v2 1/4] lib: kstrtox: support E notation in kstrtoudec64() and kstrtodec64() @ 2026-10-13 13:36 ` Rodrigo Alencar 0 siblings, 0 replies; 12+ messages in thread From: Rodrigo Alencar @ 2026-10-13 13:36 UTC (permalink / raw) To: linux-kernel, linux-iio Cc: Andrew Morton, Jonathan Cameron, David Lechner, Andy Shevchenko, Rodrigo Alencar 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. Digits beyond the resulting precision are truncated, as before. 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 | 64 +++++++++++++++++++++++++++++++++++++++-------------------- 1 file changed, 42 insertions(+), 22 deletions(-) diff --git a/lib/kstrtox.c b/lib/kstrtox.c index bac1c057e1b0..4755fc78a62c 100644 --- a/lib/kstrtox.c +++ b/lib/kstrtox.c @@ -397,39 +397,59 @@ EXPORT_SYMBOL(kstrtobool); static int _kstrtoudec64(const char *s, unsigned int scale, u64 *res) { - unsigned int rv_int, rv_frac; + const char *s_int, *s_frac; + int exp = 0, len_int = 0; + unsigned int rv; u64 _res = 0; + int ret; - rv_int = _parse_integer(s, 10, &_res); - if (rv_int & KSTRTOX_OVERFLOW) - return -ERANGE; - s += rv_int; + s_int = s; + while (isdigit(*s)) { + s++; + len_int++; + } if (*s == '.') s++; /* skip decimal point */ - rv_frac = _parse_integer(s, 10, &_res, scale, _res); - if (rv_frac & KSTRTOX_OVERFLOW) + s_frac = s; + while (isdigit(*s)) + s++; + + if (!len_int && s_frac == s) + return -EINVAL; + + if (*s == 'e' || *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 (check_add_overflow(exp, (int)scale, &exp)) return -ERANGE; - s += rv_frac; - /* - * 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)) - return -EINVAL; + if (exp < 0) { + len_int = max(len_int + exp, 0); + exp = 0; + } - while (isdigit(*s)) /* truncate digits */ - s++; + rv = _parse_integer(s_int, 10, &_res, len_int); + if (rv & KSTRTOX_OVERFLOW) + return -ERANGE; - if (*s == '\n') - s++; - if (*s) - return -EINVAL; + 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 related [flat|nested] 12+ messages in thread
* Re: [PATCH RFC v2 1/4] lib: kstrtox: support E notation in kstrtoudec64() and kstrtodec64() 2026-10-13 13:36 ` Rodrigo Alencar (?) @ 2026-10-13 18:48 ` Andy Shevchenko -1 siblings, 0 replies; 12+ messages in thread From: Andy Shevchenko @ 2026-10-13 18:48 UTC (permalink / raw) To: rodrigo.alencar Cc: linux-kernel, linux-iio, Andrew Morton, Jonathan Cameron, David Lechner, Andy Shevchenko On Tue, Oct 13, 2026 at 10:36:44AM -0300, Rodrigo Alencar via B4 Relay wrote: > 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. Digits > beyond the resulting precision are truncated, as before. > > 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 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 exp = 0, len_int = 0; int len_int, len_frac; int exp = 0; > + unsigned int rv; > u64 _res = 0; > + int ret; > > - rv_int = _parse_integer(s, 10, &_res); > - if (rv_int & KSTRTOX_OVERFLOW) > - return -ERANGE; > - s += rv_int; > + s_int = s; > + while (isdigit(*s)) { > + s++; > + len_int++; > + } 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) > + s_frac = s; > + while (isdigit(*s)) > + s++; len_frac = skip_digits(&s); > + if (!len_int && s_frac == s) if (!len_int && !len_frac) > + return -EINVAL; > + > + if (*s == 'e' || *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 (check_add_overflow(exp, (int)scale, &exp)) > return -ERANGE; > - s += rv_frac; > > - /* > - * 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)) > - return -EINVAL; > + if (exp < 0) { > + len_int = max(len_int + exp, 0); > + exp = 0; > + } > > - while (isdigit(*s)) /* truncate digits */ > - s++; > + rv = _parse_integer(s_int, 10, &_res, len_int); > + if (rv & KSTRTOX_OVERFLOW) > + return -ERANGE; > > - if (*s == '\n') > - s++; > - if (*s) > - return -EINVAL; > + 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; -- With Best Regards, Andy Shevchenko ^ permalink raw reply [flat|nested] 12+ messages in thread
* [PATCH RFC v2 2/4] lib: test-kstrtox: add E notation tests for kstrto[u]dec64() 2026-10-13 13:36 ` Rodrigo Alencar @ 2026-10-13 13:36 ` Rodrigo Alencar -1 siblings, 0 replies; 12+ messages in thread From: Rodrigo Alencar via B4 Relay @ 2026-10-13 13:36 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, truncation of integer digits 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 | 58 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 58 insertions(+) diff --git a/lib/test-kstrtox.c b/lib/test-kstrtox.c index 9255234752db..02f42a12647e 100644 --- a/lib/test-kstrtox.c +++ b/lib/test-kstrtox.c @@ -769,6 +769,28 @@ static void __init test_kstrtoudec64_ok(void) /* truncation with many excess digits */ {"0.00000000000000000000000000000000423", 34, 42}, {"1.99999999999999999999999999999999999", 3, 1999}, + /* 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, 271ULL}, + {"270e-2", 3, 2700ULL}, + /* E notation: negative exponent that truncates integer digits */ + {"27e-4", 3, 2ULL}, + /* E notation: negative exponent below 1 ULP truncates to 0 */ + {"1e-5", 3, 0ULL}, + /* E notation: large value (27 GHz in micro Hz) */ + {"2.7e10", 6, 27000000000000000ULL}, }; TEST_OK(kstrtoudec64, u64, "%llu", test_udec64_ok); } @@ -801,6 +823,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); } @@ -853,6 +886,21 @@ 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 truncates to 0 */ + {"1e-5", 3, 0LL}, + {"-1e-5", 3, 0LL}, }; TEST_OK(kstrtodec64, s64, "%lld", test_dec64_ok); } @@ -881,6 +929,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 related [flat|nested] 12+ messages in thread
* [PATCH RFC v2 2/4] lib: test-kstrtox: add E notation tests for kstrto[u]dec64() @ 2026-10-13 13:36 ` Rodrigo Alencar 0 siblings, 0 replies; 12+ messages in thread From: Rodrigo Alencar @ 2026-10-13 13:36 UTC (permalink / raw) To: linux-kernel, linux-iio Cc: Andrew Morton, Jonathan Cameron, David Lechner, Andy Shevchenko, Rodrigo Alencar Cover E notation parsing in kstrtoudec64() and kstrtodec64(): positive, zero and negative exponents, upper case 'E', an explicit '+' sign, truncation of integer digits 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 | 58 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 58 insertions(+) diff --git a/lib/test-kstrtox.c b/lib/test-kstrtox.c index 9255234752db..02f42a12647e 100644 --- a/lib/test-kstrtox.c +++ b/lib/test-kstrtox.c @@ -769,6 +769,28 @@ static void __init test_kstrtoudec64_ok(void) /* truncation with many excess digits */ {"0.00000000000000000000000000000000423", 34, 42}, {"1.99999999999999999999999999999999999", 3, 1999}, + /* 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, 271ULL}, + {"270e-2", 3, 2700ULL}, + /* E notation: negative exponent that truncates integer digits */ + {"27e-4", 3, 2ULL}, + /* E notation: negative exponent below 1 ULP truncates to 0 */ + {"1e-5", 3, 0ULL}, + /* E notation: large value (27 GHz in micro Hz) */ + {"2.7e10", 6, 27000000000000000ULL}, }; TEST_OK(kstrtoudec64, u64, "%llu", test_udec64_ok); } @@ -801,6 +823,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); } @@ -853,6 +886,21 @@ 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 truncates to 0 */ + {"1e-5", 3, 0LL}, + {"-1e-5", 3, 0LL}, }; TEST_OK(kstrtodec64, s64, "%lld", test_dec64_ok); } @@ -881,6 +929,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 related [flat|nested] 12+ messages in thread
* Re: [PATCH RFC v2 2/4] lib: test-kstrtox: add E notation tests for kstrto[u]dec64() 2026-10-13 13:36 ` Rodrigo Alencar (?) @ 2026-10-13 18:49 ` Andy Shevchenko -1 siblings, 0 replies; 12+ messages in thread From: Andy Shevchenko @ 2026-10-13 18:49 UTC (permalink / raw) To: rodrigo.alencar Cc: linux-kernel, linux-iio, Andrew Morton, Jonathan Cameron, David Lechner, Andy Shevchenko On Tue, Oct 13, 2026 at 10:36:45AM -0300, Rodrigo Alencar via B4 Relay wrote: > Cover E notation parsing in kstrtoudec64() and kstrtodec64(): positive, > zero and negative exponents, upper case 'E', an explicit '+' sign, > truncation of integer digits by negative exponents, large values, and > failures for overflowing and invalid inputs. Reviewed-by: Andy Shevchenko <andriy.shevchenko@intel.com> Always a green light for the test cases. -- With Best Regards, Andy Shevchenko ^ permalink raw reply [flat|nested] 12+ messages in thread
* [PATCH RFC v2 3/4] iio: core: use kstrtodec64() to parse fixed-point values 2026-10-13 13:36 ` Rodrigo Alencar @ 2026-10-13 13:36 ` Rodrigo Alencar -1 siblings, 0 replies; 12+ messages in thread From: Rodrigo Alencar via B4 Relay @ 2026-10-13 13:36 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 truncated to 1, matching how kstrtodec64() drops digits beyond the requested scale. Signed-off-by: Rodrigo Alencar <rodrigo.alencar@analog.com> --- drivers/iio/industrialio-core.c | 159 ++++++++++++++++++++-------------------- 1 file changed, 81 insertions(+), 78 deletions(-) diff --git a/drivers/iio/industrialio-core.c b/drivers/iio/industrialio-core.c index d73f2541375f..616bfea711e0 100644 --- a/drivers/iio/industrialio-core.c +++ b/drivers/iio/industrialio-core.c @@ -994,77 +994,6 @@ 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) -{ - 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; -} - /** * iio_str_to_fixpoint() - Parse a fixed-point number from a string * @str: The string to parse @@ -1075,13 +1004,79 @@ static int __iio_str_to_fixpoint(const char *str, int fract_mult, * Returns: * 0 on success, or a negative error code if the string could not be parsed. */ -int iio_str_to_fixpoint(const char *str, int fract_mult, - int *integer, int *fract) +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); + /* fract_mult = 10^n, so ffs(10^n) = ffs(2^n * 5^n) = n + 1 */ + unsigned int scale = ffs(fract_mult); + s64 dec64; + int ret; + + if (fract_mult) { + ret = kstrtodec64(str, scale, &dec64); + } else { + /* keep hex/octal support for integers */ + ret = kstrtoll(str, 0, &dec64); + if (ret == -EINVAL) + ret = kstrtodec64(str, scale, &dec64); + } + if (ret) + return ret; + + if (fract_mult > 0) + dec64 = div_s64_rem(dec64, fract_mult * 10, fract); + else + *fract = 0; + + if (dec64 > INT_MAX || dec64 < INT_MIN) + return -ERANGE; + + *integer = (int)dec64; + /* only carry the sign in the fractional part if the integer is zero */ + if (*integer) + *fract = abs(*fract); + + 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 + * + * 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 num_len = strlen(str); + const char *dec_str = str; + char buf[64]; + + if (num_len && str[num_len - 1] == '\n') + num_len--; + + if (num_len > units_len && + !strncmp(str + num_len - units_len, units, units_len)) { + num_len -= units_len; + if (str[num_len - 1] == ' ') + num_len--; + if (num_len >= sizeof(buf)) + return -EINVAL; + memcpy(buf, str, num_len); + buf[num_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, @@ -1143,17 +1138,25 @@ static ssize_t iio_write_channel_info(struct device *dev, return -EINVAL; integer = ch; } else if (is_64bit) { - if (dec_scale) + if (dec_scale) { ret = kstrtodec64(buf, dec_scale, &integer64); - else + } else { + /* keep hex/octal support for integers */ ret = kstrtoll(buf, 0, &integer64); + if (ret == -EINVAL) + ret = kstrtodec64(buf, dec_scale, &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 related [flat|nested] 12+ messages in thread
* [PATCH RFC v2 3/4] iio: core: use kstrtodec64() to parse fixed-point values @ 2026-10-13 13:36 ` Rodrigo Alencar 0 siblings, 0 replies; 12+ messages in thread From: Rodrigo Alencar @ 2026-10-13 13:36 UTC (permalink / raw) To: linux-kernel, linux-iio Cc: Andrew Morton, Jonathan Cameron, David Lechner, Andy Shevchenko, Rodrigo Alencar 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 truncated to 1, matching how kstrtodec64() drops digits beyond the requested scale. Signed-off-by: Rodrigo Alencar <rodrigo.alencar@analog.com> --- drivers/iio/industrialio-core.c | 159 ++++++++++++++++++++-------------------- 1 file changed, 81 insertions(+), 78 deletions(-) diff --git a/drivers/iio/industrialio-core.c b/drivers/iio/industrialio-core.c index d73f2541375f..616bfea711e0 100644 --- a/drivers/iio/industrialio-core.c +++ b/drivers/iio/industrialio-core.c @@ -994,77 +994,6 @@ 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) -{ - 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; -} - /** * iio_str_to_fixpoint() - Parse a fixed-point number from a string * @str: The string to parse @@ -1075,13 +1004,79 @@ static int __iio_str_to_fixpoint(const char *str, int fract_mult, * Returns: * 0 on success, or a negative error code if the string could not be parsed. */ -int iio_str_to_fixpoint(const char *str, int fract_mult, - int *integer, int *fract) +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); + /* fract_mult = 10^n, so ffs(10^n) = ffs(2^n * 5^n) = n + 1 */ + unsigned int scale = ffs(fract_mult); + s64 dec64; + int ret; + + if (fract_mult) { + ret = kstrtodec64(str, scale, &dec64); + } else { + /* keep hex/octal support for integers */ + ret = kstrtoll(str, 0, &dec64); + if (ret == -EINVAL) + ret = kstrtodec64(str, scale, &dec64); + } + if (ret) + return ret; + + if (fract_mult > 0) + dec64 = div_s64_rem(dec64, fract_mult * 10, fract); + else + *fract = 0; + + if (dec64 > INT_MAX || dec64 < INT_MIN) + return -ERANGE; + + *integer = (int)dec64; + /* only carry the sign in the fractional part if the integer is zero */ + if (*integer) + *fract = abs(*fract); + + 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 + * + * 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 num_len = strlen(str); + const char *dec_str = str; + char buf[64]; + + if (num_len && str[num_len - 1] == '\n') + num_len--; + + if (num_len > units_len && + !strncmp(str + num_len - units_len, units, units_len)) { + num_len -= units_len; + if (str[num_len - 1] == ' ') + num_len--; + if (num_len >= sizeof(buf)) + return -EINVAL; + memcpy(buf, str, num_len); + buf[num_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, @@ -1143,17 +1138,25 @@ static ssize_t iio_write_channel_info(struct device *dev, return -EINVAL; integer = ch; } else if (is_64bit) { - if (dec_scale) + if (dec_scale) { ret = kstrtodec64(buf, dec_scale, &integer64); - else + } else { + /* keep hex/octal support for integers */ ret = kstrtoll(buf, 0, &integer64); + if (ret == -EINVAL) + ret = kstrtodec64(buf, dec_scale, &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 related [flat|nested] 12+ messages in thread
* [PATCH RFC v2 4/4] iio: test: add kunit tests for channel info writes 2026-10-13 13:36 ` Rodrigo Alencar @ 2026-10-13 13:36 ` Rodrigo Alencar -1 siblings, 0 replies; 12+ messages in thread From: Rodrigo Alencar via B4 Relay @ 2026-10-13 13:36 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 truncation and E notation, and 32-bit range checks. - IIO_VAL_INT_PLUS_MICRO / IIO_VAL_INT_PLUS_NANO: sign handling, truncation of extra digits, 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 | 352 ++++++++++++++++++++++++++++++ 6 files changed, 376 insertions(+), 4 deletions(-) diff --git a/MAINTAINERS b/MAINTAINERS index 3a2dfd1a4fd8..5ef5caf3b88f 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -12757,6 +12757,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 616bfea711e0..7e41668591f6 100644 --- a/drivers/iio/industrialio-core.c +++ b/drivers/iio/industrialio-core.c @@ -1077,10 +1077,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); @@ -1168,6 +1168,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..b150fa3cb850 --- /dev/null +++ b/drivers/iio/test/iio-test-channel-write.c @@ -0,0 +1,352 @@ +// 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 truncated */ + EXPECT_WRITE(test, IIO_VAL_INT, "1.9", 1, 0); + EXPECT_WRITE(test, IIO_VAL_INT, "-1.9", -1, 0); + EXPECT_WRITE(test, IIO_VAL_INT, "1e3", 1000, 0); + EXPECT_WRITE(test, IIO_VAL_INT, "15e-1", 1, 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 truncated */ + EXPECT_WRITE(test, IIO_VAL_INT_PLUS_MICRO, "1.2345678", 1, 234567); + EXPECT_WRITE(test, IIO_VAL_INT_PLUS_MICRO, "0.0000009", 0, 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, 1); + 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, "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", 1); + 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", 0); + + 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, 234567); + 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 related [flat|nested] 12+ messages in thread
* [PATCH RFC v2 4/4] iio: test: add kunit tests for channel info writes @ 2026-10-13 13:36 ` Rodrigo Alencar 0 siblings, 0 replies; 12+ messages in thread From: Rodrigo Alencar @ 2026-10-13 13:36 UTC (permalink / raw) To: linux-kernel, linux-iio Cc: Andrew Morton, Jonathan Cameron, David Lechner, Andy Shevchenko, Rodrigo Alencar 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 truncation and E notation, and 32-bit range checks. - IIO_VAL_INT_PLUS_MICRO / IIO_VAL_INT_PLUS_NANO: sign handling, truncation of extra digits, 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 | 352 ++++++++++++++++++++++++++++++ 6 files changed, 376 insertions(+), 4 deletions(-) diff --git a/MAINTAINERS b/MAINTAINERS index 3a2dfd1a4fd8..5ef5caf3b88f 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -12757,6 +12757,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 616bfea711e0..7e41668591f6 100644 --- a/drivers/iio/industrialio-core.c +++ b/drivers/iio/industrialio-core.c @@ -1077,10 +1077,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); @@ -1168,6 +1168,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..b150fa3cb850 --- /dev/null +++ b/drivers/iio/test/iio-test-channel-write.c @@ -0,0 +1,352 @@ +// 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 truncated */ + EXPECT_WRITE(test, IIO_VAL_INT, "1.9", 1, 0); + EXPECT_WRITE(test, IIO_VAL_INT, "-1.9", -1, 0); + EXPECT_WRITE(test, IIO_VAL_INT, "1e3", 1000, 0); + EXPECT_WRITE(test, IIO_VAL_INT, "15e-1", 1, 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 truncated */ + EXPECT_WRITE(test, IIO_VAL_INT_PLUS_MICRO, "1.2345678", 1, 234567); + EXPECT_WRITE(test, IIO_VAL_INT_PLUS_MICRO, "0.0000009", 0, 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, 1); + 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, "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", 1); + 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", 0); + + 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, 234567); + 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 related [flat|nested] 12+ messages in thread
end of thread, other threads:[~2026-10-13 18:49 UTC | newest] Thread overview: 12+ messages (download: mbox.gz follow: Atom feed -- links below jump to the message on this page -- 2026-10-13 13:36 [PATCH RFC v2 0/4] Add E notation support to kstrtodec64() and IIO sysfs writes Rodrigo Alencar via B4 Relay 2026-10-13 13:36 ` Rodrigo Alencar 2026-10-13 13:36 ` [PATCH RFC v2 1/4] lib: kstrtox: support E notation in kstrtoudec64() and kstrtodec64() Rodrigo Alencar via B4 Relay 2026-10-13 13:36 ` Rodrigo Alencar 2026-10-13 18:48 ` Andy Shevchenko 2026-10-13 13:36 ` [PATCH RFC v2 2/4] lib: test-kstrtox: add E notation tests for kstrto[u]dec64() Rodrigo Alencar via B4 Relay 2026-10-13 13:36 ` Rodrigo Alencar 2026-10-13 18:49 ` Andy Shevchenko 2026-10-13 13:36 ` [PATCH RFC v2 3/4] iio: core: use kstrtodec64() to parse fixed-point values Rodrigo Alencar via B4 Relay 2026-10-13 13:36 ` Rodrigo Alencar 2026-10-13 13:36 ` [PATCH RFC v2 4/4] iio: test: add kunit tests for channel info writes Rodrigo Alencar via B4 Relay 2026-10-13 13:36 ` Rodrigo Alencar
This is an external index of several public inboxes, see mirroring instructions on how to clone and mirror all data and code used by this external index.