* [PATCH v2 1/2] util_macros.h: fix/rework find_closest() macros
@ 2024-11-05 14:54 Alexandru Ardelean
2024-11-05 14:54 ` [PATCH v2 2/2] lib: util_macros_kunit: add kunit test for util_macros.h Alexandru Ardelean
2024-11-05 23:08 ` [PATCH v2 1/2] util_macros.h: fix/rework find_closest() macros Andrew Morton
0 siblings, 2 replies; 5+ messages in thread
From: Alexandru Ardelean @ 2024-11-05 14:54 UTC (permalink / raw)
To: linux-iio, linux-kernel
Cc: jic23, bartosz.golaszewski, gregkh, akpm, Alexandru Ardelean
A bug was found in the find_closest() (find_closest_descending() is also
affected after some testing), where for certain values with small
progressions, the rounding (done by averaging 2 values) causes an incorrect
index to be returned.
The rounding issues occur for progressions of 1, 2 and 3. It goes away when
the progression/interval between two values is 4 or larger.
It's particularly bad for progressions of 1. For example if there's an
array of 'a = { 1, 2, 3 }', using 'find_closest(2, a ...)' would return 0
(the index of '1'), rather than returning 1 (the index of '2').
This means that for exact values (with a progression of 1), find_closest()
will misbehave and return the index of the value smaller than the one we're
searching for.
For progressions of 2 and 3, the exact values are obtained correctly; but
values aren't approximated correctly (as one would expect). Starting with
progressions of 4, all seems to be good (one gets what one would expect).
While one could argue that 'find_closest()' should not be used for arrays
with progressions of 1 (i.e. '{1, 2, 3, ...}', the macro should still
behave correctly.
The bug was found while testing the 'drivers/iio/adc/ad7606.c',
specifically the oversampling feature.
For reference, the oversampling values are listed as:
static const unsigned int ad7606_oversampling_avail[7] = {
1, 2, 4, 8, 16, 32, 64,
};
When doing:
1. $ echo 1 > /sys/bus/iio/devices/iio\:device0/oversampling_ratio
$ cat /sys/bus/iio/devices/iio\:device0/oversampling_ratio
1 # this is fine
2. $ echo 2 > /sys/bus/iio/devices/iio\:device0/oversampling_ratio
$ cat /sys/bus/iio/devices/iio\:device0/oversampling_ratio
1 # this is wrong; 2 should be returned here
3. $ echo 3 > /sys/bus/iio/devices/iio\:device0/oversampling_ratio
$ cat /sys/bus/iio/devices/iio\:device0/oversampling_ratio
2 # this is fine
4. $ echo 4 > /sys/bus/iio/devices/iio\:device0/oversampling_ratio
$ cat /sys/bus/iio/devices/iio\:device0/oversampling_ratio
4 # this is fine
And from here-on, the values are as correct (one gets what one would
expect.)
While writing a kunit test for this bug, a peculiar issue was found for the
array in the 'drivers/hwmon/ina2xx.c' & 'drivers/iio/adc/ina2xx-adc.c'
drivers. While running the kunit test (for 'ina226_avg_tab' from these
drivers):
* idx = find_closest([-1 to 2], ina226_avg_tab, ARRAY_SIZE(ina226_avg_tab));
This returns idx == 0, so value.
* idx = find_closest(3, ina226_avg_tab, ARRAY_SIZE(ina226_avg_tab));
This returns idx == 0, value 1; and now one could argue whether 3 is
closer to 4 or to 1. This quirk only appears for value '3' in this
array, but it seems to be a another rounding issue.
* And from 4 onwards the 'find_closest'() works fine (one gets what one
would expect).
This change reworks the find_closest() macros to also check the difference
between the left and right elements when 'x'. If the distance to the right
is smaller (than the distance to the left), the index is incremented by 1.
This also makes redundant the need for using the DIV_ROUND_CLOSEST() macro.
In order to accommodate for any mix of negative + positive values, the
internal variables '__fc_x', '__fc_mid_x', '__fc_left' & '__fc_right' are
forced to 'long' type. This also addresses any potential bugs/issues with
'x' being of an unsigned type. In those situations any comparison between
signed & unsigned would be promoted to a comparison between 2 unsigned
numbers; this is especially annoying when '__fc_left' & '__fc_right'
underflow.
The find_closest_descending() macro was also reworked and duplicated from
the find_closest(), and it is being iterated in reverse. The main reason
for this is to get the same indices as 'find_closest()' (but in reverse).
The comparison for '__fc_right < __fc_left' favors going the array in
ascending order.
For example for array '{ 1024, 512, 256, 128, 64, 16, 4, 1 }' and x = 3, we
get:
__fc_mid_x = 2
__fc_left = -1
__fc_right = -2
Then '__fc_right < __fc_left' evaluates to true and '__fc_i++' becomes 7
which is not quite incorrect, but 3 is closer to 4 than to 1.
This change has been validated with the kunit from the next patch.
Fixes: 95d119528b0b ("util_macros.h: add find_closest() macro")
Signed-off-by: Alexandru Ardelean <aardelean@baylibre.com>
---
Changelog v1 -> v2:
* https://lore.kernel.org/linux-iio/20241031063707.795842-1-aardelean@baylibre.com/
* split the __find_closest() macro into find_closest() & find_closest_descending()
* find_closest_descending() is iterating in reverse order
* this favors some corner cases with small values
* forcing types for '__fc_x', '__fc_mid_x', '__fc_left' && '__fc_right' to be long
* this resolves several potential issues with combining arrays of signed/unsigned
values with 'x' of type signed/unsigned
* fixed error with previous implementation where __fc_mid_x was used instead of __fc_x
when calculating '__fc_left' && '__fc_right'
* updated commit description with more information (also found on previous
thread) + the description for the changed implementation
include/linux/util_macros.h | 56 ++++++++++++++++++++++++++-----------
1 file changed, 40 insertions(+), 16 deletions(-)
diff --git a/include/linux/util_macros.h b/include/linux/util_macros.h
index 6bb460c3e818..825487fb66fa 100644
--- a/include/linux/util_macros.h
+++ b/include/linux/util_macros.h
@@ -4,19 +4,6 @@
#include <linux/math.h>
-#define __find_closest(x, a, as, op) \
-({ \
- typeof(as) __fc_i, __fc_as = (as) - 1; \
- typeof(x) __fc_x = (x); \
- typeof(*a) const *__fc_a = (a); \
- for (__fc_i = 0; __fc_i < __fc_as; __fc_i++) { \
- if (__fc_x op DIV_ROUND_CLOSEST(__fc_a[__fc_i] + \
- __fc_a[__fc_i + 1], 2)) \
- break; \
- } \
- (__fc_i); \
-})
-
/**
* find_closest - locate the closest element in a sorted array
* @x: The reference value.
@@ -25,8 +12,27 @@
* @as: Size of 'a'.
*
* Returns the index of the element closest to 'x'.
+ * Note: If using an array of negative numbers (or mixed positive numbers),
+ * then be sure that 'x' is of a signed-type to get good results.
*/
-#define find_closest(x, a, as) __find_closest(x, a, as, <=)
+#define find_closest(x, a, as) \
+({ \
+ typeof(as) __fc_i, __fc_as = (as) - 1; \
+ long __fc_mid_x, __fc_x = (x); \
+ long __fc_left, __fc_right; \
+ typeof(*a) const *__fc_a = (a); \
+ for (__fc_i = 0; __fc_i < __fc_as; __fc_i++) { \
+ __fc_mid_x = (__fc_a[__fc_i] + __fc_a[__fc_i + 1]) / 2; \
+ if (__fc_x <= __fc_mid_x) { \
+ __fc_left = __fc_x - __fc_a[__fc_i]; \
+ __fc_right = __fc_a[__fc_i + 1] - __fc_x; \
+ if (__fc_right < __fc_left) \
+ __fc_i++; \
+ break; \
+ } \
+ } \
+ (__fc_i); \
+})
/**
* find_closest_descending - locate the closest element in a sorted array
@@ -36,9 +42,27 @@
* @as: Size of 'a'.
*
* Similar to find_closest() but 'a' is expected to be sorted in descending
- * order.
+ * order. The iteration is done in reverse order, so that the comparison
+ * of '__fc_right' & '__fc_left' also works for unsigned numbers.
*/
-#define find_closest_descending(x, a, as) __find_closest(x, a, as, >=)
+#define find_closest_descending(x, a, as) \
+({ \
+ typeof(as) __fc_i, __fc_as = (as) - 1; \
+ long __fc_mid_x, __fc_x = (x); \
+ long __fc_left, __fc_right; \
+ typeof(*a) const *__fc_a = (a); \
+ for (__fc_i = __fc_as; __fc_i >= 1; __fc_i--) { \
+ __fc_mid_x = (__fc_a[__fc_i] + __fc_a[__fc_i - 1]) / 2; \
+ if (__fc_x <= __fc_mid_x) { \
+ __fc_left = __fc_x - __fc_a[__fc_i]; \
+ __fc_right = __fc_a[__fc_i - 1] - __fc_x; \
+ if (__fc_right < __fc_left) \
+ __fc_i--; \
+ break; \
+ } \
+ } \
+ (__fc_i); \
+})
/**
* is_insidevar - check if the @ptr points inside the @var memory range.
--
2.46.1
^ permalink raw reply related [flat|nested] 5+ messages in thread
* [PATCH v2 2/2] lib: util_macros_kunit: add kunit test for util_macros.h
2024-11-05 14:54 [PATCH v2 1/2] util_macros.h: fix/rework find_closest() macros Alexandru Ardelean
@ 2024-11-05 14:54 ` Alexandru Ardelean
2024-11-05 23:08 ` [PATCH v2 1/2] util_macros.h: fix/rework find_closest() macros Andrew Morton
1 sibling, 0 replies; 5+ messages in thread
From: Alexandru Ardelean @ 2024-11-05 14:54 UTC (permalink / raw)
To: linux-iio, linux-kernel
Cc: jic23, bartosz.golaszewski, gregkh, akpm, Alexandru Ardelean
A bug was found in the find_closest() (find_closest_descending() is also
affected after some testing), where for certain values with small
progressions of 1, 2 & 3, the rounding (done by averaging 2 values) causes
an incorrect index to be returned.
The bug is described in more detail in the commit which fixes the bug.
This commit adds a kunit test to validate that the fix works correctly.
This kunit test adds some of the arrays (from the driver-sphere) that seem
to produce issues with the 'find_closest()' macro. Specifically the one
from ad7606 driver (with which the bug was found) and from the ina2xx
drivers, which shows the quirk with 'find_closest()' with elements in a
array that have an interval of 3.
For the find_closest_descending() tests, the same arrays are used as for
the find_closest(), but in reverse; the idea is that
'find_closest_descending()' should return the sames indices as
'find_closest()' but in reverse.
For testing both macros, there are 4 special arrays created, one for
testing find_closest{_descending}() for arrays of progressions 1, 2, 3 and
4. The idea is to show that (for progressions of 1, 2 & 3) the fix works as
expected. When removing the fix, the issues should start to show up.
Then an extra array of negative and positive values is added. There are
currently no such arrays within drivers, but one could expect that these
macros behave correctly even for such arrays.
To run this kunit:
./tools/testing/kunit/kunit.py run "*util_macros*"
Signed-off-by: Alexandru Ardelean <aardelean@baylibre.com>
---
Changelog v1 -> v2:
* https://lore.kernel.org/linux-iio/20241031063707.795842-2-aardelean@baylibre.com/
* updated commit description with more info about this kunit
* added extra tests to show fix for arrays of progressions 1, 2, 3 and 4
(i.e. { 1, 2, 3, 4 }, { 1, 3, 5, 7 }, { 1, 4, 7, 10 } &
{ 1, 5, 9, 13 } )
- the arrays are also tested in reverse order
- the arrays also use 'int' & 'u32' types (for the array & and search
value) to see that the search works correctly).
* added test for array with mix of negative + positive numbers
lib/Kconfig.debug | 17 +++
lib/Makefile | 1 +
lib/util_macros_kunit.c | 240 ++++++++++++++++++++++++++++++++++++++++
3 files changed, 258 insertions(+)
create mode 100644 lib/util_macros_kunit.c
diff --git a/lib/Kconfig.debug b/lib/Kconfig.debug
index 7312ae7c3cc5..caf10cf2084c 100644
--- a/lib/Kconfig.debug
+++ b/lib/Kconfig.debug
@@ -2629,6 +2629,23 @@ config CHECKSUM_KUNIT
If unsure, say N.
+config UTIL_MACROS_KUNIT
+ tristate "KUnit test util_macros.h functions at runtime" if !KUNIT_ALL_TESTS
+ depends on KUNIT
+ default KUNIT_ALL_TESTS
+ help
+ Enable this option to test the util_macros.h function at boot.
+
+ KUnit tests run during boot and output the results to the debug log
+ in TAP format (http://testanything.org/). Only useful for kernel devs
+ running the KUnit test harness, and not intended for inclusion into a
+ production build.
+
+ 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 HASH_KUNIT_TEST
tristate "KUnit Test for integer hash functions" if !KUNIT_ALL_TESTS
depends on KUNIT
diff --git a/lib/Makefile b/lib/Makefile
index 773adf88af41..444fe05caed9 100644
--- a/lib/Makefile
+++ b/lib/Makefile
@@ -370,6 +370,7 @@ obj-$(CONFIG_PLDMFW) += pldmfw/
CFLAGS_bitfield_kunit.o := $(DISABLE_STRUCTLEAK_PLUGIN)
obj-$(CONFIG_BITFIELD_KUNIT) += bitfield_kunit.o
obj-$(CONFIG_CHECKSUM_KUNIT) += checksum_kunit.o
+obj-$(CONFIG_UTIL_MACROS_KUNIT) += util_macros_kunit.o
obj-$(CONFIG_LIST_KUNIT_TEST) += list-test.o
obj-$(CONFIG_HASHTABLE_KUNIT_TEST) += hashtable_test.o
obj-$(CONFIG_LINEAR_RANGES_TEST) += test_linear_ranges.o
diff --git a/lib/util_macros_kunit.c b/lib/util_macros_kunit.c
new file mode 100644
index 000000000000..94cc9f0de50a
--- /dev/null
+++ b/lib/util_macros_kunit.c
@@ -0,0 +1,240 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Test cases for bitfield helpers.
+ */
+
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
+#include <kunit/test.h>
+#include <linux/util_macros.h>
+
+#define FIND_CLOSEST_RANGE_CHECK(from, to, array, exp_idx) \
+{ \
+ int i; \
+ for (i = from; i <= to; i++) { \
+ int found = find_closest(i, array, ARRAY_SIZE(array)); \
+ KUNIT_ASSERT_EQ(ctx, exp_idx, found); \
+ } \
+}
+
+static void test_find_closest(struct kunit *ctx)
+{
+ /* This will test a few arrays that are found in drivers */
+ static const int ina226_avg_tab[] = { 1, 4, 16, 64, 128, 256, 512, 1024 };
+ static const unsigned int ad7616_oversampling_avail[] = {
+ 1, 2, 4, 8, 16, 32, 64, 128,
+ };
+ static u32 wd_timeout_table[] = { 2, 4, 6, 8, 16, 32, 48, 64 };
+ static int array_prog1a[] = { 1, 2, 3, 4, 5 };
+ static u32 array_prog1b[] = { 2, 3, 4, 5, 6 };
+ static int array_prog1mix[] = { -2, -1, 0, 1, 2 };
+ static int array_prog2a[] = { 1, 3, 5, 7 };
+ static u32 array_prog2b[] = { 2, 4, 6, 8 };
+ static int array_prog3a[] = { 1, 4, 7, 10 };
+ static u32 array_prog3b[] = { 2, 5, 8, 11 };
+ static int array_prog4a[] = { 1, 5, 9, 13 };
+ static u32 array_prog4b[] = { 2, 6, 10, 14 };
+
+ FIND_CLOSEST_RANGE_CHECK(-3, 2, ina226_avg_tab, 0);
+ FIND_CLOSEST_RANGE_CHECK(3, 10, ina226_avg_tab, 1);
+ FIND_CLOSEST_RANGE_CHECK(11, 40, ina226_avg_tab, 2);
+ FIND_CLOSEST_RANGE_CHECK(41, 96, ina226_avg_tab, 3);
+ FIND_CLOSEST_RANGE_CHECK(97, 192, ina226_avg_tab, 4);
+ FIND_CLOSEST_RANGE_CHECK(193, 384, ina226_avg_tab, 5);
+ FIND_CLOSEST_RANGE_CHECK(385, 768, ina226_avg_tab, 6);
+ FIND_CLOSEST_RANGE_CHECK(769, 2048, ina226_avg_tab, 7);
+
+ /* The array that found the bug that caused this kunit to exist */
+ FIND_CLOSEST_RANGE_CHECK(-3, 1, ad7616_oversampling_avail, 0);
+ FIND_CLOSEST_RANGE_CHECK(2, 3, ad7616_oversampling_avail, 1);
+ FIND_CLOSEST_RANGE_CHECK(4, 6, ad7616_oversampling_avail, 2);
+ FIND_CLOSEST_RANGE_CHECK(7, 12, ad7616_oversampling_avail, 3);
+ FIND_CLOSEST_RANGE_CHECK(13, 24, ad7616_oversampling_avail, 4);
+ FIND_CLOSEST_RANGE_CHECK(25, 48, ad7616_oversampling_avail, 5);
+ FIND_CLOSEST_RANGE_CHECK(49, 96, ad7616_oversampling_avail, 6);
+ FIND_CLOSEST_RANGE_CHECK(97, 256, ad7616_oversampling_avail, 7);
+
+ FIND_CLOSEST_RANGE_CHECK(-3, 3, wd_timeout_table, 0);
+ FIND_CLOSEST_RANGE_CHECK(4, 5, wd_timeout_table, 1);
+ FIND_CLOSEST_RANGE_CHECK(6, 7, wd_timeout_table, 2);
+ FIND_CLOSEST_RANGE_CHECK(8, 12, wd_timeout_table, 3);
+ FIND_CLOSEST_RANGE_CHECK(13, 24, wd_timeout_table, 4);
+ FIND_CLOSEST_RANGE_CHECK(25, 40, wd_timeout_table, 5);
+ FIND_CLOSEST_RANGE_CHECK(41, 56, wd_timeout_table, 6);
+ FIND_CLOSEST_RANGE_CHECK(57, 128, wd_timeout_table, 7);
+
+ /* One could argue that find_closest() should not be used for monotonic
+ * arrays (like 1,2,3,4,5), but even so, it should work as long as the
+ * array is sorted ascending. */
+ FIND_CLOSEST_RANGE_CHECK(-3, 1, array_prog1a, 0);
+ FIND_CLOSEST_RANGE_CHECK(2, 2, array_prog1a, 1);
+ FIND_CLOSEST_RANGE_CHECK(3, 3, array_prog1a, 2);
+ FIND_CLOSEST_RANGE_CHECK(4, 4, array_prog1a, 3);
+ FIND_CLOSEST_RANGE_CHECK(5, 8, array_prog1a, 4);
+
+ FIND_CLOSEST_RANGE_CHECK(-3, 2, array_prog1b, 0);
+ FIND_CLOSEST_RANGE_CHECK(3, 3, array_prog1b, 1);
+ FIND_CLOSEST_RANGE_CHECK(4, 4, array_prog1b, 2);
+ FIND_CLOSEST_RANGE_CHECK(5, 5, array_prog1b, 3);
+ FIND_CLOSEST_RANGE_CHECK(6, 8, array_prog1b, 4);
+
+ FIND_CLOSEST_RANGE_CHECK(-4, -2, array_prog1mix, 0);
+ FIND_CLOSEST_RANGE_CHECK(-1, -1, array_prog1mix, 1);
+ FIND_CLOSEST_RANGE_CHECK(0, 0, array_prog1mix, 2);
+ FIND_CLOSEST_RANGE_CHECK(1, 1, array_prog1mix, 3);
+ FIND_CLOSEST_RANGE_CHECK(2, 5, array_prog1mix, 4);
+
+ FIND_CLOSEST_RANGE_CHECK(-3, 2, array_prog2a, 0);
+ FIND_CLOSEST_RANGE_CHECK(3, 4, array_prog2a, 1);
+ FIND_CLOSEST_RANGE_CHECK(5, 6, array_prog2a, 2);
+ FIND_CLOSEST_RANGE_CHECK(7, 10, array_prog2a, 3);
+
+ FIND_CLOSEST_RANGE_CHECK(-3, 3, array_prog2b, 0);
+ FIND_CLOSEST_RANGE_CHECK(4, 5, array_prog2b, 1);
+ FIND_CLOSEST_RANGE_CHECK(6, 7, array_prog2b, 2);
+ FIND_CLOSEST_RANGE_CHECK(8, 10, array_prog2b, 3);
+
+ FIND_CLOSEST_RANGE_CHECK(-3, 2, array_prog3a, 0);
+ FIND_CLOSEST_RANGE_CHECK(3, 5, array_prog3a, 1);
+ FIND_CLOSEST_RANGE_CHECK(6, 8, array_prog3a, 2);
+ FIND_CLOSEST_RANGE_CHECK(9, 20, array_prog3a, 3);
+
+ FIND_CLOSEST_RANGE_CHECK(-3, 3, array_prog3b, 0);
+ FIND_CLOSEST_RANGE_CHECK(4, 6, array_prog3b, 1);
+ FIND_CLOSEST_RANGE_CHECK(7, 9, array_prog3b, 2);
+ FIND_CLOSEST_RANGE_CHECK(10, 20, array_prog3b, 3);
+
+ FIND_CLOSEST_RANGE_CHECK(-3, 3, array_prog4a, 0);
+ FIND_CLOSEST_RANGE_CHECK(4, 7, array_prog4a, 1);
+ FIND_CLOSEST_RANGE_CHECK(8, 11, array_prog4a, 2);
+ FIND_CLOSEST_RANGE_CHECK(12, 20, array_prog4a, 3);
+
+ FIND_CLOSEST_RANGE_CHECK(-3, 4, array_prog4b, 0);
+ FIND_CLOSEST_RANGE_CHECK(5, 8, array_prog4b, 1);
+ FIND_CLOSEST_RANGE_CHECK(9, 12, array_prog4b, 2);
+ FIND_CLOSEST_RANGE_CHECK(13, 20, array_prog4b, 3);
+}
+
+#define FIND_CLOSEST_DESC_RANGE_CHECK(from, to, array, exp_idx) \
+{ \
+ int i; \
+ for (i = from; i <= to; i++) { \
+ int found = find_closest_descending(i, array, \
+ ARRAY_SIZE(array)); \
+ KUNIT_ASSERT_EQ(ctx, exp_idx, found); \
+ } \
+}
+
+static void test_find_closest_descending(struct kunit *ctx)
+{
+ /* Same arrays as 'test_find_closest' but reversed */
+ static const int ina226_avg_tab[] = { 1024, 512, 256, 128, 64, 16, 4, 1 };
+ static const unsigned int ad7616_oversampling_avail[] = {
+ 128, 64, 32, 16, 8, 4, 2, 1
+ };
+ static u32 wd_timeout_table[] = { 64, 48, 32, 16, 8, 6, 4, 2 };
+ static int array_prog1a[] = { 5, 4, 3, 2, 1 };
+ static u32 array_prog1b[] = { 6, 5, 4, 3, 2 };
+ static int array_prog1mix[] = { 2, 1, 0, -1, -2 };
+ static int array_prog2a[] = { 7, 5, 3, 1 };
+ static u32 array_prog2b[] = { 8, 6, 4, 2 };
+ static int array_prog3a[] = { 10, 7, 4, 1 };
+ static u32 array_prog3b[] = { 11, 8, 5, 2 };
+ static int array_prog4a[] = { 13, 9, 5, 1 };
+ static u32 array_prog4b[] = { 14, 10, 6, 2 };
+
+ FIND_CLOSEST_DESC_RANGE_CHECK(-3, 2, ina226_avg_tab, 7);
+ FIND_CLOSEST_DESC_RANGE_CHECK(3, 10, ina226_avg_tab, 6);
+ FIND_CLOSEST_DESC_RANGE_CHECK(11, 40, ina226_avg_tab, 5);
+ FIND_CLOSEST_DESC_RANGE_CHECK(41, 96, ina226_avg_tab, 4);
+ FIND_CLOSEST_DESC_RANGE_CHECK(97, 192, ina226_avg_tab, 3);
+ FIND_CLOSEST_DESC_RANGE_CHECK(193, 384, ina226_avg_tab, 2);
+ FIND_CLOSEST_DESC_RANGE_CHECK(385, 768, ina226_avg_tab, 1);
+ FIND_CLOSEST_DESC_RANGE_CHECK(769, 2048, ina226_avg_tab, 0);
+
+ FIND_CLOSEST_DESC_RANGE_CHECK(-3, 1, ad7616_oversampling_avail, 7);
+ FIND_CLOSEST_DESC_RANGE_CHECK(2, 3, ad7616_oversampling_avail, 6);
+ FIND_CLOSEST_DESC_RANGE_CHECK(4, 6, ad7616_oversampling_avail, 5);
+ FIND_CLOSEST_DESC_RANGE_CHECK(7, 12, ad7616_oversampling_avail, 4);
+ FIND_CLOSEST_DESC_RANGE_CHECK(13, 24, ad7616_oversampling_avail, 3);
+ FIND_CLOSEST_DESC_RANGE_CHECK(25, 48, ad7616_oversampling_avail, 2);
+ FIND_CLOSEST_DESC_RANGE_CHECK(49, 96, ad7616_oversampling_avail, 1);
+ FIND_CLOSEST_DESC_RANGE_CHECK(97, 256, ad7616_oversampling_avail, 0);
+
+ FIND_CLOSEST_DESC_RANGE_CHECK(-3, 3, wd_timeout_table, 7);
+ FIND_CLOSEST_DESC_RANGE_CHECK(4, 5, wd_timeout_table, 6);
+ FIND_CLOSEST_DESC_RANGE_CHECK(6, 7, wd_timeout_table, 5);
+ FIND_CLOSEST_DESC_RANGE_CHECK(8, 12, wd_timeout_table, 4);
+ FIND_CLOSEST_DESC_RANGE_CHECK(13, 24, wd_timeout_table, 3);
+ FIND_CLOSEST_DESC_RANGE_CHECK(25, 40, wd_timeout_table, 2);
+ FIND_CLOSEST_DESC_RANGE_CHECK(41, 56, wd_timeout_table, 1);
+ FIND_CLOSEST_DESC_RANGE_CHECK(57, 128, wd_timeout_table, 0);
+
+ /* One could argue that find_closest_descending() should not be used
+ * for monotonic arrays (like 5,4,3,2,1), but even so, it should still
+ * it should work as long as the array is sorted descending. */
+ FIND_CLOSEST_DESC_RANGE_CHECK(-3, 1, array_prog1a, 4);
+ FIND_CLOSEST_DESC_RANGE_CHECK(2, 2, array_prog1a, 3);
+ FIND_CLOSEST_DESC_RANGE_CHECK(3, 3, array_prog1a, 2);
+ FIND_CLOSEST_DESC_RANGE_CHECK(4, 4, array_prog1a, 1);
+ FIND_CLOSEST_DESC_RANGE_CHECK(5, 8, array_prog1a, 0);
+
+ FIND_CLOSEST_DESC_RANGE_CHECK(-3, 2, array_prog1b, 4);
+ FIND_CLOSEST_DESC_RANGE_CHECK(3, 3, array_prog1b, 3);
+ FIND_CLOSEST_DESC_RANGE_CHECK(4, 4, array_prog1b, 2);
+ FIND_CLOSEST_DESC_RANGE_CHECK(5, 5, array_prog1b, 1);
+ FIND_CLOSEST_DESC_RANGE_CHECK(6, 8, array_prog1b, 0);
+
+ FIND_CLOSEST_DESC_RANGE_CHECK(-4, -2, array_prog1mix, 4);
+ FIND_CLOSEST_DESC_RANGE_CHECK(-1, -1, array_prog1mix, 3);
+ FIND_CLOSEST_DESC_RANGE_CHECK(0, 0, array_prog1mix, 2);
+ FIND_CLOSEST_DESC_RANGE_CHECK(1, 1, array_prog1mix, 1);
+ FIND_CLOSEST_DESC_RANGE_CHECK(2, 5, array_prog1mix, 0);
+
+ FIND_CLOSEST_DESC_RANGE_CHECK(-3, 2, array_prog2a, 3);
+ FIND_CLOSEST_DESC_RANGE_CHECK(3, 4, array_prog2a, 2);
+ FIND_CLOSEST_DESC_RANGE_CHECK(5, 6, array_prog2a, 1);
+ FIND_CLOSEST_DESC_RANGE_CHECK(7, 10, array_prog2a, 0);
+
+ FIND_CLOSEST_DESC_RANGE_CHECK(-3, 3, array_prog2b, 3);
+ FIND_CLOSEST_DESC_RANGE_CHECK(4, 5, array_prog2b, 2);
+ FIND_CLOSEST_DESC_RANGE_CHECK(6, 7, array_prog2b, 1);
+ FIND_CLOSEST_DESC_RANGE_CHECK(8, 10, array_prog2b, 0);
+
+ FIND_CLOSEST_DESC_RANGE_CHECK(-3, 2, array_prog3a, 3);
+ FIND_CLOSEST_DESC_RANGE_CHECK(3, 5, array_prog3a, 2);
+ FIND_CLOSEST_DESC_RANGE_CHECK(6, 8, array_prog3a, 1);
+ FIND_CLOSEST_DESC_RANGE_CHECK(9, 20, array_prog3a, 0);
+
+ FIND_CLOSEST_DESC_RANGE_CHECK(-3, 3, array_prog3b, 3);
+ FIND_CLOSEST_DESC_RANGE_CHECK(4, 6, array_prog3b, 2);
+ FIND_CLOSEST_DESC_RANGE_CHECK(7, 9, array_prog3b, 1);
+ FIND_CLOSEST_DESC_RANGE_CHECK(10, 20, array_prog3b, 0);
+
+ FIND_CLOSEST_DESC_RANGE_CHECK(-3, 3, array_prog4a, 3);
+ FIND_CLOSEST_DESC_RANGE_CHECK(4, 7, array_prog4a, 2);
+ FIND_CLOSEST_DESC_RANGE_CHECK(8, 11, array_prog4a, 1);
+ FIND_CLOSEST_DESC_RANGE_CHECK(12, 20, array_prog4a, 0);
+
+ FIND_CLOSEST_DESC_RANGE_CHECK(-3, 4, array_prog4b, 3);
+ FIND_CLOSEST_DESC_RANGE_CHECK(5, 8, array_prog4b, 2);
+ FIND_CLOSEST_DESC_RANGE_CHECK(9, 12, array_prog4b, 1);
+ FIND_CLOSEST_DESC_RANGE_CHECK(13, 20, array_prog4b, 0);
+}
+
+static struct kunit_case __refdata util_macros_test_cases[] = {
+ KUNIT_CASE(test_find_closest),
+ KUNIT_CASE(test_find_closest_descending),
+ {}
+};
+
+static struct kunit_suite util_macros_test_suite = {
+ .name = "util_macros.h",
+ .test_cases = util_macros_test_cases,
+};
+
+kunit_test_suites(&util_macros_test_suite);
+
+MODULE_AUTHOR("Alexandru Ardelean <aardelean@baylibre.com>");
+MODULE_DESCRIPTION("Test cases for util_macros.h helpers");
+MODULE_LICENSE("GPL");
--
2.46.1
^ permalink raw reply related [flat|nested] 5+ messages in thread
* Re: [PATCH v2 1/2] util_macros.h: fix/rework find_closest() macros
2024-11-05 14:54 [PATCH v2 1/2] util_macros.h: fix/rework find_closest() macros Alexandru Ardelean
2024-11-05 14:54 ` [PATCH v2 2/2] lib: util_macros_kunit: add kunit test for util_macros.h Alexandru Ardelean
@ 2024-11-05 23:08 ` Andrew Morton
2024-11-06 14:03 ` Alexandru Ardelean
1 sibling, 1 reply; 5+ messages in thread
From: Andrew Morton @ 2024-11-05 23:08 UTC (permalink / raw)
To: Alexandru Ardelean
Cc: linux-iio, linux-kernel, jic23, bartosz.golaszewski, gregkh
On Tue, 5 Nov 2024 16:54:05 +0200 Alexandru Ardelean <aardelean@baylibre.com> wrote:
> A bug was found in the find_closest() (find_closest_descending() is also
> affected after some testing), where for certain values with small
> progressions, the rounding (done by averaging 2 values) causes an incorrect
> index to be returned.
Convincing changelog, thanks.
> -#define find_closest(x, a, as) __find_closest(x, a, as, <=)
> +#define find_closest(x, a, as) \
> +({ \
> + typeof(as) __fc_i, __fc_as = (as) - 1; \
> + long __fc_mid_x, __fc_x = (x); \
> + long __fc_left, __fc_right; \
> + typeof(*a) const *__fc_a = (a); \
> + for (__fc_i = 0; __fc_i < __fc_as; __fc_i++) { \
> + __fc_mid_x = (__fc_a[__fc_i] + __fc_a[__fc_i + 1]) / 2; \
> + if (__fc_x <= __fc_mid_x) { \
> + __fc_left = __fc_x - __fc_a[__fc_i]; \
> + __fc_right = __fc_a[__fc_i + 1] - __fc_x; \
> + if (__fc_right < __fc_left) \
> + __fc_i++; \
> + break; \
> + } \
> + } \
> + (__fc_i); \
> +})
>
> ...
>
> +#define find_closest_descending(x, a, as) \
Boy these things are hard to read. They're also bloaty and I'm
counting 36ish callsites!
Can we fix both issues by just giving up on the macro approach and
reimplement them in out-of-line C code? All the sites I looked at are
using 32-bit quantities - a mix of signed and unsigned.
It's separate from this bugfix of course, but would it be feasible for
someone to go switch all callers to use u32's then reimplement these in
lib/find_closest.c?
^ permalink raw reply [flat|nested] 5+ messages in thread
* Re: [PATCH v2 1/2] util_macros.h: fix/rework find_closest() macros
2024-11-05 23:08 ` [PATCH v2 1/2] util_macros.h: fix/rework find_closest() macros Andrew Morton
@ 2024-11-06 14:03 ` Alexandru Ardelean
2024-11-06 20:08 ` Andrew Morton
0 siblings, 1 reply; 5+ messages in thread
From: Alexandru Ardelean @ 2024-11-06 14:03 UTC (permalink / raw)
To: Andrew Morton; +Cc: linux-iio, linux-kernel, jic23, bartosz.golaszewski, gregkh
On Wed, Nov 6, 2024 at 1:08 AM Andrew Morton <akpm@linux-foundation.org> wrote:
>
> On Tue, 5 Nov 2024 16:54:05 +0200 Alexandru Ardelean <aardelean@baylibre.com> wrote:
>
> > A bug was found in the find_closest() (find_closest_descending() is also
> > affected after some testing), where for certain values with small
> > progressions, the rounding (done by averaging 2 values) causes an incorrect
> > index to be returned.
>
> Convincing changelog, thanks.
>
> > -#define find_closest(x, a, as) __find_closest(x, a, as, <=)
> > +#define find_closest(x, a, as) \
> > +({ \
> > + typeof(as) __fc_i, __fc_as = (as) - 1; \
> > + long __fc_mid_x, __fc_x = (x); \
> > + long __fc_left, __fc_right; \
> > + typeof(*a) const *__fc_a = (a); \
> > + for (__fc_i = 0; __fc_i < __fc_as; __fc_i++) { \
> > + __fc_mid_x = (__fc_a[__fc_i] + __fc_a[__fc_i + 1]) / 2; \
> > + if (__fc_x <= __fc_mid_x) { \
> > + __fc_left = __fc_x - __fc_a[__fc_i]; \
> > + __fc_right = __fc_a[__fc_i + 1] - __fc_x; \
> > + if (__fc_right < __fc_left) \
> > + __fc_i++; \
> > + break; \
> > + } \
> > + } \
> > + (__fc_i); \
> > +})
> >
> > ...
> >
> > +#define find_closest_descending(x, a, as) \
>
> Boy these things are hard to read. They're also bloaty and I'm
> counting 36ish callsites!
>
Yeah, they're not easy on the eyes at first.
But you do get used to them, after spending some time trying to
understand why they're not working for some values.
> Can we fix both issues by just giving up on the macro approach and
> reimplement them in out-of-line C code? All the sites I looked at are
> using 32-bit quantities - a mix of signed and unsigned.
>
Converting this to a static-inline was my other thought, rather than
keeping the macros.
But I'm not sure where to draw the line between too much rework vs a bug-fix.
Just fixing the bug was done in V1 of this patch, but then the kunit
exposed a bunch more.
> It's separate from this bugfix of course, but would it be feasible for
> someone to go switch all callers to use u32's then reimplement these in
> lib/find_closest.c?
>
That would work.
How would a rework be preferred?
As a continuation to this patchset? Or a V3 to this patchset?
It's not a big effort to do, now that the kunit is in place.
I actually have a bunch of kunit variants (locally) that try various
combinations of signed/unsigned X & arrays.
But they drove me slightly nuts, until I decided to do the enforcement
to 'long' type for x, mid, left, right variables.
A catch-all implementation (for all current use-cases) would be best
with an int32 vs uint32 for X, mid, left & right (variables).
The thing with X being an int32 is more related to what userspace
would expect to see when inputting a negative number against an array
(of signed or unsigned).
The type of the elements in the array (signed or unsigned) doesn't
matter much (if focusing on the type for the X, mid, left & right
variables).
For the oversampling feature in ad7606, with unsigned X:
echo -1 > /sys/bus/iio/devices/iio\:device0/oversampling_ratio
would return 128 rather than 0 (for "cat
/sys/bus/iio/devices/iio\:device0/oversampling_ratio")
Right now, the IIO framework treats -1 as an 'int'
But, moving forward: what would some preferences be?
- have variants of find_closest() for unsigned/signed arrays? (
find_closest_u32() or find_closest_i32() ?)
- AFAICT so far, there aren't any values in the arrays that get
close to INT32_MAX, so int32 may work for now
- maybe later some 64-bit variants could be added if needed
- should the variables X, mid, left & right be the same signedness as the array
The only preference (towards which I'm leaning) is just making sure
that X (and friends) are signed.
^ permalink raw reply [flat|nested] 5+ messages in thread
* Re: [PATCH v2 1/2] util_macros.h: fix/rework find_closest() macros
2024-11-06 14:03 ` Alexandru Ardelean
@ 2024-11-06 20:08 ` Andrew Morton
0 siblings, 0 replies; 5+ messages in thread
From: Andrew Morton @ 2024-11-06 20:08 UTC (permalink / raw)
To: Alexandru Ardelean
Cc: linux-iio, linux-kernel, jic23, bartosz.golaszewski, gregkh
On Wed, 6 Nov 2024 16:03:36 +0200 Alexandru Ardelean <aardelean@baylibre.com> wrote:
> On Wed, Nov 6, 2024 at 1:08 AM Andrew Morton <akpm@linux-foundation.org> wrote:
> > Can we fix both issues by just giving up on the macro approach and
> > reimplement them in out-of-line C code? All the sites I looked at are
> > using 32-bit quantities - a mix of signed and unsigned.
> >
>
> Converting this to a static-inline was my other thought, rather than
> keeping the macros.
Non-inline, I think. It's big.
> But I'm not sure where to draw the line between too much rework vs a bug-fix.
> Just fixing the bug was done in V1 of this patch, but then the kunit
> exposed a bunch more.
Sure, just the minimum for a bugfix.
> > It's separate from this bugfix of course, but would it be feasible for
> > someone to go switch all callers to use u32's then reimplement these in
> > lib/find_closest.c?
> >
>
> That would work.
> How would a rework be preferred?
> As a continuation to this patchset? Or a V3 to this patchset?
A new and separate patchset. A low-priority cleanup from whoever has
the time and motivation ;)
> But, moving forward: what would some preferences be?
> - have variants of find_closest() for unsigned/signed arrays? (
> find_closest_u32() or find_closest_i32() ?)
> - AFAICT so far, there aren't any values in the arrays that get
> close to INT32_MAX, so int32 may work for now
> - maybe later some 64-bit variants could be added if needed
> - should the variables X, mid, left & right be the same signedness as the array
>
> The only preference (towards which I'm leaning) is just making sure
> that X (and friends) are signed.
Yes, I guess int32 would be best. I agree that unsigned values greater
than INT_MAX are unlikely.
I suggest a series of patches which convert individual callers to int32
and the final patch introduces lib/find_closest.c.
^ permalink raw reply [flat|nested] 5+ messages in thread
end of thread, other threads:[~2024-11-06 20:08 UTC | newest]
Thread overview: 5+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2024-11-05 14:54 [PATCH v2 1/2] util_macros.h: fix/rework find_closest() macros Alexandru Ardelean
2024-11-05 14:54 ` [PATCH v2 2/2] lib: util_macros_kunit: add kunit test for util_macros.h Alexandru Ardelean
2024-11-05 23:08 ` [PATCH v2 1/2] util_macros.h: fix/rework find_closest() macros Andrew Morton
2024-11-06 14:03 ` Alexandru Ardelean
2024-11-06 20:08 ` Andrew Morton
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox