* [PATCH 0/7] hwmon: iio: Add alarm support
@ 2025-07-15 1:20 Sean Anderson
2025-07-15 1:20 ` [PATCH 1/7] math64: Add div64_s64_rem Sean Anderson
` (6 more replies)
0 siblings, 7 replies; 62+ messages in thread
From: Sean Anderson @ 2025-07-15 1:20 UTC (permalink / raw)
To: Jonathan Cameron, Jean Delvare, Guenter Roeck, linux-iio,
linux-hwmon
Cc: Andy Shevchenko, Nuno Sá, linux-kernel, David Lechner,
Sean Anderson
Add alarm support for IIO HWMONs as well as the minimum/maximum
thresholds. This involves the creation of two new in-kernel IIO APIs to
set the thresholds and be notified of events.
I think this should probably go through the iio tree given the amount of
IIO stuff it touches.
Sean Anderson (7):
math64: Add div64_s64_rem
iio: inkern: Add API for reading/writing events
iio: Add in-kernel API for events
hwmon: iio: Refactor scale calculation into helper
hwmon: iio: Add helper function for creating attributes
hwmon: iio: Add min/max support
hwmon: iio: Add alarm support
drivers/hwmon/iio_hwmon.c | 522 ++++++++++++++++++++++++++++---
drivers/iio/industrialio-event.c | 34 +-
drivers/iio/inkern.c | 198 ++++++++++++
include/linux/iio/consumer.h | 86 +++++
include/linux/math64.h | 18 ++
lib/math/div64.c | 20 ++
6 files changed, 830 insertions(+), 48 deletions(-)
--
2.35.1.1320.gc452695387.dirty
^ permalink raw reply [flat|nested] 62+ messages in thread
* [PATCH 1/7] math64: Add div64_s64_rem
2025-07-15 1:20 [PATCH 0/7] hwmon: iio: Add alarm support Sean Anderson
@ 2025-07-15 1:20 ` Sean Anderson
2025-07-15 8:03 ` Andy Shevchenko
2025-07-15 1:20 ` [PATCH 2/7] iio: inkern: Add API for reading/writing events Sean Anderson
` (5 subsequent siblings)
6 siblings, 1 reply; 62+ messages in thread
From: Sean Anderson @ 2025-07-15 1:20 UTC (permalink / raw)
To: Jonathan Cameron, Jean Delvare, Guenter Roeck, linux-iio,
linux-hwmon
Cc: Andy Shevchenko, Nuno Sá, linux-kernel, David Lechner,
Sean Anderson
Add a function to do signed 64-bit division with remainder. This is
implemented using div64_u64_rem in the same way that div_s64_rem is
implemented using div_u64_rem.
Signed-off-by: Sean Anderson <sean.anderson@linux.dev>
---
include/linux/math64.h | 18 ++++++++++++++++++
lib/math/div64.c | 20 ++++++++++++++++++++
2 files changed, 38 insertions(+)
diff --git a/include/linux/math64.h b/include/linux/math64.h
index 6aaccc1626ab..0a414446af89 100644
--- a/include/linux/math64.h
+++ b/include/linux/math64.h
@@ -57,6 +57,20 @@ static inline u64 div64_u64_rem(u64 dividend, u64 divisor, u64 *remainder)
return dividend / divisor;
}
+/**
+ * div64_s64_rem - signed 64bit divide with 64bit divisor and remainder
+ * @dividend: signed 64bit dividend
+ * @divisor: signed 64bit divisor
+ * @remainder: pointer to signed 64bit remainder
+ *
+ * Return: sets ``*remainder``, then returns dividend / divisor
+ */
+static inline s64 div64_s64_rem(s64 dividend, s64 divisor, s64 *remainder)
+{
+ *remainder = dividend % divisor;
+ return dividend / divisor;
+}
+
/**
* div64_u64 - unsigned 64bit divide with 64bit divisor
* @dividend: unsigned 64bit dividend
@@ -102,6 +116,10 @@ extern s64 div_s64_rem(s64 dividend, s32 divisor, s32 *remainder);
extern u64 div64_u64_rem(u64 dividend, u64 divisor, u64 *remainder);
#endif
+#ifndef div64_s64_rem
+s64 div64_s64_rem(s64 dividend, s64 divisor, s64 *remainder);
+#endif
+
#ifndef div64_u64
extern u64 div64_u64(u64 dividend, u64 divisor);
#endif
diff --git a/lib/math/div64.c b/lib/math/div64.c
index 5faa29208bdb..ccef0db85681 100644
--- a/lib/math/div64.c
+++ b/lib/math/div64.c
@@ -124,6 +124,26 @@ u64 div64_u64_rem(u64 dividend, u64 divisor, u64 *remainder)
EXPORT_SYMBOL(div64_u64_rem);
#endif
+#ifndef div_s64_rem
+s64 div64_s64_rem(s64 dividend, s64 divisor, s64 *remainder)
+{
+ u64 quotient;
+
+ if (dividend < 0) {
+ quotient = div64_u64_rem(-dividend, abs(divisor), (u64 *)remainder);
+ *remainder = -*remainder;
+ if (divisor > 0)
+ quotient = -quotient;
+ } else {
+ quotient = div64_u64_rem(dividend, abs(divisor), (u64 *)remainder);
+ if (divisor < 0)
+ quotient = -quotient;
+ }
+ return quotient;
+}
+EXPORT_SYMBOL(div64_s64_rem);
+#endif
+
/*
* div64_u64 - unsigned 64bit divide with 64bit divisor
* @dividend: 64bit dividend
--
2.35.1.1320.gc452695387.dirty
^ permalink raw reply related [flat|nested] 62+ messages in thread
* [PATCH 2/7] iio: inkern: Add API for reading/writing events
2025-07-15 1:20 [PATCH 0/7] hwmon: iio: Add alarm support Sean Anderson
2025-07-15 1:20 ` [PATCH 1/7] math64: Add div64_s64_rem Sean Anderson
@ 2025-07-15 1:20 ` Sean Anderson
2025-07-15 8:18 ` Andy Shevchenko
` (2 more replies)
2025-07-15 1:20 ` [PATCH 3/7] iio: Add in-kernel API for events Sean Anderson
` (4 subsequent siblings)
6 siblings, 3 replies; 62+ messages in thread
From: Sean Anderson @ 2025-07-15 1:20 UTC (permalink / raw)
To: Jonathan Cameron, Jean Delvare, Guenter Roeck, linux-iio,
linux-hwmon
Cc: Andy Shevchenko, Nuno Sá, linux-kernel, David Lechner,
Sean Anderson
Add an in-kernel API for reading/writing event properties. Like the
raw-to-processed conversion, with processed-to-raw we only convert the
integer part, introducing some round-off error.
A common case is for other drivers to re-expose IIO events as sysfs
properties with a different API. To help out with this, iio_event_mode
returns the appropriate mode. It can also be used to test for existence
if the consumer doesn't care about read/write capability.
Signed-off-by: Sean Anderson <sean.anderson@linux.dev>
---
drivers/iio/inkern.c | 198 +++++++++++++++++++++++++++++++++++
include/linux/iio/consumer.h | 56 ++++++++++
2 files changed, 254 insertions(+)
diff --git a/drivers/iio/inkern.c b/drivers/iio/inkern.c
index c174ebb7d5e6..d3bbd2444fb5 100644
--- a/drivers/iio/inkern.c
+++ b/drivers/iio/inkern.c
@@ -1028,3 +1028,201 @@ ssize_t iio_read_channel_label(struct iio_channel *chan, char *buf)
return do_iio_read_channel_label(chan->indio_dev, chan->channel, buf);
}
EXPORT_SYMBOL_GPL(iio_read_channel_label);
+
+static bool iio_event_exists(struct iio_channel *channel,
+ enum iio_event_type type,
+ enum iio_event_direction dir,
+ enum iio_event_info info)
+{
+ struct iio_chan_spec const *chan = channel->channel;
+ int i;
+
+ if (!channel->indio_dev->info)
+ return false;
+
+ for (i = 0; i < chan->num_event_specs; i++) {
+ if (chan->event_spec[i].type != type)
+ continue;
+ if (chan->event_spec[i].dir != dir)
+ continue;
+ if (chan->event_spec[i].mask_separate & BIT(info))
+ return true;
+ }
+
+ return false;
+}
+
+umode_t iio_event_mode(struct iio_channel *chan, enum iio_event_type type,
+ enum iio_event_direction dir, enum iio_event_info info)
+{
+ struct iio_dev *indio_dev = chan->indio_dev;
+ struct iio_dev_opaque *iio_dev_opaque = to_iio_dev_opaque(indio_dev);
+ umode_t mode = 0;
+
+ guard(mutex)(&iio_dev_opaque->info_exist_lock);
+ if (!iio_event_exists(chan, type, dir, info))
+ return 0;
+
+ if (info == IIO_EV_INFO_ENABLE) {
+ if (indio_dev->info->read_event_config)
+ mode |= 0444;
+
+ if (indio_dev->info->write_event_config)
+ mode |= 0200;
+ } else {
+ if (indio_dev->info->read_event_value)
+ mode |= 0444;
+
+ if (indio_dev->info->write_event_value)
+ mode |= 0200;
+ }
+
+ return mode;
+}
+EXPORT_SYMBOL_GPL(iio_event_mode);
+
+int iio_read_event_processed_scale(struct iio_channel *chan,
+ enum iio_event_type type,
+ enum iio_event_direction dir,
+ enum iio_event_info info, int *val,
+ unsigned int scale)
+{
+ struct iio_dev *indio_dev = chan->indio_dev;
+ struct iio_dev_opaque *iio_dev_opaque = to_iio_dev_opaque(indio_dev);
+ int ret, raw;
+
+ guard(mutex)(&iio_dev_opaque->info_exist_lock);
+ if (!iio_event_exists(chan, type, dir, info))
+ return -ENODEV;
+
+ if (info == IIO_EV_INFO_ENABLE) {
+ if (!indio_dev->info->read_event_config)
+ return -EINVAL;
+
+ raw = indio_dev->info->read_event_config(indio_dev,
+ chan->channel, type,
+ dir);
+ if (raw < 0)
+ return raw;
+
+ *val = raw;
+ return 0;
+ }
+
+ if (!indio_dev->info->read_event_value)
+ return -EINVAL;
+
+ ret = indio_dev->info->read_event_value(indio_dev, chan->channel, type,
+ dir, info, &raw, NULL);
+ if (ret < 0)
+ return ret;
+
+ return iio_convert_raw_to_processed_unlocked(chan, raw, val, scale);
+}
+EXPORT_SYMBOL_GPL(iio_read_event_processed_scale);
+
+static int iio_convert_processed_to_raw_unlocked(struct iio_channel *chan,
+ int processed, int *raw,
+ unsigned int scale)
+{
+ int scale_type, scale_val, scale_val2;
+ int offset_type, offset_val, offset_val2;
+ s64 r, scale64, raw64;
+
+ scale_type = iio_channel_read(chan, &scale_val, &scale_val2,
+ IIO_CHAN_INFO_SCALE);
+ if (scale_type < 0) {
+ raw64 = processed / scale;
+ } else {
+ switch (scale_type) {
+ case IIO_VAL_INT:
+ scale64 = (s64)scale_val * scale;
+ if (scale64 <= INT_MAX && scale64 >= INT_MIN)
+ raw64 = processed / (int)scale64;
+ else
+ raw64 = 0;
+ break;
+ case IIO_VAL_INT_PLUS_MICRO:
+ scale64 = scale_val * scale * 1000000LL + scale_val2;
+ raw64 = div64_s64_rem(processed, scale64, &r);
+ raw64 = raw64 * 1000000 +
+ div64_s64(r * 1000000, scale64);
+ break;
+ case IIO_VAL_INT_PLUS_NANO:
+ scale64 = scale_val * scale * 1000000000LL + scale_val2;
+ raw64 = div64_s64_rem(processed, scale64, &r);
+ raw64 = raw64 * 1000000000 +
+ div64_s64(r * 1000000000, scale64);
+ break;
+ case IIO_VAL_FRACTIONAL:
+ raw64 = div64_s64((s64)processed * scale_val2,
+ (s64)scale_val * scale);
+ break;
+ case IIO_VAL_FRACTIONAL_LOG2:
+ raw64 = div64_s64((s64)processed << scale_val2,
+ (s64)scale_val * scale);
+ break;
+ default:
+ return -EINVAL;
+ }
+ }
+
+ offset_type = iio_channel_read(chan, &offset_val, &offset_val2,
+ IIO_CHAN_INFO_OFFSET);
+ if (offset_type >= 0) {
+ switch (offset_type) {
+ case IIO_VAL_INT:
+ case IIO_VAL_INT_PLUS_MICRO:
+ case IIO_VAL_INT_PLUS_NANO:
+ raw64 -= offset_val;
+ break;
+ case IIO_VAL_FRACTIONAL:
+ raw64 -= offset_val / offset_val2;
+ break;
+ case IIO_VAL_FRACTIONAL_LOG2:
+ raw64 -= offset_val >> offset_val2;
+ break;
+ default:
+ return -EINVAL;
+ }
+ }
+
+ *raw = clamp(raw64, (s64)INT_MIN, (s64)INT_MAX);
+ return 0;
+}
+
+int iio_write_event_processed_scale(struct iio_channel *chan,
+ enum iio_event_type type,
+ enum iio_event_direction dir,
+ enum iio_event_info info, int processed,
+ unsigned int scale)
+{
+ struct iio_dev *indio_dev = chan->indio_dev;
+ struct iio_dev_opaque *iio_dev_opaque = to_iio_dev_opaque(chan->indio_dev);
+ int ret, raw;
+
+ guard(mutex)(&iio_dev_opaque->info_exist_lock);
+ if (!iio_event_exists(chan, type, dir, info))
+ return -ENODEV;
+
+ if (info == IIO_EV_INFO_ENABLE) {
+ if (!indio_dev->info->write_event_config)
+ return -EINVAL;
+
+ return indio_dev->info->write_event_config(indio_dev,
+ chan->channel, type,
+ dir, processed);
+ }
+
+ if (!indio_dev->info->write_event_value)
+ return -EINVAL;
+
+ ret = iio_convert_processed_to_raw_unlocked(chan, processed, &raw,
+ scale);
+ if (ret < 0)
+ return ret;
+
+ return indio_dev->info->write_event_value(indio_dev, chan->channel,
+ type, dir, info, raw, 0);
+}
+EXPORT_SYMBOL_GPL(iio_write_event_processed_scale);
diff --git a/include/linux/iio/consumer.h b/include/linux/iio/consumer.h
index 6a4479616479..16e7682474f3 100644
--- a/include/linux/iio/consumer.h
+++ b/include/linux/iio/consumer.h
@@ -451,4 +451,60 @@ ssize_t iio_write_channel_ext_info(struct iio_channel *chan, const char *attr,
*/
ssize_t iio_read_channel_label(struct iio_channel *chan, char *buf);
+/**
+ * iio_event_mode() - get file mode for an event property
+ * @chan: Channel being queried
+ * @type: Event type (theshold, rate-of-change, etc.)
+ * @dir: Event direction (rising, falling, etc.)
+ * @info: Event property (enable, value, etc.)
+ *
+ * Determine an appropriate mode for sysfs files derived from this event.
+ *
+ * Return:
+ * - `0000` if the event is unsupported or otherwise unavailable
+ * - `0444` if the event is read-only
+ * - `0200` if the event is write-only
+ * - `0644` if the event is read-write
+ */
+umode_t iio_event_mode(struct iio_channel *chan, enum iio_event_type type,
+ enum iio_event_direction dir, enum iio_event_info info);
+
+/**
+ * iio_read_event_processed_scale() - Read an event property
+ * @chan: Channel being queried
+ * @type: Event type (theshold, rate-of-change, etc.)
+ * @dir: Event direction (rising, falling, etc.)
+ * @info: Event property (enable, value, etc.)
+ * @val: Processed property value
+ * @scale: Factor to scale @val by
+ *
+ * Read a processed (scaled and offset) event property of a given channel.
+ *
+ * Return: 0 on success, or negative error on failure
+ */
+int iio_read_event_processed_scale(struct iio_channel *chan,
+ enum iio_event_type type,
+ enum iio_event_direction dir,
+ enum iio_event_info info, int *val,
+ unsigned int scale);
+
+/**
+ * iio_write_event_processed_scale() - Read an event property
+ * @chan: Channel being queried
+ * @type: Event type (theshold, rate-of-change, etc.)
+ * @dir: Event direction (rising, falling, etc.)
+ * @info: Event property (enable, value, etc.)
+ * @processed: Processed property value
+ * @scale: Factor to scale @processed by
+ *
+ * Write a processed (scaled and offset) event property of a given channel.
+ *
+ * Return: 0 on success, or negative error on failure
+ */
+int iio_write_event_processed_scale(struct iio_channel *chan,
+ enum iio_event_type type,
+ enum iio_event_direction dir,
+ enum iio_event_info info, int processed,
+ unsigned int scale);
+
#endif
--
2.35.1.1320.gc452695387.dirty
^ permalink raw reply related [flat|nested] 62+ messages in thread
* [PATCH 3/7] iio: Add in-kernel API for events
2025-07-15 1:20 [PATCH 0/7] hwmon: iio: Add alarm support Sean Anderson
2025-07-15 1:20 ` [PATCH 1/7] math64: Add div64_s64_rem Sean Anderson
2025-07-15 1:20 ` [PATCH 2/7] iio: inkern: Add API for reading/writing events Sean Anderson
@ 2025-07-15 1:20 ` Sean Anderson
2025-07-15 8:20 ` Andy Shevchenko
` (2 more replies)
2025-07-15 1:20 ` [PATCH 4/7] hwmon: iio: Refactor scale calculation into helper Sean Anderson
` (3 subsequent siblings)
6 siblings, 3 replies; 62+ messages in thread
From: Sean Anderson @ 2025-07-15 1:20 UTC (permalink / raw)
To: Jonathan Cameron, Jean Delvare, Guenter Roeck, linux-iio,
linux-hwmon
Cc: Andy Shevchenko, Nuno Sá, linux-kernel, David Lechner,
Sean Anderson
Add an API to notify consumers about events. Events still need to be
enabled using the iio_read_event/iio_write_event functions. Of course,
userspace can also manipulate the enabled events. I don't think this is
too much of an issue, since userspace can also manipulate the event
thresholds. But enabling events may cause existing programs to be
surprised when they get something unexpected. Maybe we should set the
interface as busy when there are any in-kernel listeners?
Signed-off-by: Sean Anderson <sean.anderson@linux.dev>
---
drivers/iio/industrialio-event.c | 34 +++++++++++++++++++++++++++-----
include/linux/iio/consumer.h | 30 ++++++++++++++++++++++++++++
2 files changed, 59 insertions(+), 5 deletions(-)
diff --git a/drivers/iio/industrialio-event.c b/drivers/iio/industrialio-event.c
index 06295cfc2da8..b9e3ff1cd5c9 100644
--- a/drivers/iio/industrialio-event.c
+++ b/drivers/iio/industrialio-event.c
@@ -12,11 +12,13 @@
#include <linux/kernel.h>
#include <linux/kfifo.h>
#include <linux/module.h>
+#include <linux/notifier.h>
#include <linux/poll.h>
#include <linux/sched.h>
#include <linux/slab.h>
#include <linux/uaccess.h>
#include <linux/wait.h>
+#include <linux/iio/consumer.h>
#include <linux/iio/iio.h>
#include <linux/iio/iio-opaque.h>
#include "iio_core.h"
@@ -26,6 +28,7 @@
/**
* struct iio_event_interface - chrdev interface for an event line
* @wait: wait queue to allow blocking reads of events
+ * @notifier: notifier head for in-kernel event listeners
* @det_events: list of detected events
* @dev_attr_list: list of event interface sysfs attribute
* @flags: file operations related flags including busy flag.
@@ -35,6 +38,7 @@
*/
struct iio_event_interface {
wait_queue_head_t wait;
+ struct atomic_notifier_head notifier;
DECLARE_KFIFO(det_events, struct iio_event_data, 16);
struct list_head dev_attr_list;
@@ -67,18 +71,19 @@ int iio_push_event(struct iio_dev *indio_dev, u64 ev_code, s64 timestamp)
{
struct iio_dev_opaque *iio_dev_opaque = to_iio_dev_opaque(indio_dev);
struct iio_event_interface *ev_int = iio_dev_opaque->event_interface;
- struct iio_event_data ev;
+ struct iio_event_data ev = {
+ .id = ev_code,
+ .timestamp = timestamp,
+ };
int copied;
if (!ev_int)
return 0;
+ atomic_notifier_call_chain(&ev_int->notifier, IIO_NOTIFY_EVENT, &ev);
+
/* Does anyone care? */
if (iio_event_enabled(ev_int)) {
-
- ev.id = ev_code;
- ev.timestamp = timestamp;
-
copied = kfifo_put(&ev_int->det_events, ev);
if (copied != 0)
wake_up_poll(&ev_int->wait, EPOLLIN);
@@ -223,6 +228,25 @@ static int iio_event_getfd(struct iio_dev *indio_dev)
return fd;
}
+int iio_event_register(struct iio_dev *indio_dev, struct notifier_block *block)
+{
+ struct iio_dev_opaque *iio_dev_opaque = to_iio_dev_opaque(indio_dev);
+ struct iio_event_interface *ev_int = iio_dev_opaque->event_interface;
+
+ return atomic_notifier_chain_register(&ev_int->notifier, block);
+}
+EXPORT_SYMBOL_GPL(iio_event_register);
+
+void iio_event_unregister(struct iio_dev *indio_dev,
+ struct notifier_block *block)
+{
+ struct iio_dev_opaque *iio_dev_opaque = to_iio_dev_opaque(indio_dev);
+ struct iio_event_interface *ev_int = iio_dev_opaque->event_interface;
+
+ WARN_ON(atomic_notifier_chain_unregister(&ev_int->notifier, block));
+}
+EXPORT_SYMBOL_GPL(iio_event_unregister);
+
static const char * const iio_ev_type_text[] = {
[IIO_EV_TYPE_THRESH] = "thresh",
[IIO_EV_TYPE_MAG] = "mag",
diff --git a/include/linux/iio/consumer.h b/include/linux/iio/consumer.h
index 16e7682474f3..9918e3f7af3d 100644
--- a/include/linux/iio/consumer.h
+++ b/include/linux/iio/consumer.h
@@ -507,4 +507,34 @@ int iio_write_event_processed_scale(struct iio_channel *chan,
enum iio_event_info info, int processed,
unsigned int scale);
+struct notifier_block;
+enum iio_notifier_val {
+ /** IIO_NOTIFY_EVENT: v is a pointer to &struct iio_event_data */
+ IIO_NOTIFY_EVENT,
+};
+
+/**
+ * iio_event_register() - Register a notifier for events
+ * @indio_dev: Device to be notified of events on
+ * @block: Notifier block to register
+ *
+ * Register a notifier for events on @indio_dev. @v will be a member of &enum
+ * iio_notifier_val. Notifiers will be called in atomic context. @indio_dev
+ * must stay valid until you call iio_event_unregister().
+ *
+ * Return: 0 on success, or -EEXIST if @block has already been registered
+ */
+int iio_event_register(struct iio_dev *indio_dev,
+ struct notifier_block *block);
+
+/**
+ * iio_event_unregister() - Remove a previously-added notifier
+ * @indio_dev: Device to be notified of events on
+ * @block: Notifier previously-registered with iio_event_register()
+ *
+ * Remove a previously-added notifier.
+ */
+void iio_event_unregister(struct iio_dev *indio_dev,
+ struct notifier_block *block);
+
#endif
--
2.35.1.1320.gc452695387.dirty
^ permalink raw reply related [flat|nested] 62+ messages in thread
* [PATCH 4/7] hwmon: iio: Refactor scale calculation into helper
2025-07-15 1:20 [PATCH 0/7] hwmon: iio: Add alarm support Sean Anderson
` (2 preceding siblings ...)
2025-07-15 1:20 ` [PATCH 3/7] iio: Add in-kernel API for events Sean Anderson
@ 2025-07-15 1:20 ` Sean Anderson
2025-07-15 8:35 ` Andy Shevchenko
2025-07-15 1:20 ` [PATCH 5/7] hwmon: iio: Add helper function for creating attributes Sean Anderson
` (2 subsequent siblings)
6 siblings, 1 reply; 62+ messages in thread
From: Sean Anderson @ 2025-07-15 1:20 UTC (permalink / raw)
To: Jonathan Cameron, Jean Delvare, Guenter Roeck, linux-iio,
linux-hwmon
Cc: Andy Shevchenko, Nuno Sá, linux-kernel, David Lechner,
Sean Anderson
Add a function to determine the scale parameter, since it will soon be
used in several places.
Signed-off-by: Sean Anderson <sean.anderson@linux.dev>
---
drivers/hwmon/iio_hwmon.c | 40 ++++++++++++++++++++++++++++-----------
1 file changed, 29 insertions(+), 11 deletions(-)
diff --git a/drivers/hwmon/iio_hwmon.c b/drivers/hwmon/iio_hwmon.c
index e376d4cde5ad..bba8919377eb 100644
--- a/drivers/hwmon/iio_hwmon.c
+++ b/drivers/hwmon/iio_hwmon.c
@@ -44,6 +44,30 @@ static ssize_t iio_hwmon_read_label(struct device *dev,
return iio_read_channel_label(chan, buf);
}
+/**
+ * iio_hwmon_scale() - Look up the scaling factor for a channel
+ * @chan: Channel to get the scale of
+ *
+ * Determine the scale to use with @chan in the case where IIO and HWMON use
+ * different units.
+ *
+ * Return: scale of @chan
+ */
+static int iio_hwmon_scale(struct iio_channel *chan)
+{
+ enum iio_chan_type type;
+ int ret;
+
+ ret = iio_get_channel_type(chan, &type);
+ if (ret < 0)
+ return ret;
+
+ /* mili-Watts to micro-Watts conversion */
+ if (type == IIO_POWER)
+ return 1000;
+ return 1;
+}
+
/*
* Assumes that IIO and hwmon operate in the same base units.
* This is supposed to be true, but needs verification for
@@ -53,22 +77,16 @@ static ssize_t iio_hwmon_read_val(struct device *dev,
struct device_attribute *attr,
char *buf)
{
- int result;
- int ret;
struct sensor_device_attribute *sattr = to_sensor_dev_attr(attr);
struct iio_hwmon_state *state = dev_get_drvdata(dev);
struct iio_channel *chan = &state->channels[sattr->index];
- enum iio_chan_type type;
+ int ret, result, scale;
- ret = iio_get_channel_type(chan, &type);
- if (ret < 0)
- return ret;
+ scale = iio_hwmon_scale(chan);
+ if (scale < 0)
+ return scale;
- if (type == IIO_POWER)
- /* mili-Watts to micro-Watts conversion */
- ret = iio_read_channel_processed_scale(chan, &result, 1000);
- else
- ret = iio_read_channel_processed(chan, &result);
+ ret = iio_read_channel_processed_scale(chan, &result, scale);
if (ret < 0)
return ret;
--
2.35.1.1320.gc452695387.dirty
^ permalink raw reply related [flat|nested] 62+ messages in thread
* [PATCH 5/7] hwmon: iio: Add helper function for creating attributes
2025-07-15 1:20 [PATCH 0/7] hwmon: iio: Add alarm support Sean Anderson
` (3 preceding siblings ...)
2025-07-15 1:20 ` [PATCH 4/7] hwmon: iio: Refactor scale calculation into helper Sean Anderson
@ 2025-07-15 1:20 ` Sean Anderson
2025-07-15 8:38 ` Andy Shevchenko
2025-07-27 16:31 ` Jonathan Cameron
2025-07-15 1:20 ` [PATCH 6/7] hwmon: iio: Add min/max support Sean Anderson
2025-07-15 1:20 ` [PATCH 7/7] hwmon: iio: Add alarm support Sean Anderson
6 siblings, 2 replies; 62+ messages in thread
From: Sean Anderson @ 2025-07-15 1:20 UTC (permalink / raw)
To: Jonathan Cameron, Jean Delvare, Guenter Roeck, linux-iio,
linux-hwmon
Cc: Andy Shevchenko, Nuno Sá, linux-kernel, David Lechner,
Sean Anderson
Add a helper function to create attributes and initialize their fields.
This reduces repetition when creating several attributes per channel.
Signed-off-by: Sean Anderson <sean.anderson@linux.dev>
---
drivers/hwmon/iio_hwmon.c | 78 +++++++++++++++++++++------------------
1 file changed, 42 insertions(+), 36 deletions(-)
diff --git a/drivers/hwmon/iio_hwmon.c b/drivers/hwmon/iio_hwmon.c
index bba8919377eb..7dc156d2aea4 100644
--- a/drivers/hwmon/iio_hwmon.c
+++ b/drivers/hwmon/iio_hwmon.c
@@ -24,13 +24,15 @@
* @attr_group: the group of attributes
* @groups: null terminated array of attribute groups
* @attrs: null terminated array of attribute pointers.
+ * @num_attrs: length of @attrs
*/
struct iio_hwmon_state {
struct iio_channel *channels;
- int num_channels;
struct attribute_group attr_group;
const struct attribute_group *groups[2];
struct attribute **attrs;
+ size_t num_attrs;
+ int num_channels;
};
static ssize_t iio_hwmon_read_label(struct device *dev,
@@ -93,12 +95,39 @@ static ssize_t iio_hwmon_read_val(struct device *dev,
return sprintf(buf, "%d\n", result);
}
+static int add_device_attr(struct device *dev, struct iio_hwmon_state *st,
+ ssize_t (*show)(struct device *dev,
+ struct device_attribute *attr,
+ char *buf),
+ int i, const char *fmt, ...)
+{
+ struct sensor_device_attribute *a;
+ va_list ap;
+
+ a = devm_kzalloc(dev, sizeof(*a), GFP_KERNEL);
+ if (!a)
+ return -ENOMEM;
+
+ sysfs_attr_init(&a->dev_attr.attr);
+ va_start(ap, fmt);
+ a->dev_attr.attr.name = devm_kvasprintf(dev, GFP_KERNEL, fmt, ap);
+ va_end(ap);
+ if (!a->dev_attr.attr.name)
+ return -ENOMEM;
+
+ a->dev_attr.show = show;
+ a->dev_attr.attr.mode = 0444;
+ a->index = i;
+
+ st->attrs[st->num_attrs++] = &a->dev_attr.attr;
+ return 0;
+}
+
static int iio_hwmon_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
struct iio_hwmon_state *st;
- struct sensor_device_attribute *a;
- int ret, i, attr = 0;
+ int ret, i;
int in_i = 1, temp_i = 1, curr_i = 1, humidity_i = 1, power_i = 1;
enum iio_chan_type type;
struct iio_channel *channels;
@@ -136,11 +165,6 @@ static int iio_hwmon_probe(struct platform_device *pdev)
const char *prefix;
int n;
- a = devm_kzalloc(dev, sizeof(*a), GFP_KERNEL);
- if (a == NULL)
- return -ENOMEM;
-
- sysfs_attr_init(&a->dev_attr.attr);
ret = iio_get_channel_type(&st->channels[i], &type);
if (ret < 0)
return ret;
@@ -170,36 +194,18 @@ static int iio_hwmon_probe(struct platform_device *pdev)
return -EINVAL;
}
- a->dev_attr.attr.name = devm_kasprintf(dev, GFP_KERNEL,
- "%s%d_input",
- prefix, n);
- if (a->dev_attr.attr.name == NULL)
- return -ENOMEM;
-
- a->dev_attr.show = iio_hwmon_read_val;
- a->dev_attr.attr.mode = 0444;
- a->index = i;
- st->attrs[attr++] = &a->dev_attr.attr;
+ ret = add_device_attr(dev, st, iio_hwmon_read_val, i,
+ "%s%d_input", prefix, n);
+ if (ret)
+ return ret;
/* Let's see if we have a label... */
- if (iio_read_channel_label(&st->channels[i], buf) < 0)
- continue;
-
- a = devm_kzalloc(dev, sizeof(*a), GFP_KERNEL);
- if (a == NULL)
- return -ENOMEM;
-
- sysfs_attr_init(&a->dev_attr.attr);
- a->dev_attr.attr.name = devm_kasprintf(dev, GFP_KERNEL,
- "%s%d_label",
- prefix, n);
- if (!a->dev_attr.attr.name)
- return -ENOMEM;
-
- a->dev_attr.show = iio_hwmon_read_label;
- a->dev_attr.attr.mode = 0444;
- a->index = i;
- st->attrs[attr++] = &a->dev_attr.attr;
+ if (iio_read_channel_label(&st->channels[i], buf) >= 0) {
+ ret = add_device_attr(dev, st, iio_hwmon_read_label,
+ i, "%s%d_label", prefix, n);
+ if (ret)
+ return ret;
+ }
}
devm_free_pages(dev, (unsigned long)buf);
--
2.35.1.1320.gc452695387.dirty
^ permalink raw reply related [flat|nested] 62+ messages in thread
* [PATCH 6/7] hwmon: iio: Add min/max support
2025-07-15 1:20 [PATCH 0/7] hwmon: iio: Add alarm support Sean Anderson
` (4 preceding siblings ...)
2025-07-15 1:20 ` [PATCH 5/7] hwmon: iio: Add helper function for creating attributes Sean Anderson
@ 2025-07-15 1:20 ` Sean Anderson
2025-07-15 8:41 ` Andy Shevchenko
2025-07-27 16:35 ` Jonathan Cameron
2025-07-15 1:20 ` [PATCH 7/7] hwmon: iio: Add alarm support Sean Anderson
6 siblings, 2 replies; 62+ messages in thread
From: Sean Anderson @ 2025-07-15 1:20 UTC (permalink / raw)
To: Jonathan Cameron, Jean Delvare, Guenter Roeck, linux-iio,
linux-hwmon
Cc: Andy Shevchenko, Nuno Sá, linux-kernel, David Lechner,
Sean Anderson
Add support for minimum/maximum attributes. Like the _input attribute,
we just need to call into the IIO API.
Signed-off-by: Sean Anderson <sean.anderson@linux.dev>
---
drivers/hwmon/iio_hwmon.c | 94 ++++++++++++++++++++++++++++++++++++++-
1 file changed, 93 insertions(+), 1 deletion(-)
diff --git a/drivers/hwmon/iio_hwmon.c b/drivers/hwmon/iio_hwmon.c
index 7dc156d2aea4..3db4d4b30022 100644
--- a/drivers/hwmon/iio_hwmon.c
+++ b/drivers/hwmon/iio_hwmon.c
@@ -95,6 +95,54 @@ static ssize_t iio_hwmon_read_val(struct device *dev,
return sprintf(buf, "%d\n", result);
}
+static ssize_t iio_hwmon_read_event(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ struct sensor_device_attribute_2 *sattr = to_sensor_dev_attr_2(attr);
+ struct iio_hwmon_state *state = dev_get_drvdata(dev);
+ struct iio_channel *chan = &state->channels[sattr->index];
+ int ret, result, scale;
+
+ scale = iio_hwmon_scale(chan);
+ if (scale < 0)
+ return scale;
+
+ ret = iio_read_event_processed_scale(chan, IIO_EV_TYPE_THRESH,
+ sattr->nr, IIO_EV_INFO_VALUE,
+ &result, scale);
+ if (ret < 0)
+ return ret;
+
+ return sprintf(buf, "%d\n", result);
+}
+
+static ssize_t iio_hwmon_write_event(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct sensor_device_attribute_2 *sattr = to_sensor_dev_attr_2(attr);
+ struct iio_hwmon_state *state = dev_get_drvdata(dev);
+ struct iio_channel *chan = &state->channels[sattr->index];
+ int ret, scale, val;
+
+ ret = kstrtoint(buf, 0, &val);
+ if (ret)
+ return ret;
+
+ scale = iio_hwmon_scale(chan);
+ if (scale < 0)
+ return scale;
+
+ ret = iio_write_event_processed_scale(chan, IIO_EV_TYPE_THRESH,
+ sattr->nr, IIO_EV_INFO_VALUE,
+ val, scale);
+ if (ret < 0)
+ return ret;
+
+ return count;
+}
+
static int add_device_attr(struct device *dev, struct iio_hwmon_state *st,
ssize_t (*show)(struct device *dev,
struct device_attribute *attr,
@@ -123,6 +171,40 @@ static int add_device_attr(struct device *dev, struct iio_hwmon_state *st,
return 0;
}
+static int add_event_attr(struct device *dev, struct iio_hwmon_state *st,
+ int i, enum iio_event_direction dir,
+ const char *fmt, ...)
+{
+ struct sensor_device_attribute_2 *a;
+ umode_t mode;
+ va_list ap;
+
+ mode = iio_event_mode(&st->channels[i], IIO_EV_TYPE_THRESH, dir,
+ IIO_EV_INFO_VALUE);
+ if (!mode)
+ return 0;
+
+ a = devm_kzalloc(dev, sizeof(*a), GFP_KERNEL);
+ if (!a)
+ return -ENOMEM;
+
+ sysfs_attr_init(&a->dev_attr.attr);
+ va_start(ap, fmt);
+ a->dev_attr.attr.name = devm_kvasprintf(dev, GFP_KERNEL, fmt, ap);
+ va_end(ap);
+ if (!a->dev_attr.attr.name)
+ return -ENOMEM;
+
+ a->dev_attr.show = iio_hwmon_read_event;
+ a->dev_attr.store = iio_hwmon_write_event;
+ a->dev_attr.attr.mode = mode;
+ a->index = i;
+ a->nr = dir;
+
+ st->attrs[st->num_attrs++] = &a->dev_attr.attr;
+ return 0;
+}
+
static int iio_hwmon_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
@@ -156,7 +238,7 @@ static int iio_hwmon_probe(struct platform_device *pdev)
st->num_channels++;
st->attrs = devm_kcalloc(dev,
- 2 * st->num_channels + 1, sizeof(*st->attrs),
+ 4 * st->num_channels + 1, sizeof(*st->attrs),
GFP_KERNEL);
if (st->attrs == NULL)
return -ENOMEM;
@@ -206,6 +288,16 @@ static int iio_hwmon_probe(struct platform_device *pdev)
if (ret)
return ret;
}
+
+ ret = add_event_attr(dev, st, i, IIO_EV_DIR_FALLING,
+ "%s%d_min", prefix, n);
+ if (ret)
+ return ret;
+
+ ret = add_event_attr(dev, st, i, IIO_EV_DIR_RISING,
+ "%s%d_max", prefix, n);
+ if (ret)
+ return ret;
}
devm_free_pages(dev, (unsigned long)buf);
--
2.35.1.1320.gc452695387.dirty
^ permalink raw reply related [flat|nested] 62+ messages in thread
* [PATCH 7/7] hwmon: iio: Add alarm support
2025-07-15 1:20 [PATCH 0/7] hwmon: iio: Add alarm support Sean Anderson
` (5 preceding siblings ...)
2025-07-15 1:20 ` [PATCH 6/7] hwmon: iio: Add min/max support Sean Anderson
@ 2025-07-15 1:20 ` Sean Anderson
2025-07-15 8:50 ` Andy Shevchenko
` (5 more replies)
6 siblings, 6 replies; 62+ messages in thread
From: Sean Anderson @ 2025-07-15 1:20 UTC (permalink / raw)
To: Jonathan Cameron, Jean Delvare, Guenter Roeck, linux-iio,
linux-hwmon
Cc: Andy Shevchenko, Nuno Sá, linux-kernel, David Lechner,
Sean Anderson
Add alarm support based on IIO threshold events. The alarm is cleared on
read, but will be set again if the condition is still present. This is
detected by disabling and re-enabling the event. The same trick is done
when creating the attribute to detect already-triggered events.
The alarms are updated by an event listener. To keep the notifier call
chain short, we create one listener per iio device, shared across all
hwmon devices.
To avoid dynamic creation of alarms, alarms for all possible events are
allocated at creation. Lookup is done by a linear scan, as I expect
events to occur rarely. If performance becomes an issue, a binary search
could be done instead (or some kind of hash lookup).
Signed-off-by: Sean Anderson <sean.anderson@linux.dev>
---
drivers/hwmon/iio_hwmon.c | 322 +++++++++++++++++++++++++++++++++++++-
1 file changed, 321 insertions(+), 1 deletion(-)
diff --git a/drivers/hwmon/iio_hwmon.c b/drivers/hwmon/iio_hwmon.c
index 3db4d4b30022..c963bc5452ba 100644
--- a/drivers/hwmon/iio_hwmon.c
+++ b/drivers/hwmon/iio_hwmon.c
@@ -8,6 +8,7 @@
#include <linux/slab.h>
#include <linux/mod_devicetable.h>
#include <linux/module.h>
+#include <linux/notifier.h>
#include <linux/err.h>
#include <linux/platform_device.h>
#include <linux/property.h>
@@ -15,7 +16,192 @@
#include <linux/hwmon.h>
#include <linux/hwmon-sysfs.h>
#include <linux/iio/consumer.h>
+#include <linux/iio/events.h>
+#include <linux/iio/iio.h>
#include <linux/iio/types.h>
+#include <uapi/linux/iio/events.h>
+
+/* Protects iio_hwmon_listeners and listeners' refcnt */
+DEFINE_MUTEX(iio_hwmon_listener_lock);
+LIST_HEAD(iio_hwmon_listeners);
+
+/**
+ * struct iio_hwmon_listener - Listener for IIO events
+ * @block: Notifier for events
+ * @ids: Array of IIO event ids, one per alarm
+ * @alarms: Bitmap of alarms
+ * @num_alarms: Length of @ids and @alarms
+ * @indio_dev: Device we are listening to
+ * @list: List of all listeners
+ * @refcnt: Reference count
+ */
+struct iio_hwmon_listener {
+ struct notifier_block block;
+ u64 *ids;
+ unsigned long *alarms;
+ size_t num_alarms;
+
+ struct iio_dev *indio_dev;
+ struct list_head list;
+ unsigned int refcnt;
+};
+
+/**
+ * iio_hwmon_lookup_alarm() - Find an alarm by id
+ * @listener: Event listener
+ * @id: IIO event id
+ *
+ * Return: index of @id in @listener->ids, or -1 if not found
+ */
+static ssize_t iio_hwmon_lookup_alarm(struct iio_hwmon_listener *listener,
+ u64 id)
+{
+ ssize_t i;
+
+ for (i = 0; i < listener->num_alarms; i++)
+ if (listener->ids[i] == id)
+ return i;
+
+ return -1;
+}
+
+static int iio_hwmon_listener_callback(struct notifier_block *block,
+ unsigned long action, void *data)
+{
+ struct iio_hwmon_listener *listener =
+ container_of(block, struct iio_hwmon_listener, block);
+ struct iio_event_data *ev = data;
+ ssize_t i;
+
+ if (action != IIO_NOTIFY_EVENT)
+ return NOTIFY_DONE;
+
+ i = iio_hwmon_lookup_alarm(listener, ev->id);
+ if (i >= 0)
+ set_bit(i, listener->alarms);
+ else
+ dev_warn_once(&listener->indio_dev->dev,
+ "unknown event %016llx\n", ev->id);
+
+ return NOTIFY_DONE;
+}
+
+/**
+ * iio_event_id() - Calculate an IIO event id
+ * @channel: IIO channel for this event
+ * @type: Event type (theshold, rate-of-change, etc.)
+ * @dir: Event direction (rising, falling, etc.)
+ *
+ * Return: IIO event id corresponding to this event's IIO id
+ */
+static u64 iio_event_id(struct iio_chan_spec const *chan,
+ enum iio_event_type type,
+ enum iio_event_direction dir)
+{
+ if (chan->differential)
+ return IIO_DIFF_EVENT_CODE(chan->type, chan->channel,
+ chan->channel2, type, dir);
+ if (chan->modified)
+ return IIO_MOD_EVENT_CODE(chan->type, chan->channel,
+ chan->channel2, type, dir);
+ return IIO_UNMOD_EVENT_CODE(chan->type, chan->channel, type, dir);
+}
+
+/**
+ * iio_hwmon_listener_get() - Get a listener for an IIO device
+ * @indio_dev: IIO device to listen to
+ *
+ * Look up or create a new listener for @indio_dev. The returned listener is
+ * registered with @indio_dev, but events still need to be manually enabled.
+ * You must call iio_hwmon_listener_put() when you are done.
+ *
+ * Return: Listener for @indio_dev, or an error pointer
+ */
+static struct iio_hwmon_listener *iio_hwmon_listener_get(struct iio_dev *indio_dev)
+{
+ struct iio_hwmon_listener *listener;
+ int err = -ENOMEM;
+ size_t i, j;
+
+ guard(mutex)(&iio_hwmon_listener_lock);
+ list_for_each_entry(listener, &iio_hwmon_listeners, list) {
+ if (listener->indio_dev == indio_dev) {
+ if (likely(listener->refcnt != UINT_MAX))
+ listener->refcnt++;
+ return listener;
+ }
+ }
+
+ listener = kzalloc(sizeof(*listener), GFP_KERNEL);
+ if (!listener)
+ goto err_unlock;
+
+ listener->refcnt = 1;
+ listener->indio_dev = indio_dev;
+ listener->block.notifier_call = iio_hwmon_listener_callback;
+ for (i = 0; i < indio_dev->num_channels; i++)
+ listener->num_alarms += indio_dev->channels[i].num_event_specs;
+
+ listener->ids = kcalloc(listener->num_alarms, sizeof(*listener->ids),
+ GFP_KERNEL);
+ listener->alarms = bitmap_zalloc(listener->num_alarms, GFP_KERNEL);
+ if (!listener->ids || !listener->alarms)
+ goto err_listener;
+
+ i = 0;
+ for (j = 0; j < indio_dev->num_channels; j++) {
+ struct iio_chan_spec const *chan = &indio_dev->channels[j];
+ size_t k;
+
+ for (k = 0; k < chan->num_event_specs; k++)
+ listener->ids[i++] =
+ iio_event_id(chan, chan->event_spec[k].type,
+ chan->event_spec[k].dir);
+ }
+
+ err = iio_event_register(indio_dev, &listener->block);
+ if (err)
+ goto err_alarms;
+
+ list_add(&listener->list, &iio_hwmon_listeners);
+ mutex_unlock(&iio_hwmon_listener_lock);
+ return listener;
+
+err_alarms:
+ kfree(listener->alarms);
+ kfree(listener->ids);
+err_listener:
+ kfree(listener);
+err_unlock:
+ mutex_unlock(&iio_hwmon_listener_lock);
+ return ERR_PTR(err);
+}
+
+/**
+ * iio_hwmon_listener_put() - Release a listener
+ * @data: &struct iio_hwmon_listener to release
+ *
+ * For convenience, @data is void.
+ */
+static void iio_hwmon_listener_put(void *data)
+{
+ struct iio_hwmon_listener *listener = data;
+
+ scoped_guard(mutex, &iio_hwmon_listener_lock) {
+ if (unlikely(listener->refcnt == UINT_MAX))
+ return;
+
+ if (--listener->refcnt)
+ return;
+
+ list_del(&listener->list);
+ iio_event_unregister(listener->indio_dev, &listener->block);
+ }
+
+ kfree(listener->alarms);
+ kfree(listener->ids);
+ kfree(listener);
+}
/**
* struct iio_hwmon_state - device instance state
@@ -143,6 +329,68 @@ static ssize_t iio_hwmon_write_event(struct device *dev,
return count;
}
+/**
+ * struct iio_hwmon_alarm_attribute - IIO HWMON alarm attribute
+ * @dev_attr: Base device attribute
+ * @listener: Listener for this alarm
+ * @index: Index of the channel in the IIO HWMON
+ * @alarm: Index of the alarm within @listener
+ */
+struct iio_hwmon_alarm_attribute {
+ struct device_attribute dev_attr;
+ struct iio_hwmon_listener *listener;
+ size_t index;
+ size_t alarm;
+};
+#define to_alarm_attr(_dev_attr) \
+ container_of(_dev_attr, struct iio_hwmon_alarm_attribute, dev_attr)
+
+/**
+ * iio_hwmon_alarm_toggle() - Turn an event off and back on again
+ * @chan: Channel of the event
+ * @dir: Event direction (rising, falling, etc.)
+ *
+ * Toggle an event's enable so we get notified if the alarm is already
+ * triggered. We use this to convert IIO's event-triggered events into
+ * level-triggered alarms.
+ *
+ * Return: 0 on success or negative error on failure
+ */
+static int iio_hwmon_alarm_toggle(struct iio_channel *chan,
+ enum iio_event_direction dir)
+{
+ int ret;
+
+ ret = iio_write_event_processed_scale(chan, IIO_EV_TYPE_THRESH, dir,
+ IIO_EV_INFO_ENABLE, 0, 1);
+ if (ret)
+ return ret;
+
+ return iio_write_event_processed_scale(chan, IIO_EV_TYPE_THRESH, dir,
+ IIO_EV_INFO_ENABLE, 1, 1);
+}
+
+static ssize_t iio_hwmon_read_alarm(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ struct iio_hwmon_alarm_attribute *sattr = to_alarm_attr(attr);
+ struct iio_hwmon_state *state = dev_get_drvdata(dev);
+ struct iio_channel *chan = &state->channels[sattr->index];
+
+ if (test_and_clear_bit(sattr->alarm, sattr->listener->alarms)) {
+ u64 id = sattr->listener->ids[sattr->alarm];
+ enum iio_event_direction dir = IIO_EVENT_CODE_EXTRACT_DIR(id);
+
+ WARN_ON(iio_hwmon_alarm_toggle(chan, dir));
+ strcpy(buf, "1\n");
+ return 2;
+ }
+
+ strcpy(buf, "0\n");
+ return 2;
+}
+
static int add_device_attr(struct device *dev, struct iio_hwmon_state *st,
ssize_t (*show)(struct device *dev,
struct device_attribute *attr,
@@ -205,6 +453,63 @@ static int add_event_attr(struct device *dev, struct iio_hwmon_state *st,
return 0;
}
+static int add_alarm_attr(struct device *dev, struct iio_hwmon_state *st,
+ int i, enum iio_event_direction dir,
+ const char *fmt, ...)
+{
+ struct iio_hwmon_alarm_attribute *a;
+ struct iio_hwmon_listener *listener;
+ ssize_t alarm;
+ umode_t mode;
+ va_list ap;
+ int ret;
+
+ mode = iio_event_mode(&st->channels[i], IIO_EV_TYPE_THRESH, dir,
+ IIO_EV_INFO_ENABLE);
+ if (!(mode & 0200))
+ return 0;
+
+ listener = iio_hwmon_listener_get(st->channels[i].indio_dev);
+ if (listener == ERR_PTR(-EBUSY))
+ return 0;
+ if (IS_ERR(listener))
+ return PTR_ERR(listener);
+
+ ret = devm_add_action_or_reset(dev, iio_hwmon_listener_put, listener);
+ if (ret)
+ return ret;
+
+ alarm = iio_hwmon_lookup_alarm(listener,
+ iio_event_id(st->channels[i].channel,
+ IIO_EV_TYPE_THRESH, dir));
+ if (WARN_ON_ONCE(alarm < 0))
+ return -ENOENT;
+
+ ret = iio_hwmon_alarm_toggle(&st->channels[i], dir);
+ if (ret)
+ return ret;
+
+ a = devm_kzalloc(dev, sizeof(*a), GFP_KERNEL);
+ if (!a)
+ return -ENOMEM;
+
+ sysfs_attr_init(&a->dev_attr.attr);
+ va_start(ap, fmt);
+ a->dev_attr.attr.name = devm_kvasprintf(dev, GFP_KERNEL, fmt, ap);
+ va_end(ap);
+ if (!a->dev_attr.attr.name)
+ return -ENOMEM;
+
+ a->dev_attr.show = iio_hwmon_read_alarm;
+ a->dev_attr.attr.mode = 0400;
+ a->listener = listener;
+ a->index = i;
+ a->alarm = alarm;
+
+ st->attrs[st->num_attrs++] = &a->dev_attr.attr;
+ return 0;
+}
+
static int iio_hwmon_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
@@ -238,7 +543,7 @@ static int iio_hwmon_probe(struct platform_device *pdev)
st->num_channels++;
st->attrs = devm_kcalloc(dev,
- 4 * st->num_channels + 1, sizeof(*st->attrs),
+ 7 * st->num_channels + 1, sizeof(*st->attrs),
GFP_KERNEL);
if (st->attrs == NULL)
return -ENOMEM;
@@ -298,6 +603,21 @@ static int iio_hwmon_probe(struct platform_device *pdev)
"%s%d_max", prefix, n);
if (ret)
return ret;
+
+ ret = add_alarm_attr(dev, st, i, IIO_EV_DIR_RISING,
+ "%s%d_max_alarm", prefix, n);
+ if (ret)
+ return ret;
+
+ ret = add_alarm_attr(dev, st, i, IIO_EV_DIR_FALLING,
+ "%s%d_min_alarm", prefix, n);
+ if (ret)
+ return ret;
+
+ ret = add_alarm_attr(dev, st, i, IIO_EV_DIR_EITHER,
+ "%s%d_alarm", prefix, n);
+ if (ret)
+ return ret;
}
devm_free_pages(dev, (unsigned long)buf);
--
2.35.1.1320.gc452695387.dirty
^ permalink raw reply related [flat|nested] 62+ messages in thread
* Re: [PATCH 1/7] math64: Add div64_s64_rem
2025-07-15 1:20 ` [PATCH 1/7] math64: Add div64_s64_rem Sean Anderson
@ 2025-07-15 8:03 ` Andy Shevchenko
2025-07-15 17:36 ` Sean Anderson
0 siblings, 1 reply; 62+ messages in thread
From: Andy Shevchenko @ 2025-07-15 8:03 UTC (permalink / raw)
To: Sean Anderson
Cc: Jonathan Cameron, Jean Delvare, Guenter Roeck, linux-iio,
linux-hwmon, Andy Shevchenko, Nuno Sá, linux-kernel,
David Lechner
On Mon, Jul 14, 2025 at 09:20:17PM -0400, Sean Anderson wrote:
> Add a function to do signed 64-bit division with remainder. This is
> implemented using div64_u64_rem in the same way that div_s64_rem is
> implemented using div_u64_rem.
LGTM, but one important Q. Can we (start to) add the test cases, please?
--
With Best Regards,
Andy Shevchenko
^ permalink raw reply [flat|nested] 62+ messages in thread
* Re: [PATCH 2/7] iio: inkern: Add API for reading/writing events
2025-07-15 1:20 ` [PATCH 2/7] iio: inkern: Add API for reading/writing events Sean Anderson
@ 2025-07-15 8:18 ` Andy Shevchenko
2025-07-15 15:42 ` Sean Anderson
2025-07-15 10:35 ` Nuno Sá
2025-07-27 16:13 ` Jonathan Cameron
2 siblings, 1 reply; 62+ messages in thread
From: Andy Shevchenko @ 2025-07-15 8:18 UTC (permalink / raw)
To: Sean Anderson
Cc: Jonathan Cameron, Jean Delvare, Guenter Roeck, linux-iio,
linux-hwmon, Andy Shevchenko, Nuno Sá, linux-kernel,
David Lechner
On Mon, Jul 14, 2025 at 09:20:18PM -0400, Sean Anderson wrote:
> Add an in-kernel API for reading/writing event properties. Like the
> raw-to-processed conversion, with processed-to-raw we only convert the
> integer part, introducing some round-off error.
>
> A common case is for other drivers to re-expose IIO events as sysfs
> properties with a different API. To help out with this, iio_event_mode
> returns the appropriate mode. It can also be used to test for existence
> if the consumer doesn't care about read/write capability.
...
> +EXPORT_SYMBOL_GPL(iio_event_mode);
Can we move this to namespace? Otherwise it will be never ending story...
Ditto for other new APIs.
...
> + if (scale64 <= INT_MAX && scale64 >= INT_MIN)
> + raw64 = processed / (int)scale64;
Do you need the casting? (I mean if the compiler is dumb enough to not see this)
...
> + case IIO_VAL_INT_PLUS_MICRO:
> + scale64 = scale_val * scale * 1000000LL + scale_val2;
> + raw64 = div64_s64_rem(processed, scale64, &r);
> + raw64 = raw64 * 1000000 +
> + div64_s64(r * 1000000, scale64);
Logically this should be 1000000L, but can we somehow use the constants?
Like
scale64 = (s64)MICRO * scale_val * scale + scale_val2;
raw64 = div64_s64_rem(processed, scale64, &r);
raw64 = raw64 * (s32)MICRO +
div64_s64(r * (s64)MICRO, scale64);
> + break;
Ditto for other cases?
...
> + *raw = clamp(raw64, (s64)INT_MIN, (s64)INT_MAX);
You already have similar approach here...
...
> + ret = iio_convert_processed_to_raw_unlocked(chan, processed, &raw,
> + scale);
> + if (ret < 0)
Why ' < 0' ?
> + return ret;
--
With Best Regards,
Andy Shevchenko
^ permalink raw reply [flat|nested] 62+ messages in thread
* Re: [PATCH 3/7] iio: Add in-kernel API for events
2025-07-15 1:20 ` [PATCH 3/7] iio: Add in-kernel API for events Sean Anderson
@ 2025-07-15 8:20 ` Andy Shevchenko
2025-07-15 15:47 ` Sean Anderson
2025-07-15 11:09 ` Nuno Sá
2025-07-27 16:24 ` Jonathan Cameron
2 siblings, 1 reply; 62+ messages in thread
From: Andy Shevchenko @ 2025-07-15 8:20 UTC (permalink / raw)
To: Sean Anderson
Cc: Jonathan Cameron, Jean Delvare, Guenter Roeck, linux-iio,
linux-hwmon, Andy Shevchenko, Nuno Sá, linux-kernel,
David Lechner
On Mon, Jul 14, 2025 at 09:20:19PM -0400, Sean Anderson wrote:
> Add an API to notify consumers about events. Events still need to be
> enabled using the iio_read_event/iio_write_event functions. Of course,
> userspace can also manipulate the enabled events. I don't think this is
> too much of an issue, since userspace can also manipulate the event
> thresholds. But enabling events may cause existing programs to be
> surprised when they get something unexpected. Maybe we should set the
> interface as busy when there are any in-kernel listeners?
...
> #include <linux/wait.h>
While at it...
+ blank line here...
> +#include <linux/iio/consumer.h>
> #include <linux/iio/iio.h>
> #include <linux/iio/iio-opaque.h>
...and here?
> #include "iio_core.h"
...
> + struct iio_event_data ev = {
> + .id = ev_code,
> + .timestamp = timestamp,
> + };
...
> /* Does anyone care? */
> if (iio_event_enabled(ev_int)) {
> -
> - ev.id = ev_code;
> - ev.timestamp = timestamp;
> -
> copied = kfifo_put(&ev_int->det_events, ev);
> if (copied != 0)
> wake_up_poll(&ev_int->wait, EPOLLIN);
Looks like this refactoring can be done before main change.
...
> + WARN_ON(atomic_notifier_chain_unregister(&ev_int->notifier, block));
Is bug.h already included?
--
With Best Regards,
Andy Shevchenko
^ permalink raw reply [flat|nested] 62+ messages in thread
* Re: [PATCH 4/7] hwmon: iio: Refactor scale calculation into helper
2025-07-15 1:20 ` [PATCH 4/7] hwmon: iio: Refactor scale calculation into helper Sean Anderson
@ 2025-07-15 8:35 ` Andy Shevchenko
0 siblings, 0 replies; 62+ messages in thread
From: Andy Shevchenko @ 2025-07-15 8:35 UTC (permalink / raw)
To: Sean Anderson
Cc: Jonathan Cameron, Jean Delvare, Guenter Roeck, linux-iio,
linux-hwmon, Andy Shevchenko, Nuno Sá, linux-kernel,
David Lechner
On Mon, Jul 14, 2025 at 09:20:20PM -0400, Sean Anderson wrote:
> Add a function to determine the scale parameter, since it will soon be
> used in several places.
...
> + * Return: scale of @chan
Needs a bit of elaboration. Can it be negative? What is the unit of scale
(just plain integer multiplier)? Should it be (always) signed?
...
> + /* mili-Watts to micro-Watts conversion */
> + if (type == IIO_POWER)
> + return 1000;
MICROWATT_PER_MILLIWATT ?
--
With Best Regards,
Andy Shevchenko
^ permalink raw reply [flat|nested] 62+ messages in thread
* Re: [PATCH 5/7] hwmon: iio: Add helper function for creating attributes
2025-07-15 1:20 ` [PATCH 5/7] hwmon: iio: Add helper function for creating attributes Sean Anderson
@ 2025-07-15 8:38 ` Andy Shevchenko
2025-07-15 15:55 ` Sean Anderson
2025-07-27 16:31 ` Jonathan Cameron
1 sibling, 1 reply; 62+ messages in thread
From: Andy Shevchenko @ 2025-07-15 8:38 UTC (permalink / raw)
To: Sean Anderson
Cc: Jonathan Cameron, Jean Delvare, Guenter Roeck, linux-iio,
linux-hwmon, Andy Shevchenko, Nuno Sá, linux-kernel,
David Lechner
On Mon, Jul 14, 2025 at 09:20:21PM -0400, Sean Anderson wrote:
> Add a helper function to create attributes and initialize their fields.
> This reduces repetition when creating several attributes per channel.
...
> + * @num_attrs: length of @attrs
Other lines use TABs.
...
> +static int add_device_attr(struct device *dev, struct iio_hwmon_state *st,
This should hint that this is managed:
add_device_managed_attr()
?
> + ssize_t (*show)(struct device *dev,
> + struct device_attribute *attr,
> + char *buf),
> + int i, const char *fmt, ...)
__printf() attribute is missing.
> +{
> + struct sensor_device_attribute *a;
> + va_list ap;
> +
> + a = devm_kzalloc(dev, sizeof(*a), GFP_KERNEL);
> + if (!a)
> + return -ENOMEM;
> +
> + sysfs_attr_init(&a->dev_attr.attr);
> + va_start(ap, fmt);
> + a->dev_attr.attr.name = devm_kvasprintf(dev, GFP_KERNEL, fmt, ap);
> + va_end(ap);
> + if (!a->dev_attr.attr.name)
> + return -ENOMEM;
> +
> + a->dev_attr.show = show;
> + a->dev_attr.attr.mode = 0444;
> + a->index = i;
> +
> + st->attrs[st->num_attrs++] = &a->dev_attr.attr;
> + return 0;
> +}
...
> struct device *dev = &pdev->dev;
> struct iio_hwmon_state *st;
> - struct sensor_device_attribute *a;
> - int ret, i, attr = 0;
> + int ret, i;
Also move it a bit to make it more of a reversed xmas tree ordering?
> int in_i = 1, temp_i = 1, curr_i = 1, humidity_i = 1, power_i = 1;
> enum iio_chan_type type;
> struct iio_channel *channels;
--
With Best Regards,
Andy Shevchenko
^ permalink raw reply [flat|nested] 62+ messages in thread
* Re: [PATCH 6/7] hwmon: iio: Add min/max support
2025-07-15 1:20 ` [PATCH 6/7] hwmon: iio: Add min/max support Sean Anderson
@ 2025-07-15 8:41 ` Andy Shevchenko
2025-07-15 16:05 ` Sean Anderson
2025-07-27 16:35 ` Jonathan Cameron
1 sibling, 1 reply; 62+ messages in thread
From: Andy Shevchenko @ 2025-07-15 8:41 UTC (permalink / raw)
To: Sean Anderson
Cc: Jonathan Cameron, Jean Delvare, Guenter Roeck, linux-iio,
linux-hwmon, Andy Shevchenko, Nuno Sá, linux-kernel,
David Lechner
On Mon, Jul 14, 2025 at 09:20:22PM -0400, Sean Anderson wrote:
> Add support for minimum/maximum attributes. Like the _input attribute,
> we just need to call into the IIO API.
...
> +static ssize_t iio_hwmon_read_event(struct device *dev,
> + struct device_attribute *attr,
> + char *buf)
> +{
> + struct sensor_device_attribute_2 *sattr = to_sensor_dev_attr_2(attr);
> + struct iio_hwmon_state *state = dev_get_drvdata(dev);
> + struct iio_channel *chan = &state->channels[sattr->index];
> + int ret, result, scale;
> +
> + scale = iio_hwmon_scale(chan);
> + if (scale < 0)
This part is definitely missed in the respective description.
> + return scale;
> +
> + ret = iio_read_event_processed_scale(chan, IIO_EV_TYPE_THRESH,
> + sattr->nr, IIO_EV_INFO_VALUE,
> + &result, scale);
> + if (ret < 0)
Why ' < 0' here?
> + return ret;
> +
> + return sprintf(buf, "%d\n", result);
Mustn't be sysfs_emit() ?
> +}
...
> + ret = iio_write_event_processed_scale(chan, IIO_EV_TYPE_THRESH,
> + sattr->nr, IIO_EV_INFO_VALUE,
> + val, scale);
> + if (ret < 0)
< 0 ?
> + return ret;
...
> +static int add_event_attr(struct device *dev, struct iio_hwmon_state *st,
> + int i, enum iio_event_direction dir,
> + const char *fmt, ...)
Same comments as per previous patch adding another attribute API.
...
> + va_start(ap, fmt);
> + a->dev_attr.attr.name = devm_kvasprintf(dev, GFP_KERNEL, fmt, ap);
> + va_end(ap);
Can't %pV be used?
--
With Best Regards,
Andy Shevchenko
^ permalink raw reply [flat|nested] 62+ messages in thread
* Re: [PATCH 7/7] hwmon: iio: Add alarm support
2025-07-15 1:20 ` [PATCH 7/7] hwmon: iio: Add alarm support Sean Anderson
@ 2025-07-15 8:50 ` Andy Shevchenko
2025-07-15 16:20 ` Sean Anderson
2025-07-15 11:28 ` Nuno Sá
` (4 subsequent siblings)
5 siblings, 1 reply; 62+ messages in thread
From: Andy Shevchenko @ 2025-07-15 8:50 UTC (permalink / raw)
To: Sean Anderson
Cc: Jonathan Cameron, Jean Delvare, Guenter Roeck, linux-iio,
linux-hwmon, Andy Shevchenko, Nuno Sá, linux-kernel,
David Lechner
On Mon, Jul 14, 2025 at 09:20:23PM -0400, Sean Anderson wrote:
> Add alarm support based on IIO threshold events. The alarm is cleared on
> read, but will be set again if the condition is still present. This is
> detected by disabling and re-enabling the event. The same trick is done
> when creating the attribute to detect already-triggered events.
>
> The alarms are updated by an event listener. To keep the notifier call
> chain short, we create one listener per iio device, shared across all
> hwmon devices.
>
> To avoid dynamic creation of alarms, alarms for all possible events are
> allocated at creation. Lookup is done by a linear scan, as I expect
> events to occur rarely. If performance becomes an issue, a binary search
> could be done instead (or some kind of hash lookup).
...
> #include <linux/hwmon-sysfs.h>
+ blank line here..
> #include <linux/iio/consumer.h>
> +#include <linux/iio/events.h>
> +#include <linux/iio/iio.h>
> #include <linux/iio/types.h>
...and here?
> +#include <uapi/linux/iio/events.h>
...
> +static ssize_t iio_hwmon_lookup_alarm(struct iio_hwmon_listener *listener,
> + u64 id)
> +{
> + ssize_t i;
> +
> + for (i = 0; i < listener->num_alarms; i++)
> + if (listener->ids[i] == id)
> + return i;
> + return -1;
-ENOENT ?
This will allow to propagate an error code to the upper layer(s).
> +}
...
> +static int iio_hwmon_listener_callback(struct notifier_block *block,
> + unsigned long action, void *data)
> +{
> + struct iio_hwmon_listener *listener =
> + container_of(block, struct iio_hwmon_listener, block);
> + struct iio_event_data *ev = data;
> + ssize_t i;
> +
> + if (action != IIO_NOTIFY_EVENT)
> + return NOTIFY_DONE;
> +
> + i = iio_hwmon_lookup_alarm(listener, ev->id);
> + if (i >= 0)
> + set_bit(i, listener->alarms);
Do you need an atomic set?
> + else
> + dev_warn_once(&listener->indio_dev->dev,
> + "unknown event %016llx\n", ev->id);
> +
> + return NOTIFY_DONE;
> +}
...
> +static struct iio_hwmon_listener *iio_hwmon_listener_get(struct iio_dev *indio_dev)
> +{
> + struct iio_hwmon_listener *listener;
> + int err = -ENOMEM;
> + size_t i, j;
> +
> + guard(mutex)(&iio_hwmon_listener_lock);
> + list_for_each_entry(listener, &iio_hwmon_listeners, list) {
> + if (listener->indio_dev == indio_dev) {
> + if (likely(listener->refcnt != UINT_MAX))
> + listener->refcnt++;
> + return listener;
> + }
> + }
> +
> + listener = kzalloc(sizeof(*listener), GFP_KERNEL);
> + if (!listener)
> + goto err_unlock;
> +
> + listener->refcnt = 1;
> + listener->indio_dev = indio_dev;
> + listener->block.notifier_call = iio_hwmon_listener_callback;
> + for (i = 0; i < indio_dev->num_channels; i++)
> + listener->num_alarms += indio_dev->channels[i].num_event_specs;
> +
> + listener->ids = kcalloc(listener->num_alarms, sizeof(*listener->ids),
> + GFP_KERNEL);
> + listener->alarms = bitmap_zalloc(listener->num_alarms, GFP_KERNEL);
> + if (!listener->ids || !listener->alarms)
> + goto err_listener;
> +
> + i = 0;
> + for (j = 0; j < indio_dev->num_channels; j++) {
> + struct iio_chan_spec const *chan = &indio_dev->channels[j];
> + size_t k;
> +
> + for (k = 0; k < chan->num_event_specs; k++)
> + listener->ids[i++] =
> + iio_event_id(chan, chan->event_spec[k].type,
> + chan->event_spec[k].dir);
> + }
> +
> + err = iio_event_register(indio_dev, &listener->block);
> + if (err)
> + goto err_alarms;
> +
> + list_add(&listener->list, &iio_hwmon_listeners);
> + mutex_unlock(&iio_hwmon_listener_lock);
With guard() ???
> + return listener;
> +
> +err_alarms:
> + kfree(listener->alarms);
> + kfree(listener->ids);
> +err_listener:
> + kfree(listener);
> +err_unlock:
> + mutex_unlock(&iio_hwmon_listener_lock);
> + return ERR_PTR(err);
What about using __free()?
> +}
...
> +static void iio_hwmon_listener_put(void *data)
> +{
> + struct iio_hwmon_listener *listener = data;
> +
> + scoped_guard(mutex, &iio_hwmon_listener_lock) {
> + if (unlikely(listener->refcnt == UINT_MAX))
> + return;
> +
> + if (--listener->refcnt)
> + return;
Can the refcount_t be used with the respective APIs? Or even kref?
> + list_del(&listener->list);
> + iio_event_unregister(listener->indio_dev, &listener->block);
> + }
> +
> + kfree(listener->alarms);
> + kfree(listener->ids);
> + kfree(listener);
> +}
...
> +static ssize_t iio_hwmon_read_alarm(struct device *dev,
> + struct device_attribute *attr,
> + char *buf)
> +{
> + struct iio_hwmon_alarm_attribute *sattr = to_alarm_attr(attr);
> + struct iio_hwmon_state *state = dev_get_drvdata(dev);
> + struct iio_channel *chan = &state->channels[sattr->index];
> +
> + if (test_and_clear_bit(sattr->alarm, sattr->listener->alarms)) {
> + u64 id = sattr->listener->ids[sattr->alarm];
> + enum iio_event_direction dir = IIO_EVENT_CODE_EXTRACT_DIR(id);
> +
> + WARN_ON(iio_hwmon_alarm_toggle(chan, dir));
> + strcpy(buf, "1\n");
> + return 2;
> + }
> +
> + strcpy(buf, "0\n");
> + return 2;
Better to assign the value and
return sysfs_emit(...);
which will make even easier to recognize that this is supplied to user via
sysfs.
> +}
...
> +static int add_alarm_attr(struct device *dev, struct iio_hwmon_state *st,
> + int i, enum iio_event_direction dir,
> + const char *fmt, ...)
Same comments as per previous patches.
--
With Best Regards,
Andy Shevchenko
^ permalink raw reply [flat|nested] 62+ messages in thread
* Re: [PATCH 2/7] iio: inkern: Add API for reading/writing events
2025-07-15 1:20 ` [PATCH 2/7] iio: inkern: Add API for reading/writing events Sean Anderson
2025-07-15 8:18 ` Andy Shevchenko
@ 2025-07-15 10:35 ` Nuno Sá
2025-07-15 15:43 ` Sean Anderson
2025-07-27 16:13 ` Jonathan Cameron
2 siblings, 1 reply; 62+ messages in thread
From: Nuno Sá @ 2025-07-15 10:35 UTC (permalink / raw)
To: Sean Anderson, Jonathan Cameron, Jean Delvare, Guenter Roeck,
linux-iio, linux-hwmon
Cc: Andy Shevchenko, Nuno Sá, linux-kernel, David Lechner
On Mon, 2025-07-14 at 21:20 -0400, Sean Anderson wrote:
> Add an in-kernel API for reading/writing event properties. Like the
> raw-to-processed conversion, with processed-to-raw we only convert the
> integer part, introducing some round-off error.
>
> A common case is for other drivers to re-expose IIO events as sysfs
> properties with a different API. To help out with this, iio_event_mode
> returns the appropriate mode. It can also be used to test for existence
> if the consumer doesn't care about read/write capability.
>
> Signed-off-by: Sean Anderson <sean.anderson@linux.dev>
> ---
Just one comment on top of Andy's review
>
> drivers/iio/inkern.c | 198 +++++++++++++++++++++++++++++++++++
> include/linux/iio/consumer.h | 56 ++++++++++
> 2 files changed, 254 insertions(+)
>
> diff --git a/drivers/iio/inkern.c b/drivers/iio/inkern.c
> index c174ebb7d5e6..d3bbd2444fb5 100644
> --- a/drivers/iio/inkern.c
> +++ b/drivers/iio/inkern.c
> @@ -1028,3 +1028,201 @@ ssize_t iio_read_channel_label(struct iio_channel
> *chan, char *buf)
> return do_iio_read_channel_label(chan->indio_dev, chan->channel,
> buf);
> }
> EXPORT_SYMBOL_GPL(iio_read_channel_label);
> +
> +static bool iio_event_exists(struct iio_channel *channel,
> + enum iio_event_type type,
> + enum iio_event_direction dir,
> + enum iio_event_info info)
> +{
> + struct iio_chan_spec const *chan = channel->channel;
> + int i;
> +
Can we iio_event_exists() -> iio_event_exists_locked()? Or likely the best way
would be to annotate it with lockdep (though that would mean some dance to get
the opaque object.
Anyways, bottom line is it should clear that iio_event_exists() is to be called
with the lock held.
- Nuno Sá
> + if (!channel->indio_dev->info)
> + return false;
> +
> + for (i = 0; i < chan->num_event_specs; i++) {
> + if (chan->event_spec[i].type != type)
> + continue;
> + if (chan->event_spec[i].dir != dir)
> + continue;
> + if (chan->event_spec[i].mask_separate & BIT(info))
> + return true;
> + }
> +
> + return false;
> +}
> +
> +umode_t iio_event_mode(struct iio_channel *chan, enum iio_event_type type,
> + enum iio_event_direction dir, enum iio_event_info
> info)
> +{
> + struct iio_dev *indio_dev = chan->indio_dev;
> + struct iio_dev_opaque *iio_dev_opaque = to_iio_dev_opaque(indio_dev);
> + umode_t mode = 0;
> +
> + guard(mutex)(&iio_dev_opaque->info_exist_lock);
> + if (!iio_event_exists(chan, type, dir, info))
> + return 0;
> +
> + if (info == IIO_EV_INFO_ENABLE) {
> + if (indio_dev->info->read_event_config)
> + mode |= 0444;
> +
> + if (indio_dev->info->write_event_config)
> + mode |= 0200;
> + } else {
> + if (indio_dev->info->read_event_value)
> + mode |= 0444;
> +
> + if (indio_dev->info->write_event_value)
> + mode |= 0200;
> + }
> +
> + return mode;
> +}
> +EXPORT_SYMBOL_GPL(iio_event_mode);
> +
> +int iio_read_event_processed_scale(struct iio_channel *chan,
> + enum iio_event_type type,
> + enum iio_event_direction dir,
> + enum iio_event_info info, int *val,
> + unsigned int scale)
> +{
> + struct iio_dev *indio_dev = chan->indio_dev;
> + struct iio_dev_opaque *iio_dev_opaque = to_iio_dev_opaque(indio_dev);
> + int ret, raw;
> +
> + guard(mutex)(&iio_dev_opaque->info_exist_lock);
> + if (!iio_event_exists(chan, type, dir, info))
> + return -ENODEV;
> +
> + if (info == IIO_EV_INFO_ENABLE) {
> + if (!indio_dev->info->read_event_config)
> + return -EINVAL;
> +
> + raw = indio_dev->info->read_event_config(indio_dev,
> + chan->channel, type,
> + dir);
> + if (raw < 0)
> + return raw;
> +
> + *val = raw;
> + return 0;
> + }
> +
> + if (!indio_dev->info->read_event_value)
> + return -EINVAL;
> +
> + ret = indio_dev->info->read_event_value(indio_dev, chan->channel,
> type,
> + dir, info, &raw, NULL);
> + if (ret < 0)
> + return ret;
> +
> + return iio_convert_raw_to_processed_unlocked(chan, raw, val, scale);
> +}
> +EXPORT_SYMBOL_GPL(iio_read_event_processed_scale);
> +
> +static int iio_convert_processed_to_raw_unlocked(struct iio_channel *chan,
> + int processed, int *raw,
> + unsigned int scale)
> +{
> + int scale_type, scale_val, scale_val2;
> + int offset_type, offset_val, offset_val2;
> + s64 r, scale64, raw64;
> +
> + scale_type = iio_channel_read(chan, &scale_val, &scale_val2,
> + IIO_CHAN_INFO_SCALE);
> + if (scale_type < 0) {
> + raw64 = processed / scale;
> + } else {
> + switch (scale_type) {
> + case IIO_VAL_INT:
> + scale64 = (s64)scale_val * scale;
> + if (scale64 <= INT_MAX && scale64 >= INT_MIN)
> + raw64 = processed / (int)scale64;
> + else
> + raw64 = 0;
> + break;
> + case IIO_VAL_INT_PLUS_MICRO:
> + scale64 = scale_val * scale * 1000000LL + scale_val2;
> + raw64 = div64_s64_rem(processed, scale64, &r);
> + raw64 = raw64 * 1000000 +
> + div64_s64(r * 1000000, scale64);
> + break;
> + case IIO_VAL_INT_PLUS_NANO:
> + scale64 = scale_val * scale * 1000000000LL +
> scale_val2;
> + raw64 = div64_s64_rem(processed, scale64, &r);
> + raw64 = raw64 * 1000000000 +
> + div64_s64(r * 1000000000, scale64);
> + break;
> + case IIO_VAL_FRACTIONAL:
> + raw64 = div64_s64((s64)processed * scale_val2,
> + (s64)scale_val * scale);
> + break;
> + case IIO_VAL_FRACTIONAL_LOG2:
> + raw64 = div64_s64((s64)processed << scale_val2,
> + (s64)scale_val * scale);
> + break;
> + default:
> + return -EINVAL;
> + }
> + }
> +
> + offset_type = iio_channel_read(chan, &offset_val, &offset_val2,
> + IIO_CHAN_INFO_OFFSET);
> + if (offset_type >= 0) {
> + switch (offset_type) {
> + case IIO_VAL_INT:
> + case IIO_VAL_INT_PLUS_MICRO:
> + case IIO_VAL_INT_PLUS_NANO:
> + raw64 -= offset_val;
> + break;
> + case IIO_VAL_FRACTIONAL:
> + raw64 -= offset_val / offset_val2;
> + break;
> + case IIO_VAL_FRACTIONAL_LOG2:
> + raw64 -= offset_val >> offset_val2;
> + break;
> + default:
> + return -EINVAL;
> + }
> + }
> +
> + *raw = clamp(raw64, (s64)INT_MIN, (s64)INT_MAX);
> + return 0;
> +}
> +
> +int iio_write_event_processed_scale(struct iio_channel *chan,
> + enum iio_event_type type,
> + enum iio_event_direction dir,
> + enum iio_event_info info, int processed,
> + unsigned int scale)
> +{
> + struct iio_dev *indio_dev = chan->indio_dev;
> + struct iio_dev_opaque *iio_dev_opaque = to_iio_dev_opaque(chan-
> >indio_dev);
> + int ret, raw;
> +
> + guard(mutex)(&iio_dev_opaque->info_exist_lock);
> + if (!iio_event_exists(chan, type, dir, info))
> + return -ENODEV;
> +
> + if (info == IIO_EV_INFO_ENABLE) {
> + if (!indio_dev->info->write_event_config)
> + return -EINVAL;
> +
> + return indio_dev->info->write_event_config(indio_dev,
> + chan->channel,
> type,
> + dir, processed);
> + }
> +
> + if (!indio_dev->info->write_event_value)
> + return -EINVAL;
> +
> + ret = iio_convert_processed_to_raw_unlocked(chan, processed, &raw,
> + scale);
> + if (ret < 0)
> + return ret;
> +
> + return indio_dev->info->write_event_value(indio_dev, chan->channel,
> + type, dir, info, raw, 0);
> +}
> +EXPORT_SYMBOL_GPL(iio_write_event_processed_scale);
> diff --git a/include/linux/iio/consumer.h b/include/linux/iio/consumer.h
> index 6a4479616479..16e7682474f3 100644
> --- a/include/linux/iio/consumer.h
> +++ b/include/linux/iio/consumer.h
> @@ -451,4 +451,60 @@ ssize_t iio_write_channel_ext_info(struct iio_channel
> *chan, const char *attr,
> */
> ssize_t iio_read_channel_label(struct iio_channel *chan, char *buf);
>
> +/**
> + * iio_event_mode() - get file mode for an event property
> + * @chan: Channel being queried
> + * @type: Event type (theshold, rate-of-change, etc.)
> + * @dir: Event direction (rising, falling, etc.)
> + * @info: Event property (enable, value, etc.)
> + *
> + * Determine an appropriate mode for sysfs files derived from this event.
> + *
> + * Return:
> + * - `0000` if the event is unsupported or otherwise unavailable
> + * - `0444` if the event is read-only
> + * - `0200` if the event is write-only
> + * - `0644` if the event is read-write
> + */
> +umode_t iio_event_mode(struct iio_channel *chan, enum iio_event_type type,
> + enum iio_event_direction dir, enum iio_event_info
> info);
> +
> +/**
> + * iio_read_event_processed_scale() - Read an event property
> + * @chan: Channel being queried
> + * @type: Event type (theshold, rate-of-change, etc.)
> + * @dir: Event direction (rising, falling, etc.)
> + * @info: Event property (enable, value, etc.)
> + * @val: Processed property value
> + * @scale: Factor to scale @val by
> + *
> + * Read a processed (scaled and offset) event property of a given channel.
> + *
> + * Return: 0 on success, or negative error on failure
> + */
> +int iio_read_event_processed_scale(struct iio_channel *chan,
> + enum iio_event_type type,
> + enum iio_event_direction dir,
> + enum iio_event_info info, int *val,
> + unsigned int scale);
> +
> +/**
> + * iio_write_event_processed_scale() - Read an event property
> + * @chan: Channel being queried
> + * @type: Event type (theshold, rate-of-change, etc.)
> + * @dir: Event direction (rising, falling, etc.)
> + * @info: Event property (enable, value, etc.)
> + * @processed: Processed property value
> + * @scale: Factor to scale @processed by
> + *
> + * Write a processed (scaled and offset) event property of a given channel.
> + *
> + * Return: 0 on success, or negative error on failure
> + */
> +int iio_write_event_processed_scale(struct iio_channel *chan,
> + enum iio_event_type type,
> + enum iio_event_direction dir,
> + enum iio_event_info info, int processed,
> + unsigned int scale);
> +
> #endif
^ permalink raw reply [flat|nested] 62+ messages in thread
* Re: [PATCH 3/7] iio: Add in-kernel API for events
2025-07-15 1:20 ` [PATCH 3/7] iio: Add in-kernel API for events Sean Anderson
2025-07-15 8:20 ` Andy Shevchenko
@ 2025-07-15 11:09 ` Nuno Sá
2025-07-15 16:52 ` Sean Anderson
2025-07-27 16:24 ` Jonathan Cameron
2 siblings, 1 reply; 62+ messages in thread
From: Nuno Sá @ 2025-07-15 11:09 UTC (permalink / raw)
To: Sean Anderson, Jonathan Cameron, Jean Delvare, Guenter Roeck,
linux-iio, linux-hwmon
Cc: Andy Shevchenko, Nuno Sá, linux-kernel, David Lechner
On Mon, 2025-07-14 at 21:20 -0400, Sean Anderson wrote:
> Add an API to notify consumers about events. Events still need to be
> enabled using the iio_read_event/iio_write_event functions. Of course,
> userspace can also manipulate the enabled events. I don't think this is
> too much of an issue, since userspace can also manipulate the event
> thresholds. But enabling events may cause existing programs to be
> surprised when they get something unexpected. Maybe we should set the
> interface as busy when there are any in-kernel listeners?
>
Sensible question. I'm not that familiar with events but I suspect is not
trivial (if doable) to do a similar approach as with buffers? With buffers, an
inkernal consumer get's it's own buffer object (that goes into a list of active
buffers in the iio device) with all channels enabled and then we demux the
appropriate channels for each consumer.
Independent of the above, we can argue that having both inkernel and userspace
changing thresholds is ok (I mean, there's nothing stopping two userspace apps
doing that) but we should likely be careful with enabling/disabling. If multiple
consumers enable the same event, one of them disabling it should not disable it
for all the consumers, right?
- Nuno Sá
> Signed-off-by: Sean Anderson <sean.anderson@linux.dev>
> ---
>
> drivers/iio/industrialio-event.c | 34 +++++++++++++++++++++++++++-----
> include/linux/iio/consumer.h | 30 ++++++++++++++++++++++++++++
> 2 files changed, 59 insertions(+), 5 deletions(-)
>
> diff --git a/drivers/iio/industrialio-event.c b/drivers/iio/industrialio-
> event.c
> index 06295cfc2da8..b9e3ff1cd5c9 100644
> --- a/drivers/iio/industrialio-event.c
> +++ b/drivers/iio/industrialio-event.c
> @@ -12,11 +12,13 @@
> #include <linux/kernel.h>
> #include <linux/kfifo.h>
> #include <linux/module.h>
> +#include <linux/notifier.h>
> #include <linux/poll.h>
> #include <linux/sched.h>
> #include <linux/slab.h>
> #include <linux/uaccess.h>
> #include <linux/wait.h>
> +#include <linux/iio/consumer.h>
> #include <linux/iio/iio.h>
> #include <linux/iio/iio-opaque.h>
> #include "iio_core.h"
> @@ -26,6 +28,7 @@
> /**
> * struct iio_event_interface - chrdev interface for an event line
> * @wait: wait queue to allow blocking reads of events
> + * @notifier: notifier head for in-kernel event listeners
> * @det_events: list of detected events
> * @dev_attr_list: list of event interface sysfs attribute
> * @flags: file operations related flags including busy flag.
> @@ -35,6 +38,7 @@
> */
> struct iio_event_interface {
> wait_queue_head_t wait;
> + struct atomic_notifier_head notifier;
> DECLARE_KFIFO(det_events, struct iio_event_data, 16);
>
> struct list_head dev_attr_list;
> @@ -67,18 +71,19 @@ int iio_push_event(struct iio_dev *indio_dev, u64 ev_code,
> s64 timestamp)
> {
> struct iio_dev_opaque *iio_dev_opaque = to_iio_dev_opaque(indio_dev);
> struct iio_event_interface *ev_int = iio_dev_opaque->event_interface;
> - struct iio_event_data ev;
> + struct iio_event_data ev = {
> + .id = ev_code,
> + .timestamp = timestamp,
> + };
> int copied;
>
> if (!ev_int)
> return 0;
>
> + atomic_notifier_call_chain(&ev_int->notifier, IIO_NOTIFY_EVENT, &ev);
> +
> /* Does anyone care? */
> if (iio_event_enabled(ev_int)) {
> -
> - ev.id = ev_code;
> - ev.timestamp = timestamp;
> -
> copied = kfifo_put(&ev_int->det_events, ev);
> if (copied != 0)
> wake_up_poll(&ev_int->wait, EPOLLIN);
> @@ -223,6 +228,25 @@ static int iio_event_getfd(struct iio_dev *indio_dev)
> return fd;
> }
>
> +int iio_event_register(struct iio_dev *indio_dev, struct notifier_block
> *block)
> +{
> + struct iio_dev_opaque *iio_dev_opaque = to_iio_dev_opaque(indio_dev);
> + struct iio_event_interface *ev_int = iio_dev_opaque->event_interface;
> +
> + return atomic_notifier_chain_register(&ev_int->notifier, block);
> +}
> +EXPORT_SYMBOL_GPL(iio_event_register);
> +
> +void iio_event_unregister(struct iio_dev *indio_dev,
> + struct notifier_block *block)
> +{
> + struct iio_dev_opaque *iio_dev_opaque = to_iio_dev_opaque(indio_dev);
> + struct iio_event_interface *ev_int = iio_dev_opaque->event_interface;
> +
> + WARN_ON(atomic_notifier_chain_unregister(&ev_int->notifier, block));
> +}
> +EXPORT_SYMBOL_GPL(iio_event_unregister);
> +
> static const char * const iio_ev_type_text[] = {
> [IIO_EV_TYPE_THRESH] = "thresh",
> [IIO_EV_TYPE_MAG] = "mag",
> diff --git a/include/linux/iio/consumer.h b/include/linux/iio/consumer.h
> index 16e7682474f3..9918e3f7af3d 100644
> --- a/include/linux/iio/consumer.h
> +++ b/include/linux/iio/consumer.h
> @@ -507,4 +507,34 @@ int iio_write_event_processed_scale(struct iio_channel
> *chan,
> enum iio_event_info info, int processed,
> unsigned int scale);
>
> +struct notifier_block;
> +enum iio_notifier_val {
> + /** IIO_NOTIFY_EVENT: v is a pointer to &struct iio_event_data */
> + IIO_NOTIFY_EVENT,
> +};
> +
> +/**
> + * iio_event_register() - Register a notifier for events
> + * @indio_dev: Device to be notified of events on
> + * @block: Notifier block to register
> + *
> + * Register a notifier for events on @indio_dev. @v will be a member of &enum
> + * iio_notifier_val. Notifiers will be called in atomic context. @indio_dev
> + * must stay valid until you call iio_event_unregister().
> + *
> + * Return: 0 on success, or -EEXIST if @block has already been registered
> + */
> +int iio_event_register(struct iio_dev *indio_dev,
> + struct notifier_block *block);
> +
> +/**
> + * iio_event_unregister() - Remove a previously-added notifier
> + * @indio_dev: Device to be notified of events on
> + * @block: Notifier previously-registered with iio_event_register()
> + *
> + * Remove a previously-added notifier.
> + */
> +void iio_event_unregister(struct iio_dev *indio_dev,
> + struct notifier_block *block);
> +
> #endif
^ permalink raw reply [flat|nested] 62+ messages in thread
* Re: [PATCH 7/7] hwmon: iio: Add alarm support
2025-07-15 1:20 ` [PATCH 7/7] hwmon: iio: Add alarm support Sean Anderson
2025-07-15 8:50 ` Andy Shevchenko
@ 2025-07-15 11:28 ` Nuno Sá
2025-07-15 17:02 ` Sean Anderson
2025-07-15 16:13 ` kernel test robot
` (3 subsequent siblings)
5 siblings, 1 reply; 62+ messages in thread
From: Nuno Sá @ 2025-07-15 11:28 UTC (permalink / raw)
To: Sean Anderson, Jonathan Cameron, Jean Delvare, Guenter Roeck,
linux-iio, linux-hwmon
Cc: Andy Shevchenko, Nuno Sá, linux-kernel, David Lechner
On Mon, 2025-07-14 at 21:20 -0400, Sean Anderson wrote:
> Add alarm support based on IIO threshold events. The alarm is cleared on
> read, but will be set again if the condition is still present. This is
> detected by disabling and re-enabling the event. The same trick is done
> when creating the attribute to detect already-triggered events.
>
> The alarms are updated by an event listener. To keep the notifier call
> chain short, we create one listener per iio device, shared across all
> hwmon devices.
>
> To avoid dynamic creation of alarms, alarms for all possible events are
> allocated at creation. Lookup is done by a linear scan, as I expect
> events to occur rarely. If performance becomes an issue, a binary search
> could be done instead (or some kind of hash lookup).
>
> Signed-off-by: Sean Anderson <sean.anderson@linux.dev>
> ---
>
> drivers/hwmon/iio_hwmon.c | 322 +++++++++++++++++++++++++++++++++++++-
> 1 file changed, 321 insertions(+), 1 deletion(-)
>
> diff --git a/drivers/hwmon/iio_hwmon.c b/drivers/hwmon/iio_hwmon.c
> index 3db4d4b30022..c963bc5452ba 100644
> --- a/drivers/hwmon/iio_hwmon.c
> +++ b/drivers/hwmon/iio_hwmon.c
> @@ -8,6 +8,7 @@
> #include <linux/slab.h>
> #include <linux/mod_devicetable.h>
> #include <linux/module.h>
> +#include <linux/notifier.h>
> #include <linux/err.h>
> #include <linux/platform_device.h>
> #include <linux/property.h>
> @@ -15,7 +16,192 @@
> #include <linux/hwmon.h>
> #include <linux/hwmon-sysfs.h>
> #include <linux/iio/consumer.h>
> +#include <linux/iio/events.h>
> +#include <linux/iio/iio.h>
> #include <linux/iio/types.h>
> +#include <uapi/linux/iio/events.h>
> +
> +/* Protects iio_hwmon_listeners and listeners' refcnt */
> +DEFINE_MUTEX(iio_hwmon_listener_lock);
> +LIST_HEAD(iio_hwmon_listeners);
> +
> +/**
> + * struct iio_hwmon_listener - Listener for IIO events
> + * @block: Notifier for events
> + * @ids: Array of IIO event ids, one per alarm
> + * @alarms: Bitmap of alarms
> + * @num_alarms: Length of @ids and @alarms
> + * @indio_dev: Device we are listening to
> + * @list: List of all listeners
> + * @refcnt: Reference count
> + */
> +struct iio_hwmon_listener {
> + struct notifier_block block;
> + u64 *ids;
> + unsigned long *alarms;
> + size_t num_alarms;
> +
> + struct iio_dev *indio_dev;
> + struct list_head list;
> + unsigned int refcnt;
> +};
> +
> +/**
> + * iio_hwmon_lookup_alarm() - Find an alarm by id
> + * @listener: Event listener
> + * @id: IIO event id
> + *
> + * Return: index of @id in @listener->ids, or -1 if not found
> + */
> +static ssize_t iio_hwmon_lookup_alarm(struct iio_hwmon_listener *listener,
> + u64 id)
> +{
> + ssize_t i;
> +
> + for (i = 0; i < listener->num_alarms; i++)
> + if (listener->ids[i] == id)
> + return i;
> +
> + return -1;
> +}
> +
> +static int iio_hwmon_listener_callback(struct notifier_block *block,
> + unsigned long action, void *data)
> +{
> + struct iio_hwmon_listener *listener =
> + container_of(block, struct iio_hwmon_listener, block);
> + struct iio_event_data *ev = data;
> + ssize_t i;
> +
> + if (action != IIO_NOTIFY_EVENT)
> + return NOTIFY_DONE;
> +
> + i = iio_hwmon_lookup_alarm(listener, ev->id);
> + if (i >= 0)
> + set_bit(i, listener->alarms);
> + else
> + dev_warn_once(&listener->indio_dev->dev,
> + "unknown event %016llx\n", ev->id);
> +
> + return NOTIFY_DONE;
> +}
> +
> +/**
> + * iio_event_id() - Calculate an IIO event id
> + * @channel: IIO channel for this event
> + * @type: Event type (theshold, rate-of-change, etc.)
> + * @dir: Event direction (rising, falling, etc.)
> + *
> + * Return: IIO event id corresponding to this event's IIO id
> + */
> +static u64 iio_event_id(struct iio_chan_spec const *chan,
> + enum iio_event_type type,
> + enum iio_event_direction dir)
> +{
> + if (chan->differential)
> + return IIO_DIFF_EVENT_CODE(chan->type, chan->channel,
> + chan->channel2, type, dir);
> + if (chan->modified)
> + return IIO_MOD_EVENT_CODE(chan->type, chan->channel,
> + chan->channel2, type, dir);
> + return IIO_UNMOD_EVENT_CODE(chan->type, chan->channel, type, dir);
> +}
> +
> +/**
> + * iio_hwmon_listener_get() - Get a listener for an IIO device
> + * @indio_dev: IIO device to listen to
> + *
> + * Look up or create a new listener for @indio_dev. The returned listener is
> + * registered with @indio_dev, but events still need to be manually enabled.
> + * You must call iio_hwmon_listener_put() when you are done.
> + *
> + * Return: Listener for @indio_dev, or an error pointer
> + */
> +static struct iio_hwmon_listener *iio_hwmon_listener_get(struct iio_dev
> *indio_dev)
> +{
> + struct iio_hwmon_listener *listener;
> + int err = -ENOMEM;
> + size_t i, j;
> +
> + guard(mutex)(&iio_hwmon_listener_lock);
> + list_for_each_entry(listener, &iio_hwmon_listeners, list) {
> + if (listener->indio_dev == indio_dev) {
> + if (likely(listener->refcnt != UINT_MAX))
> + listener->refcnt++;
I dunno for the above to ever happen :). And as Andy stated, let's just use
proper refcount APIs.
> + return listener;
> + }
> + }
> +
> + listener = kzalloc(sizeof(*listener), GFP_KERNEL);
> + if (!listener)
> + goto err_unlock;
> +
> + listener->refcnt = 1;
> + listener->indio_dev = indio_dev;
> + listener->block.notifier_call = iio_hwmon_listener_callback;
> + for (i = 0; i < indio_dev->num_channels; i++)
> + listener->num_alarms += indio_dev-
> >channels[i].num_event_specs;
> +
> + listener->ids = kcalloc(listener->num_alarms, sizeof(*listener->ids),
> + GFP_KERNEL);
> + listener->alarms = bitmap_zalloc(listener->num_alarms, GFP_KERNEL);
> + if (!listener->ids || !listener->alarms)
> + goto err_listener;
> +
> + i = 0;
> + for (j = 0; j < indio_dev->num_channels; j++) {
> + struct iio_chan_spec const *chan = &indio_dev->channels[j];
> + size_t k;
> +
> + for (k = 0; k < chan->num_event_specs; k++)
> + listener->ids[i++] =
> + iio_event_id(chan, chan->event_spec[k].type,
> + chan->event_spec[k].dir);
> + }
> +
> + err = iio_event_register(indio_dev, &listener->block);
> + if (err)
> + goto err_alarms;
> +
> + list_add(&listener->list, &iio_hwmon_listeners);
> + mutex_unlock(&iio_hwmon_listener_lock);
> + return listener;
> +
> +err_alarms:
> + kfree(listener->alarms);
> + kfree(listener->ids);
> +err_listener:
> + kfree(listener);
> +err_unlock:
> + mutex_unlock(&iio_hwmon_listener_lock);
> + return ERR_PTR(err);
> +}
> +
> +/**
> + * iio_hwmon_listener_put() - Release a listener
> + * @data: &struct iio_hwmon_listener to release
> + *
> + * For convenience, @data is void.
> + */
> +static void iio_hwmon_listener_put(void *data)
> +{
> + struct iio_hwmon_listener *listener = data;
> +
> + scoped_guard(mutex, &iio_hwmon_listener_lock) {
> + if (unlikely(listener->refcnt == UINT_MAX))
> + return;
> +
> + if (--listener->refcnt)
> + return;
> +
> + list_del(&listener->list);
> + iio_event_unregister(listener->indio_dev, &listener->block);
> + }
> +
> + kfree(listener->alarms);
> + kfree(listener->ids);
> + kfree(listener);
> +}
>
> /**
> * struct iio_hwmon_state - device instance state
> @@ -143,6 +329,68 @@ static ssize_t iio_hwmon_write_event(struct device *dev,
> return count;
> }
>
> +/**
> + * struct iio_hwmon_alarm_attribute - IIO HWMON alarm attribute
> + * @dev_attr: Base device attribute
> + * @listener: Listener for this alarm
> + * @index: Index of the channel in the IIO HWMON
> + * @alarm: Index of the alarm within @listener
> + */
> +struct iio_hwmon_alarm_attribute {
> + struct device_attribute dev_attr;
> + struct iio_hwmon_listener *listener;
> + size_t index;
> + size_t alarm;
> +};
> +#define to_alarm_attr(_dev_attr) \
> + container_of(_dev_attr, struct iio_hwmon_alarm_attribute, dev_attr)
> +
> +/**
> + * iio_hwmon_alarm_toggle() - Turn an event off and back on again
> + * @chan: Channel of the event
> + * @dir: Event direction (rising, falling, etc.)
> + *
> + * Toggle an event's enable so we get notified if the alarm is already
> + * triggered. We use this to convert IIO's event-triggered events into
> + * level-triggered alarms.
> + *
> + * Return: 0 on success or negative error on failure
> + */
> +static int iio_hwmon_alarm_toggle(struct iio_channel *chan,
> + enum iio_event_direction dir)
> +{
> + int ret;
> +
> + ret = iio_write_event_processed_scale(chan, IIO_EV_TYPE_THRESH, dir,
> + IIO_EV_INFO_ENABLE, 0, 1);
> + if (ret)
> + return ret;
> +
> + return iio_write_event_processed_scale(chan, IIO_EV_TYPE_THRESH, dir,
> + IIO_EV_INFO_ENABLE, 1, 1);
> +}
> +
> +static ssize_t iio_hwmon_read_alarm(struct device *dev,
> + struct device_attribute *attr,
> + char *buf)
> +{
> + struct iio_hwmon_alarm_attribute *sattr = to_alarm_attr(attr);
> + struct iio_hwmon_state *state = dev_get_drvdata(dev);
> + struct iio_channel *chan = &state->channels[sattr->index];
> +
> + if (test_and_clear_bit(sattr->alarm, sattr->listener->alarms)) {
> + u64 id = sattr->listener->ids[sattr->alarm];
> + enum iio_event_direction dir =
> IIO_EVENT_CODE_EXTRACT_DIR(id);
> +
> + WARN_ON(iio_hwmon_alarm_toggle(chan, dir));
WARN_ON() is highly discouraged... Also do we really need a "scary" splat in
this case?
> + strcpy(buf, "1\n");
> + return 2;
> + }
> +
> + strcpy(buf, "0\n");
> + return 2;
As stated, sysfs_emit()
> +}
> +
> static int add_device_attr(struct device *dev, struct iio_hwmon_state *st,
> ssize_t (*show)(struct device *dev,
> struct device_attribute *attr,
> @@ -205,6 +453,63 @@ static int add_event_attr(struct device *dev, struct
> iio_hwmon_state *st,
> return 0;
> }
>
> +static int add_alarm_attr(struct device *dev, struct iio_hwmon_state *st,
> + int i, enum iio_event_direction dir,
> + const char *fmt, ...)
> +{
> + struct iio_hwmon_alarm_attribute *a;
> + struct iio_hwmon_listener *listener;
> + ssize_t alarm;
> + umode_t mode;
> + va_list ap;
> + int ret;
> +
> + mode = iio_event_mode(&st->channels[i], IIO_EV_TYPE_THRESH, dir,
> + IIO_EV_INFO_ENABLE);
> + if (!(mode & 0200))
> + return 0;
> +
> + listener = iio_hwmon_listener_get(st->channels[i].indio_dev);
> + if (listener == ERR_PTR(-EBUSY))
> + return 0;
Maybe I missed something, where can we get -EBUSY? And should we ignore it?
> + if (IS_ERR(listener))
> + return PTR_ERR(listener);
> +
> + ret = devm_add_action_or_reset(dev, iio_hwmon_listener_put,
> listener);
> + if (ret)
> + return ret;
> +
> + alarm = iio_hwmon_lookup_alarm(listener,
> + iio_event_id(st->channels[i].channel,
> + IIO_EV_TYPE_THRESH,
> dir));
> + if (WARN_ON_ONCE(alarm < 0))
> + return -ENOENT;
> +
Again, I would drop WARN_ON_ONCE()
- Nuno Sá
^ permalink raw reply [flat|nested] 62+ messages in thread
* Re: [PATCH 2/7] iio: inkern: Add API for reading/writing events
2025-07-15 8:18 ` Andy Shevchenko
@ 2025-07-15 15:42 ` Sean Anderson
2025-07-16 9:28 ` Andy Shevchenko
0 siblings, 1 reply; 62+ messages in thread
From: Sean Anderson @ 2025-07-15 15:42 UTC (permalink / raw)
To: Andy Shevchenko
Cc: Jonathan Cameron, Jean Delvare, Guenter Roeck, linux-iio,
linux-hwmon, Andy Shevchenko, Nuno Sá, linux-kernel,
David Lechner
On 7/15/25 04:18, Andy Shevchenko wrote:
> On Mon, Jul 14, 2025 at 09:20:18PM -0400, Sean Anderson wrote:
>> Add an in-kernel API for reading/writing event properties. Like the
>> raw-to-processed conversion, with processed-to-raw we only convert the
>> integer part, introducing some round-off error.
>>
>> A common case is for other drivers to re-expose IIO events as sysfs
>> properties with a different API. To help out with this, iio_event_mode
>> returns the appropriate mode. It can also be used to test for existence
>> if the consumer doesn't care about read/write capability.
>
> ...
>
>> +EXPORT_SYMBOL_GPL(iio_event_mode);
>
> Can we move this to namespace? Otherwise it will be never ending story...
> Ditto for other new APIs.
Never ending story of what?
> ...
>
>> + if (scale64 <= INT_MAX && scale64 >= INT_MIN)
>> + raw64 = processed / (int)scale64;
>
> Do you need the casting? (I mean if the compiler is dumb enough to not see this)
AIUI 64-bit division is not available on 32-bit platforms. The cast
ensures we get 32-bit division.
> ...
>
>> + case IIO_VAL_INT_PLUS_MICRO:
>> + scale64 = scale_val * scale * 1000000LL + scale_val2;
>> + raw64 = div64_s64_rem(processed, scale64, &r);
>> + raw64 = raw64 * 1000000 +
>> + div64_s64(r * 1000000, scale64);
>
> Logically this should be 1000000L, but can we somehow use the constants?
> Like
>
> scale64 = (s64)MICRO * scale_val * scale + scale_val2;
> raw64 = div64_s64_rem(processed, scale64, &r);
> raw64 = raw64 * (s32)MICRO +
> div64_s64(r * (s64)MICRO, scale64);
>
This follows iio_convert_raw_to_processed_unlocked but ok...
>> + break;
>
> Ditto for other cases?
>
> ...
>
>> + *raw = clamp(raw64, (s64)INT_MIN, (s64)INT_MAX);
>
> You already have similar approach here...
Well, I can spell it 0x7fffffffLL if you'd like...
>> + ret = iio_convert_processed_to_raw_unlocked(chan, processed, &raw,
>> + scale);
>> + if (ret < 0)
>
> Why ' < 0' ?
Originally I returned IIO_VAL_INT but later decided against it.
--Sean
^ permalink raw reply [flat|nested] 62+ messages in thread
* Re: [PATCH 2/7] iio: inkern: Add API for reading/writing events
2025-07-15 10:35 ` Nuno Sá
@ 2025-07-15 15:43 ` Sean Anderson
2025-07-16 6:23 ` Nuno Sá
0 siblings, 1 reply; 62+ messages in thread
From: Sean Anderson @ 2025-07-15 15:43 UTC (permalink / raw)
To: Nuno Sá, Jonathan Cameron, Jean Delvare, Guenter Roeck,
linux-iio, linux-hwmon
Cc: Andy Shevchenko, Nuno Sá, linux-kernel, David Lechner
On 7/15/25 06:35, Nuno Sá wrote:
> On Mon, 2025-07-14 at 21:20 -0400, Sean Anderson wrote:
>> Add an in-kernel API for reading/writing event properties. Like the
>> raw-to-processed conversion, with processed-to-raw we only convert the
>> integer part, introducing some round-off error.
>>
>> A common case is for other drivers to re-expose IIO events as sysfs
>> properties with a different API. To help out with this, iio_event_mode
>> returns the appropriate mode. It can also be used to test for existence
>> if the consumer doesn't care about read/write capability.
>>
>> Signed-off-by: Sean Anderson <sean.anderson@linux.dev>
>> ---
>
> Just one comment on top of Andy's review
>
>>
>> drivers/iio/inkern.c | 198 +++++++++++++++++++++++++++++++++++
>> include/linux/iio/consumer.h | 56 ++++++++++
>> 2 files changed, 254 insertions(+)
>>
>> diff --git a/drivers/iio/inkern.c b/drivers/iio/inkern.c
>> index c174ebb7d5e6..d3bbd2444fb5 100644
>> --- a/drivers/iio/inkern.c
>> +++ b/drivers/iio/inkern.c
>> @@ -1028,3 +1028,201 @@ ssize_t iio_read_channel_label(struct iio_channel
>> *chan, char *buf)
>> return do_iio_read_channel_label(chan->indio_dev, chan->channel,
>> buf);
>> }
>> EXPORT_SYMBOL_GPL(iio_read_channel_label);
>> +
>> +static bool iio_event_exists(struct iio_channel *channel,
>> + enum iio_event_type type,
>> + enum iio_event_direction dir,
>> + enum iio_event_info info)
>> +{
>> + struct iio_chan_spec const *chan = channel->channel;
>> + int i;
>> +
>
> Can we iio_event_exists() -> iio_event_exists_locked()? Or likely the best way
wouldn't _unlocked be the convention for this file?
> would be to annotate it with lockdep (though that would mean some dance to get
> the opaque object.
I will add a lockdep annotation.
--Sean
> Anyways, bottom line is it should clear that iio_event_exists() is to be called
> with the lock held.
>
> - Nuno Sá
>
>> + if (!channel->indio_dev->info)
>> + return false;
>> +
>> + for (i = 0; i < chan->num_event_specs; i++) {
>> + if (chan->event_spec[i].type != type)
>> + continue;
>> + if (chan->event_spec[i].dir != dir)
>> + continue;
>> + if (chan->event_spec[i].mask_separate & BIT(info))
>> + return true;
>> + }
>> +
>> + return false;
>> +}
>> +
>> +umode_t iio_event_mode(struct iio_channel *chan, enum iio_event_type type,
>> + enum iio_event_direction dir, enum iio_event_info
>> info)
>> +{
>> + struct iio_dev *indio_dev = chan->indio_dev;
>> + struct iio_dev_opaque *iio_dev_opaque = to_iio_dev_opaque(indio_dev);
>> + umode_t mode = 0;
>> +
>> + guard(mutex)(&iio_dev_opaque->info_exist_lock);
>> + if (!iio_event_exists(chan, type, dir, info))
>> + return 0;
>> +
>> + if (info == IIO_EV_INFO_ENABLE) {
>> + if (indio_dev->info->read_event_config)
>> + mode |= 0444;
>> +
>> + if (indio_dev->info->write_event_config)
>> + mode |= 0200;
>> + } else {
>> + if (indio_dev->info->read_event_value)
>> + mode |= 0444;
>> +
>> + if (indio_dev->info->write_event_value)
>> + mode |= 0200;
>> + }
>> +
>> + return mode;
>> +}
>> +EXPORT_SYMBOL_GPL(iio_event_mode);
>> +
>> +int iio_read_event_processed_scale(struct iio_channel *chan,
>> + enum iio_event_type type,
>> + enum iio_event_direction dir,
>> + enum iio_event_info info, int *val,
>> + unsigned int scale)
>> +{
>> + struct iio_dev *indio_dev = chan->indio_dev;
>> + struct iio_dev_opaque *iio_dev_opaque = to_iio_dev_opaque(indio_dev);
>> + int ret, raw;
>> +
>> + guard(mutex)(&iio_dev_opaque->info_exist_lock);
>> + if (!iio_event_exists(chan, type, dir, info))
>> + return -ENODEV;
>> +
>> + if (info == IIO_EV_INFO_ENABLE) {
>> + if (!indio_dev->info->read_event_config)
>> + return -EINVAL;
>> +
>> + raw = indio_dev->info->read_event_config(indio_dev,
>> + chan->channel, type,
>> + dir);
>> + if (raw < 0)
>> + return raw;
>> +
>> + *val = raw;
>> + return 0;
>> + }
>> +
>> + if (!indio_dev->info->read_event_value)
>> + return -EINVAL;
>> +
>> + ret = indio_dev->info->read_event_value(indio_dev, chan->channel,
>> type,
>> + dir, info, &raw, NULL);
>> + if (ret < 0)
>> + return ret;
>> +
>> + return iio_convert_raw_to_processed_unlocked(chan, raw, val, scale);
>> +}
>> +EXPORT_SYMBOL_GPL(iio_read_event_processed_scale);
>> +
>> +static int iio_convert_processed_to_raw_unlocked(struct iio_channel *chan,
>> + int processed, int *raw,
>> + unsigned int scale)
>> +{
>> + int scale_type, scale_val, scale_val2;
>> + int offset_type, offset_val, offset_val2;
>> + s64 r, scale64, raw64;
>> +
>> + scale_type = iio_channel_read(chan, &scale_val, &scale_val2,
>> + IIO_CHAN_INFO_SCALE);
>> + if (scale_type < 0) {
>> + raw64 = processed / scale;
>> + } else {
>> + switch (scale_type) {
>> + case IIO_VAL_INT:
>> + scale64 = (s64)scale_val * scale;
>> + if (scale64 <= INT_MAX && scale64 >= INT_MIN)
>> + raw64 = processed / (int)scale64;
>> + else
>> + raw64 = 0;
>> + break;
>> + case IIO_VAL_INT_PLUS_MICRO:
>> + scale64 = scale_val * scale * 1000000LL + scale_val2;
>> + raw64 = div64_s64_rem(processed, scale64, &r);
>> + raw64 = raw64 * 1000000 +
>> + div64_s64(r * 1000000, scale64);
>> + break;
>> + case IIO_VAL_INT_PLUS_NANO:
>> + scale64 = scale_val * scale * 1000000000LL +
>> scale_val2;
>> + raw64 = div64_s64_rem(processed, scale64, &r);
>> + raw64 = raw64 * 1000000000 +
>> + div64_s64(r * 1000000000, scale64);
>> + break;
>> + case IIO_VAL_FRACTIONAL:
>> + raw64 = div64_s64((s64)processed * scale_val2,
>> + (s64)scale_val * scale);
>> + break;
>> + case IIO_VAL_FRACTIONAL_LOG2:
>> + raw64 = div64_s64((s64)processed << scale_val2,
>> + (s64)scale_val * scale);
>> + break;
>> + default:
>> + return -EINVAL;
>> + }
>> + }
>> +
>> + offset_type = iio_channel_read(chan, &offset_val, &offset_val2,
>> + IIO_CHAN_INFO_OFFSET);
>> + if (offset_type >= 0) {
>> + switch (offset_type) {
>> + case IIO_VAL_INT:
>> + case IIO_VAL_INT_PLUS_MICRO:
>> + case IIO_VAL_INT_PLUS_NANO:
>> + raw64 -= offset_val;
>> + break;
>> + case IIO_VAL_FRACTIONAL:
>> + raw64 -= offset_val / offset_val2;
>> + break;
>> + case IIO_VAL_FRACTIONAL_LOG2:
>> + raw64 -= offset_val >> offset_val2;
>> + break;
>> + default:
>> + return -EINVAL;
>> + }
>> + }
>> +
>> + *raw = clamp(raw64, (s64)INT_MIN, (s64)INT_MAX);
>> + return 0;
>> +}
>> +
>> +int iio_write_event_processed_scale(struct iio_channel *chan,
>> + enum iio_event_type type,
>> + enum iio_event_direction dir,
>> + enum iio_event_info info, int processed,
>> + unsigned int scale)
>> +{
>> + struct iio_dev *indio_dev = chan->indio_dev;
>> + struct iio_dev_opaque *iio_dev_opaque = to_iio_dev_opaque(chan-
>> >indio_dev);
>> + int ret, raw;
>> +
>> + guard(mutex)(&iio_dev_opaque->info_exist_lock);
>> + if (!iio_event_exists(chan, type, dir, info))
>> + return -ENODEV;
>> +
>> + if (info == IIO_EV_INFO_ENABLE) {
>> + if (!indio_dev->info->write_event_config)
>> + return -EINVAL;
>> +
>> + return indio_dev->info->write_event_config(indio_dev,
>> + chan->channel,
>> type,
>> + dir, processed);
>> + }
>> +
>> + if (!indio_dev->info->write_event_value)
>> + return -EINVAL;
>> +
>> + ret = iio_convert_processed_to_raw_unlocked(chan, processed, &raw,
>> + scale);
>> + if (ret < 0)
>> + return ret;
>> +
>> + return indio_dev->info->write_event_value(indio_dev, chan->channel,
>> + type, dir, info, raw, 0);
>> +}
>> +EXPORT_SYMBOL_GPL(iio_write_event_processed_scale);
>> diff --git a/include/linux/iio/consumer.h b/include/linux/iio/consumer.h
>> index 6a4479616479..16e7682474f3 100644
>> --- a/include/linux/iio/consumer.h
>> +++ b/include/linux/iio/consumer.h
>> @@ -451,4 +451,60 @@ ssize_t iio_write_channel_ext_info(struct iio_channel
>> *chan, const char *attr,
>> */
>> ssize_t iio_read_channel_label(struct iio_channel *chan, char *buf);
>>
>> +/**
>> + * iio_event_mode() - get file mode for an event property
>> + * @chan: Channel being queried
>> + * @type: Event type (theshold, rate-of-change, etc.)
>> + * @dir: Event direction (rising, falling, etc.)
>> + * @info: Event property (enable, value, etc.)
>> + *
>> + * Determine an appropriate mode for sysfs files derived from this event.
>> + *
>> + * Return:
>> + * - `0000` if the event is unsupported or otherwise unavailable
>> + * - `0444` if the event is read-only
>> + * - `0200` if the event is write-only
>> + * - `0644` if the event is read-write
>> + */
>> +umode_t iio_event_mode(struct iio_channel *chan, enum iio_event_type type,
>> + enum iio_event_direction dir, enum iio_event_info
>> info);
>> +
>> +/**
>> + * iio_read_event_processed_scale() - Read an event property
>> + * @chan: Channel being queried
>> + * @type: Event type (theshold, rate-of-change, etc.)
>> + * @dir: Event direction (rising, falling, etc.)
>> + * @info: Event property (enable, value, etc.)
>> + * @val: Processed property value
>> + * @scale: Factor to scale @val by
>> + *
>> + * Read a processed (scaled and offset) event property of a given channel.
>> + *
>> + * Return: 0 on success, or negative error on failure
>> + */
>> +int iio_read_event_processed_scale(struct iio_channel *chan,
>> + enum iio_event_type type,
>> + enum iio_event_direction dir,
>> + enum iio_event_info info, int *val,
>> + unsigned int scale);
>> +
>> +/**
>> + * iio_write_event_processed_scale() - Read an event property
>> + * @chan: Channel being queried
>> + * @type: Event type (theshold, rate-of-change, etc.)
>> + * @dir: Event direction (rising, falling, etc.)
>> + * @info: Event property (enable, value, etc.)
>> + * @processed: Processed property value
>> + * @scale: Factor to scale @processed by
>> + *
>> + * Write a processed (scaled and offset) event property of a given channel.
>> + *
>> + * Return: 0 on success, or negative error on failure
>> + */
>> +int iio_write_event_processed_scale(struct iio_channel *chan,
>> + enum iio_event_type type,
>> + enum iio_event_direction dir,
>> + enum iio_event_info info, int processed,
>> + unsigned int scale);
>> +
>> #endif
^ permalink raw reply [flat|nested] 62+ messages in thread
* Re: [PATCH 3/7] iio: Add in-kernel API for events
2025-07-15 8:20 ` Andy Shevchenko
@ 2025-07-15 15:47 ` Sean Anderson
2025-07-16 9:47 ` Andy Shevchenko
0 siblings, 1 reply; 62+ messages in thread
From: Sean Anderson @ 2025-07-15 15:47 UTC (permalink / raw)
To: Andy Shevchenko
Cc: Jonathan Cameron, Jean Delvare, Guenter Roeck, linux-iio,
linux-hwmon, Andy Shevchenko, Nuno Sá, linux-kernel,
David Lechner
On 7/15/25 04:20, Andy Shevchenko wrote:
> On Mon, Jul 14, 2025 at 09:20:19PM -0400, Sean Anderson wrote:
>> Add an API to notify consumers about events. Events still need to be
>> enabled using the iio_read_event/iio_write_event functions. Of course,
>> userspace can also manipulate the enabled events. I don't think this is
>> too much of an issue, since userspace can also manipulate the event
>> thresholds. But enabling events may cause existing programs to be
>> surprised when they get something unexpected. Maybe we should set the
>> interface as busy when there are any in-kernel listeners?
>
> ...
>
>> #include <linux/wait.h>
>
> While at it...
>
> + blank line here...
>
>> +#include <linux/iio/consumer.h>
>> #include <linux/iio/iio.h>
>> #include <linux/iio/iio-opaque.h>
>
> ...and here?
>
>> #include "iio_core.h"
>
> ...
>
>> + struct iio_event_data ev = {
>> + .id = ev_code,
>> + .timestamp = timestamp,
>> + };
>
> ...
>
>> /* Does anyone care? */
>> if (iio_event_enabled(ev_int)) {
>> -
>> - ev.id = ev_code;
>> - ev.timestamp = timestamp;
>> -
>> copied = kfifo_put(&ev_int->det_events, ev);
>> if (copied != 0)
>> wake_up_poll(&ev_int->wait, EPOLLIN);
>
> Looks like this refactoring can be done before main change.
I think it is clearer to keep this in the same patch as the
functionality that uses it.
> ...
>
>> + WARN_ON(atomic_notifier_chain_unregister(&ev_int->notifier, block));
>
> Is bug.h already included?
I assume so. No build errors.
--Sean
^ permalink raw reply [flat|nested] 62+ messages in thread
* Re: [PATCH 5/7] hwmon: iio: Add helper function for creating attributes
2025-07-15 8:38 ` Andy Shevchenko
@ 2025-07-15 15:55 ` Sean Anderson
2025-07-16 10:00 ` Andy Shevchenko
0 siblings, 1 reply; 62+ messages in thread
From: Sean Anderson @ 2025-07-15 15:55 UTC (permalink / raw)
To: Andy Shevchenko
Cc: Jonathan Cameron, Jean Delvare, Guenter Roeck, linux-iio,
linux-hwmon, Andy Shevchenko, Nuno Sá, linux-kernel,
David Lechner
On 7/15/25 04:38, Andy Shevchenko wrote:
> On Mon, Jul 14, 2025 at 09:20:21PM -0400, Sean Anderson wrote:
>> Add a helper function to create attributes and initialize their fields.
>> This reduces repetition when creating several attributes per channel.
>
> ...
>
>> + * @num_attrs: length of @attrs
>
> Other lines use TABs.
>
> ...
OK
>> +static int add_device_attr(struct device *dev, struct iio_hwmon_state *st,
>
> This should hint that this is managed:
>
> add_device_managed_attr()
That just makes it more difficult to format the calling code within 80 columns...
>
>> + ssize_t (*show)(struct device *dev,
>> + struct device_attribute *attr,
>> + char *buf),
>> + int i, const char *fmt, ...)
>
> __printf() attribute is missing.
It's static, so I thought the compiler could infer it but I guess not.
>> +{
>> + struct sensor_device_attribute *a;
>> + va_list ap;
>> +
>> + a = devm_kzalloc(dev, sizeof(*a), GFP_KERNEL);
>> + if (!a)
>> + return -ENOMEM;
>> +
>> + sysfs_attr_init(&a->dev_attr.attr);
>> + va_start(ap, fmt);
>> + a->dev_attr.attr.name = devm_kvasprintf(dev, GFP_KERNEL, fmt, ap);
>> + va_end(ap);
>> + if (!a->dev_attr.attr.name)
>> + return -ENOMEM;
>> +
>> + a->dev_attr.show = show;
>> + a->dev_attr.attr.mode = 0444;
>> + a->index = i;
>> +
>> + st->attrs[st->num_attrs++] = &a->dev_attr.attr;
>> + return 0;
>> +}
>
> ...
>
>> struct device *dev = &pdev->dev;
>> struct iio_hwmon_state *st;
>> - struct sensor_device_attribute *a;
>> - int ret, i, attr = 0;
>> + int ret, i;
>
> Also move it a bit to make it more of a reversed xmas tree ordering?
It's not ordered as-is, and I don't think this subsystem requires it.
>> int in_i = 1, temp_i = 1, curr_i = 1, humidity_i = 1, power_i = 1;
>> enum iio_chan_type type;
>> struct iio_channel *channels;
>
--Sean
^ permalink raw reply [flat|nested] 62+ messages in thread
* Re: [PATCH 6/7] hwmon: iio: Add min/max support
2025-07-15 8:41 ` Andy Shevchenko
@ 2025-07-15 16:05 ` Sean Anderson
2025-07-16 10:01 ` Andy Shevchenko
0 siblings, 1 reply; 62+ messages in thread
From: Sean Anderson @ 2025-07-15 16:05 UTC (permalink / raw)
To: Andy Shevchenko
Cc: Jonathan Cameron, Jean Delvare, Guenter Roeck, linux-iio,
linux-hwmon, Andy Shevchenko, Nuno Sá, linux-kernel,
David Lechner
On 7/15/25 04:41, Andy Shevchenko wrote:
> On Mon, Jul 14, 2025 at 09:20:22PM -0400, Sean Anderson wrote:
>> Add support for minimum/maximum attributes. Like the _input attribute,
>> we just need to call into the IIO API.
>
> ...
>
>> +static ssize_t iio_hwmon_read_event(struct device *dev,
>> + struct device_attribute *attr,
>> + char *buf)
>> +{
>> + struct sensor_device_attribute_2 *sattr = to_sensor_dev_attr_2(attr);
>> + struct iio_hwmon_state *state = dev_get_drvdata(dev);
>> + struct iio_channel *chan = &state->channels[sattr->index];
>> + int ret, result, scale;
>> +
>> + scale = iio_hwmon_scale(chan);
>
>> + if (scale < 0)
>
> This part is definitely missed in the respective description.
OK
>> + return scale;
>> +
>> + ret = iio_read_event_processed_scale(chan, IIO_EV_TYPE_THRESH,
>> + sattr->nr, IIO_EV_INFO_VALUE,
>> + &result, scale);
>> + if (ret < 0)
>
> Why ' < 0' here?
This originally returned IIO_VAL_INT on success.
>> + return ret;
>> +
>> + return sprintf(buf, "%d\n", result);
>
> Mustn't be sysfs_emit() ?
It doesn't matter in this case (as %d can never emit more
than 20ish characters), but that works too.
>> +}
>
> ...
>
>> + ret = iio_write_event_processed_scale(chan, IIO_EV_TYPE_THRESH,
>> + sattr->nr, IIO_EV_INFO_VALUE,
>> + val, scale);
>> + if (ret < 0)
>
> < 0 ?
>
>> + return ret;
>
> ...
>
>> +static int add_event_attr(struct device *dev, struct iio_hwmon_state *st,
>> + int i, enum iio_event_direction dir,
>> + const char *fmt, ...)
>
> Same comments as per previous patch adding another attribute API.
>
> ...
>
>> + va_start(ap, fmt);
>> + a->dev_attr.attr.name = devm_kvasprintf(dev, GFP_KERNEL, fmt, ap);
>> + va_end(ap);
>
> Can't %pV be used?
%pV is for when we have additional info to add. e.g. if we were doing
devm_kasprintf(dev, GFP_KERNEL, "my_extra_info_%d_%pV", i, &vaf);
but we aren't so there's no point adding a level of indirection.
--Sean
^ permalink raw reply [flat|nested] 62+ messages in thread
* Re: [PATCH 7/7] hwmon: iio: Add alarm support
2025-07-15 1:20 ` [PATCH 7/7] hwmon: iio: Add alarm support Sean Anderson
2025-07-15 8:50 ` Andy Shevchenko
2025-07-15 11:28 ` Nuno Sá
@ 2025-07-15 16:13 ` kernel test robot
2025-07-15 19:34 ` Guenter Roeck
` (2 subsequent siblings)
5 siblings, 0 replies; 62+ messages in thread
From: kernel test robot @ 2025-07-15 16:13 UTC (permalink / raw)
To: Sean Anderson, Jonathan Cameron, Jean Delvare, Guenter Roeck,
linux-iio, linux-hwmon
Cc: oe-kbuild-all, Andy Shevchenko, Nuno Sá, linux-kernel,
David Lechner, Sean Anderson
Hi Sean,
kernel test robot noticed the following build warnings:
[auto build test WARNING on jic23-iio/togreg]
[also build test WARNING on groeck-staging/hwmon-next akpm-mm/mm-nonmm-unstable linus/master v6.16-rc6 next-20250715]
[If your patch is applied to the wrong git tree, kindly drop us a note.
And when submitting patch, we suggest to use '--base' as documented in
https://git-scm.com/docs/git-format-patch#_base_tree_information]
url: https://github.com/intel-lab-lkp/linux/commits/Sean-Anderson/math64-Add-div64_s64_rem/20250715-092337
base: https://git.kernel.org/pub/scm/linux/kernel/git/jic23/iio.git togreg
patch link: https://lore.kernel.org/r/20250715012023.2050178-8-sean.anderson%40linux.dev
patch subject: [PATCH 7/7] hwmon: iio: Add alarm support
config: i386-buildonly-randconfig-001-20250715 (https://download.01.org/0day-ci/archive/20250715/202507152309.wBE1wHwM-lkp@intel.com/config)
compiler: gcc-12 (Debian 12.2.0-14+deb12u1) 12.2.0
reproduce (this is a W=1 build): (https://download.01.org/0day-ci/archive/20250715/202507152309.wBE1wHwM-lkp@intel.com/reproduce)
If you fix the issue in a separate patch/commit (i.e. not just a new version of
the same patch/commit), kindly add following tags
| Reported-by: kernel test robot <lkp@intel.com>
| Closes: https://lore.kernel.org/oe-kbuild-all/202507152309.wBE1wHwM-lkp@intel.com/
All warnings (new ones prefixed by >>):
>> Warning: drivers/hwmon/iio_hwmon.c:99 function parameter 'chan' not described in 'iio_event_id'
>> Warning: drivers/hwmon/iio_hwmon.c:99 Excess function parameter 'channel' description in 'iio_event_id'
--
0-DAY CI Kernel Test Service
https://github.com/intel/lkp-tests/wiki
^ permalink raw reply [flat|nested] 62+ messages in thread
* Re: [PATCH 7/7] hwmon: iio: Add alarm support
2025-07-15 8:50 ` Andy Shevchenko
@ 2025-07-15 16:20 ` Sean Anderson
2025-07-16 10:08 ` Andy Shevchenko
0 siblings, 1 reply; 62+ messages in thread
From: Sean Anderson @ 2025-07-15 16:20 UTC (permalink / raw)
To: Andy Shevchenko
Cc: Jonathan Cameron, Jean Delvare, Guenter Roeck, linux-iio,
linux-hwmon, Andy Shevchenko, Nuno Sá, linux-kernel,
David Lechner
On 7/15/25 04:50, Andy Shevchenko wrote:
> On Mon, Jul 14, 2025 at 09:20:23PM -0400, Sean Anderson wrote:
>> Add alarm support based on IIO threshold events. The alarm is cleared on
>> read, but will be set again if the condition is still present. This is
>> detected by disabling and re-enabling the event. The same trick is done
>> when creating the attribute to detect already-triggered events.
>>
>> The alarms are updated by an event listener. To keep the notifier call
>> chain short, we create one listener per iio device, shared across all
>> hwmon devices.
>>
>> To avoid dynamic creation of alarms, alarms for all possible events are
>> allocated at creation. Lookup is done by a linear scan, as I expect
>> events to occur rarely. If performance becomes an issue, a binary search
>> could be done instead (or some kind of hash lookup).
>
> ...
>
>> #include <linux/hwmon-sysfs.h>
>
> + blank line here..
why?
>> #include <linux/iio/consumer.h>
>> +#include <linux/iio/events.h>
>> +#include <linux/iio/iio.h>
>> #include <linux/iio/types.h>
>
> ...and here?
OK
>> +#include <uapi/linux/iio/events.h>
>
> ...
>
>> +static ssize_t iio_hwmon_lookup_alarm(struct iio_hwmon_listener *listener,
>> + u64 id)
>> +{
>> + ssize_t i;
>> +
>> + for (i = 0; i < listener->num_alarms; i++)
>> + if (listener->ids[i] == id)
>> + return i;
>
>> + return -1;
>
> -ENOENT ?
> This will allow to propagate an error code to the upper layer(s).
I suppose. But I think
alarm = iio_hwmon_lookup_alarm(...);
if (alarm < 0)
return -ENOENT;
is clearer than
alarm = iio_hwmon_lookup_alarm(...);
if (alarm < 0)
return alarm;
because you don't have to read the definition of iio_hwmon_lookup_alarm
to determine what the return value is.
>> +}
>
> ...
>
>> +static int iio_hwmon_listener_callback(struct notifier_block *block,
>> + unsigned long action, void *data)
>> +{
>> + struct iio_hwmon_listener *listener =
>> + container_of(block, struct iio_hwmon_listener, block);
>> + struct iio_event_data *ev = data;
>> + ssize_t i;
>> +
>> + if (action != IIO_NOTIFY_EVENT)
>> + return NOTIFY_DONE;
>> +
>> + i = iio_hwmon_lookup_alarm(listener, ev->id);
>> + if (i >= 0)
>> + set_bit(i, listener->alarms);
>
> Do you need an atomic set?
Yes. This protects against concurrent access by iio_hwmon_read_alarm.
>> + else
>> + dev_warn_once(&listener->indio_dev->dev,
>> + "unknown event %016llx\n", ev->id);
>> +
>> + return NOTIFY_DONE;
>> +}
>
> ...
>
>> +static struct iio_hwmon_listener *iio_hwmon_listener_get(struct iio_dev *indio_dev)
>> +{
>> + struct iio_hwmon_listener *listener;
>> + int err = -ENOMEM;
>> + size_t i, j;
>> +
>> + guard(mutex)(&iio_hwmon_listener_lock);
>> + list_for_each_entry(listener, &iio_hwmon_listeners, list) {
>> + if (listener->indio_dev == indio_dev) {
>> + if (likely(listener->refcnt != UINT_MAX))
>> + listener->refcnt++;
>> + return listener;
>> + }
>> + }
>> +
>> + listener = kzalloc(sizeof(*listener), GFP_KERNEL);
>> + if (!listener)
>> + goto err_unlock;
>> +
>> + listener->refcnt = 1;
>> + listener->indio_dev = indio_dev;
>> + listener->block.notifier_call = iio_hwmon_listener_callback;
>> + for (i = 0; i < indio_dev->num_channels; i++)
>> + listener->num_alarms += indio_dev->channels[i].num_event_specs;
>> +
>> + listener->ids = kcalloc(listener->num_alarms, sizeof(*listener->ids),
>> + GFP_KERNEL);
>> + listener->alarms = bitmap_zalloc(listener->num_alarms, GFP_KERNEL);
>> + if (!listener->ids || !listener->alarms)
>> + goto err_listener;
>> +
>> + i = 0;
>> + for (j = 0; j < indio_dev->num_channels; j++) {
>> + struct iio_chan_spec const *chan = &indio_dev->channels[j];
>> + size_t k;
>> +
>> + for (k = 0; k < chan->num_event_specs; k++)
>> + listener->ids[i++] =
>> + iio_event_id(chan, chan->event_spec[k].type,
>> + chan->event_spec[k].dir);
>> + }
>> +
>> + err = iio_event_register(indio_dev, &listener->block);
>> + if (err)
>> + goto err_alarms;
>> +
>> + list_add(&listener->list, &iio_hwmon_listeners);
>
>> + mutex_unlock(&iio_hwmon_listener_lock);
>
> With guard() ???
Whoops. Missed that when refactoring.
>> + return listener;
>> +
>> +err_alarms:
>> + kfree(listener->alarms);
>> + kfree(listener->ids);
>> +err_listener:
>> + kfree(listener);
>> +err_unlock:
>> + mutex_unlock(&iio_hwmon_listener_lock);
>> + return ERR_PTR(err);
>
> What about using __free()?
That works for listener, but not for alarms or ids.
>> +}
>
> ...
>
>> +static void iio_hwmon_listener_put(void *data)
>> +{
>> + struct iio_hwmon_listener *listener = data;
>> +
>> + scoped_guard(mutex, &iio_hwmon_listener_lock) {
>> + if (unlikely(listener->refcnt == UINT_MAX))
>> + return;
>> +
>> + if (--listener->refcnt)
>> + return;
>
> Can the refcount_t be used with the respective APIs? Or even kref?
Why? We do all the manipulation under a mutex, so there is no point in
atomic access. Instead of the games refcnt_t has to play to try and
prevent overflow we can just check for it directly.
>> + list_del(&listener->list);
>> + iio_event_unregister(listener->indio_dev, &listener->block);
>> + }
>> +
>> + kfree(listener->alarms);
>> + kfree(listener->ids);
>> + kfree(listener);
>> +}
>
> ...
>
>> +static ssize_t iio_hwmon_read_alarm(struct device *dev,
>> + struct device_attribute *attr,
>> + char *buf)
>> +{
>> + struct iio_hwmon_alarm_attribute *sattr = to_alarm_attr(attr);
>> + struct iio_hwmon_state *state = dev_get_drvdata(dev);
>> + struct iio_channel *chan = &state->channels[sattr->index];
>> +
>> + if (test_and_clear_bit(sattr->alarm, sattr->listener->alarms)) {
>> + u64 id = sattr->listener->ids[sattr->alarm];
>> + enum iio_event_direction dir = IIO_EVENT_CODE_EXTRACT_DIR(id);
>> +
>> + WARN_ON(iio_hwmon_alarm_toggle(chan, dir));
>
>> + strcpy(buf, "1\n");
>> + return 2;
>
>> + }
>> +
>> + strcpy(buf, "0\n");
>> + return 2;
>
> Better to assign the value and
>
> return sysfs_emit(...);
>
> which will make even easier to recognize that this is supplied to user via
> sysfs.
:l
the things we do to avoid memcpy...
--Sean
>> +}
>
> ...
>
>> +static int add_alarm_attr(struct device *dev, struct iio_hwmon_state *st,
>> + int i, enum iio_event_direction dir,
>> + const char *fmt, ...)
>
> Same comments as per previous patches.
>
^ permalink raw reply [flat|nested] 62+ messages in thread
* Re: [PATCH 3/7] iio: Add in-kernel API for events
2025-07-15 11:09 ` Nuno Sá
@ 2025-07-15 16:52 ` Sean Anderson
2025-07-27 16:21 ` Jonathan Cameron
0 siblings, 1 reply; 62+ messages in thread
From: Sean Anderson @ 2025-07-15 16:52 UTC (permalink / raw)
To: Nuno Sá, Jonathan Cameron, Jean Delvare, Guenter Roeck,
linux-iio, linux-hwmon
Cc: Andy Shevchenko, Nuno Sá, linux-kernel, David Lechner
On 7/15/25 07:09, Nuno Sá wrote:
> On Mon, 2025-07-14 at 21:20 -0400, Sean Anderson wrote:
>> Add an API to notify consumers about events. Events still need to be
>> enabled using the iio_read_event/iio_write_event functions. Of course,
>> userspace can also manipulate the enabled events. I don't think this is
>> too much of an issue, since userspace can also manipulate the event
>> thresholds. But enabling events may cause existing programs to be
>> surprised when they get something unexpected. Maybe we should set the
>> interface as busy when there are any in-kernel listeners?
>>
>
> Sensible question. I'm not that familiar with events but I suspect is not
> trivial (if doable) to do a similar approach as with buffers? With buffers, an
> inkernal consumer get's it's own buffer object (that goes into a list of active
> buffers in the iio device) with all channels enabled and then we demux the
> appropriate channels for each consumer.
For in-kernel consumers I think it's reasonable to expect them to handle
events they didn't explicitly enable. I'm not sure about userspace
consumers.
> Independent of the above, we can argue that having both inkernel and userspace
> changing thresholds is ok (I mean, there's nothing stopping two userspace apps
> doing that) but we should likely be careful with enabling/disabling. If multiple
> consumers enable the same event, one of them disabling it should not disable it
> for all the consumers, right?
Right now the HWMON consumer never permanently disable events to avoid this
issue. It does toggle the enable to determine if an alarm should stay
enabled:
________
condition __/ \________
_____ ____ ___
enable \__/ \__/
event | |
__ ____
alarm __/ \__/ \_____
read 1 1 0
I suppose this could also be done by comparing the raw threshold to the
channel.
--Sean
>
>> Signed-off-by: Sean Anderson <sean.anderson@linux.dev>
>> ---
>>
>> drivers/iio/industrialio-event.c | 34 +++++++++++++++++++++++++++-----
>> include/linux/iio/consumer.h | 30 ++++++++++++++++++++++++++++
>> 2 files changed, 59 insertions(+), 5 deletions(-)
>>
>> diff --git a/drivers/iio/industrialio-event.c b/drivers/iio/industrialio-
>> event.c
>> index 06295cfc2da8..b9e3ff1cd5c9 100644
>> --- a/drivers/iio/industrialio-event.c
>> +++ b/drivers/iio/industrialio-event.c
>> @@ -12,11 +12,13 @@
>> #include <linux/kernel.h>
>> #include <linux/kfifo.h>
>> #include <linux/module.h>
>> +#include <linux/notifier.h>
>> #include <linux/poll.h>
>> #include <linux/sched.h>
>> #include <linux/slab.h>
>> #include <linux/uaccess.h>
>> #include <linux/wait.h>
>> +#include <linux/iio/consumer.h>
>> #include <linux/iio/iio.h>
>> #include <linux/iio/iio-opaque.h>
>> #include "iio_core.h"
>> @@ -26,6 +28,7 @@
>> /**
>> * struct iio_event_interface - chrdev interface for an event line
>> * @wait: wait queue to allow blocking reads of events
>> + * @notifier: notifier head for in-kernel event listeners
>> * @det_events: list of detected events
>> * @dev_attr_list: list of event interface sysfs attribute
>> * @flags: file operations related flags including busy flag.
>> @@ -35,6 +38,7 @@
>> */
>> struct iio_event_interface {
>> wait_queue_head_t wait;
>> + struct atomic_notifier_head notifier;
>> DECLARE_KFIFO(det_events, struct iio_event_data, 16);
>>
>> struct list_head dev_attr_list;
>> @@ -67,18 +71,19 @@ int iio_push_event(struct iio_dev *indio_dev, u64 ev_code,
>> s64 timestamp)
>> {
>> struct iio_dev_opaque *iio_dev_opaque = to_iio_dev_opaque(indio_dev);
>> struct iio_event_interface *ev_int = iio_dev_opaque->event_interface;
>> - struct iio_event_data ev;
>> + struct iio_event_data ev = {
>> + .id = ev_code,
>> + .timestamp = timestamp,
>> + };
>> int copied;
>>
>> if (!ev_int)
>> return 0;
>>
>> + atomic_notifier_call_chain(&ev_int->notifier, IIO_NOTIFY_EVENT, &ev);
>> +
>> /* Does anyone care? */
>> if (iio_event_enabled(ev_int)) {
>> -
>> - ev.id = ev_code;
>> - ev.timestamp = timestamp;
>> -
>> copied = kfifo_put(&ev_int->det_events, ev);
>> if (copied != 0)
>> wake_up_poll(&ev_int->wait, EPOLLIN);
>> @@ -223,6 +228,25 @@ static int iio_event_getfd(struct iio_dev *indio_dev)
>> return fd;
>> }
>>
>> +int iio_event_register(struct iio_dev *indio_dev, struct notifier_block
>> *block)
>> +{
>> + struct iio_dev_opaque *iio_dev_opaque = to_iio_dev_opaque(indio_dev);
>> + struct iio_event_interface *ev_int = iio_dev_opaque->event_interface;
>> +
>> + return atomic_notifier_chain_register(&ev_int->notifier, block);
>> +}
>> +EXPORT_SYMBOL_GPL(iio_event_register);
>> +
>> +void iio_event_unregister(struct iio_dev *indio_dev,
>> + struct notifier_block *block)
>> +{
>> + struct iio_dev_opaque *iio_dev_opaque = to_iio_dev_opaque(indio_dev);
>> + struct iio_event_interface *ev_int = iio_dev_opaque->event_interface;
>> +
>> + WARN_ON(atomic_notifier_chain_unregister(&ev_int->notifier, block));
>> +}
>> +EXPORT_SYMBOL_GPL(iio_event_unregister);
>> +
>> static const char * const iio_ev_type_text[] = {
>> [IIO_EV_TYPE_THRESH] = "thresh",
>> [IIO_EV_TYPE_MAG] = "mag",
>> diff --git a/include/linux/iio/consumer.h b/include/linux/iio/consumer.h
>> index 16e7682474f3..9918e3f7af3d 100644
>> --- a/include/linux/iio/consumer.h
>> +++ b/include/linux/iio/consumer.h
>> @@ -507,4 +507,34 @@ int iio_write_event_processed_scale(struct iio_channel
>> *chan,
>> enum iio_event_info info, int processed,
>> unsigned int scale);
>>
>> +struct notifier_block;
>> +enum iio_notifier_val {
>> + /** IIO_NOTIFY_EVENT: v is a pointer to &struct iio_event_data */
>> + IIO_NOTIFY_EVENT,
>> +};
>> +
>> +/**
>> + * iio_event_register() - Register a notifier for events
>> + * @indio_dev: Device to be notified of events on
>> + * @block: Notifier block to register
>> + *
>> + * Register a notifier for events on @indio_dev. @v will be a member of &enum
>> + * iio_notifier_val. Notifiers will be called in atomic context. @indio_dev
>> + * must stay valid until you call iio_event_unregister().
>> + *
>> + * Return: 0 on success, or -EEXIST if @block has already been registered
>> + */
>> +int iio_event_register(struct iio_dev *indio_dev,
>> + struct notifier_block *block);
>> +
>> +/**
>> + * iio_event_unregister() - Remove a previously-added notifier
>> + * @indio_dev: Device to be notified of events on
>> + * @block: Notifier previously-registered with iio_event_register()
>> + *
>> + * Remove a previously-added notifier.
>> + */
>> +void iio_event_unregister(struct iio_dev *indio_dev,
>> + struct notifier_block *block);
>> +
>> #endif
^ permalink raw reply [flat|nested] 62+ messages in thread
* Re: [PATCH 7/7] hwmon: iio: Add alarm support
2025-07-15 11:28 ` Nuno Sá
@ 2025-07-15 17:02 ` Sean Anderson
2025-07-15 19:26 ` Guenter Roeck
2025-07-16 6:37 ` Nuno Sá
0 siblings, 2 replies; 62+ messages in thread
From: Sean Anderson @ 2025-07-15 17:02 UTC (permalink / raw)
To: Nuno Sá, Jonathan Cameron, Jean Delvare, Guenter Roeck,
linux-iio, linux-hwmon
Cc: Andy Shevchenko, Nuno Sá, linux-kernel, David Lechner
On 7/15/25 07:28, Nuno Sá wrote:
> On Mon, 2025-07-14 at 21:20 -0400, Sean Anderson wrote:
>> Add alarm support based on IIO threshold events. The alarm is cleared on
>> read, but will be set again if the condition is still present. This is
>> detected by disabling and re-enabling the event. The same trick is done
>> when creating the attribute to detect already-triggered events.
>>
>> The alarms are updated by an event listener. To keep the notifier call
>> chain short, we create one listener per iio device, shared across all
>> hwmon devices.
>>
>> To avoid dynamic creation of alarms, alarms for all possible events are
>> allocated at creation. Lookup is done by a linear scan, as I expect
>> events to occur rarely. If performance becomes an issue, a binary search
>> could be done instead (or some kind of hash lookup).
>>
>> Signed-off-by: Sean Anderson <sean.anderson@linux.dev>
>> ---
>>
>> drivers/hwmon/iio_hwmon.c | 322 +++++++++++++++++++++++++++++++++++++-
>> 1 file changed, 321 insertions(+), 1 deletion(-)
>>
>> diff --git a/drivers/hwmon/iio_hwmon.c b/drivers/hwmon/iio_hwmon.c
>> index 3db4d4b30022..c963bc5452ba 100644
>> --- a/drivers/hwmon/iio_hwmon.c
>> +++ b/drivers/hwmon/iio_hwmon.c
>> @@ -8,6 +8,7 @@
>> #include <linux/slab.h>
>> #include <linux/mod_devicetable.h>
>> #include <linux/module.h>
>> +#include <linux/notifier.h>
>> #include <linux/err.h>
>> #include <linux/platform_device.h>
>> #include <linux/property.h>
>> @@ -15,7 +16,192 @@
>> #include <linux/hwmon.h>
>> #include <linux/hwmon-sysfs.h>
>> #include <linux/iio/consumer.h>
>> +#include <linux/iio/events.h>
>> +#include <linux/iio/iio.h>
>> #include <linux/iio/types.h>
>> +#include <uapi/linux/iio/events.h>
>> +
>> +/* Protects iio_hwmon_listeners and listeners' refcnt */
>> +DEFINE_MUTEX(iio_hwmon_listener_lock);
>> +LIST_HEAD(iio_hwmon_listeners);
>> +
>> +/**
>> + * struct iio_hwmon_listener - Listener for IIO events
>> + * @block: Notifier for events
>> + * @ids: Array of IIO event ids, one per alarm
>> + * @alarms: Bitmap of alarms
>> + * @num_alarms: Length of @ids and @alarms
>> + * @indio_dev: Device we are listening to
>> + * @list: List of all listeners
>> + * @refcnt: Reference count
>> + */
>> +struct iio_hwmon_listener {
>> + struct notifier_block block;
>> + u64 *ids;
>> + unsigned long *alarms;
>> + size_t num_alarms;
>> +
>> + struct iio_dev *indio_dev;
>> + struct list_head list;
>> + unsigned int refcnt;
>> +};
>> +
>> +/**
>> + * iio_hwmon_lookup_alarm() - Find an alarm by id
>> + * @listener: Event listener
>> + * @id: IIO event id
>> + *
>> + * Return: index of @id in @listener->ids, or -1 if not found
>> + */
>> +static ssize_t iio_hwmon_lookup_alarm(struct iio_hwmon_listener *listener,
>> + u64 id)
>> +{
>> + ssize_t i;
>> +
>> + for (i = 0; i < listener->num_alarms; i++)
>> + if (listener->ids[i] == id)
>> + return i;
>> +
>> + return -1;
>> +}
>> +
>> +static int iio_hwmon_listener_callback(struct notifier_block *block,
>> + unsigned long action, void *data)
>> +{
>> + struct iio_hwmon_listener *listener =
>> + container_of(block, struct iio_hwmon_listener, block);
>> + struct iio_event_data *ev = data;
>> + ssize_t i;
>> +
>> + if (action != IIO_NOTIFY_EVENT)
>> + return NOTIFY_DONE;
>> +
>> + i = iio_hwmon_lookup_alarm(listener, ev->id);
>> + if (i >= 0)
>> + set_bit(i, listener->alarms);
>> + else
>> + dev_warn_once(&listener->indio_dev->dev,
>> + "unknown event %016llx\n", ev->id);
>> +
>> + return NOTIFY_DONE;
>> +}
>> +
>> +/**
>> + * iio_event_id() - Calculate an IIO event id
>> + * @channel: IIO channel for this event
>> + * @type: Event type (theshold, rate-of-change, etc.)
>> + * @dir: Event direction (rising, falling, etc.)
>> + *
>> + * Return: IIO event id corresponding to this event's IIO id
>> + */
>> +static u64 iio_event_id(struct iio_chan_spec const *chan,
>> + enum iio_event_type type,
>> + enum iio_event_direction dir)
>> +{
>> + if (chan->differential)
>> + return IIO_DIFF_EVENT_CODE(chan->type, chan->channel,
>> + chan->channel2, type, dir);
>> + if (chan->modified)
>> + return IIO_MOD_EVENT_CODE(chan->type, chan->channel,
>> + chan->channel2, type, dir);
>> + return IIO_UNMOD_EVENT_CODE(chan->type, chan->channel, type, dir);
>> +}
>> +
>> +/**
>> + * iio_hwmon_listener_get() - Get a listener for an IIO device
>> + * @indio_dev: IIO device to listen to
>> + *
>> + * Look up or create a new listener for @indio_dev. The returned listener is
>> + * registered with @indio_dev, but events still need to be manually enabled.
>> + * You must call iio_hwmon_listener_put() when you are done.
>> + *
>> + * Return: Listener for @indio_dev, or an error pointer
>> + */
>> +static struct iio_hwmon_listener *iio_hwmon_listener_get(struct iio_dev
>> *indio_dev)
>> +{
>> + struct iio_hwmon_listener *listener;
>> + int err = -ENOMEM;
>> + size_t i, j;
>> +
>> + guard(mutex)(&iio_hwmon_listener_lock);
>> + list_for_each_entry(listener, &iio_hwmon_listeners, list) {
>> + if (listener->indio_dev == indio_dev) {
>> + if (likely(listener->refcnt != UINT_MAX))
>> + listener->refcnt++;
>
> I dunno for the above to ever happen :).
Well, I can remove it if you like.
> And as Andy stated, let's just use proper refcount APIs.
No point in using atomic ops if they are only accessed under a mutex.
>> + return listener;
>> + }
>> + }
>> +
>> + listener = kzalloc(sizeof(*listener), GFP_KERNEL);
>> + if (!listener)
>> + goto err_unlock;
>> +
>> + listener->refcnt = 1;
>> + listener->indio_dev = indio_dev;
>> + listener->block.notifier_call = iio_hwmon_listener_callback;
>> + for (i = 0; i < indio_dev->num_channels; i++)
>> + listener->num_alarms += indio_dev-
>> >channels[i].num_event_specs;
>> +
>> + listener->ids = kcalloc(listener->num_alarms, sizeof(*listener->ids),
>> + GFP_KERNEL);
>> + listener->alarms = bitmap_zalloc(listener->num_alarms, GFP_KERNEL);
>> + if (!listener->ids || !listener->alarms)
>> + goto err_listener;
>> +
>> + i = 0;
>> + for (j = 0; j < indio_dev->num_channels; j++) {
>> + struct iio_chan_spec const *chan = &indio_dev->channels[j];
>> + size_t k;
>> +
>> + for (k = 0; k < chan->num_event_specs; k++)
>> + listener->ids[i++] =
>> + iio_event_id(chan, chan->event_spec[k].type,
>> + chan->event_spec[k].dir);
>> + }
>> +
>> + err = iio_event_register(indio_dev, &listener->block);
>> + if (err)
>> + goto err_alarms;
>> +
>> + list_add(&listener->list, &iio_hwmon_listeners);
>> + mutex_unlock(&iio_hwmon_listener_lock);
>> + return listener;
>> +
>> +err_alarms:
>> + kfree(listener->alarms);
>> + kfree(listener->ids);
>> +err_listener:
>> + kfree(listener);
>> +err_unlock:
>> + mutex_unlock(&iio_hwmon_listener_lock);
>> + return ERR_PTR(err);
>> +}
>> +
>> +/**
>> + * iio_hwmon_listener_put() - Release a listener
>> + * @data: &struct iio_hwmon_listener to release
>> + *
>> + * For convenience, @data is void.
>> + */
>> +static void iio_hwmon_listener_put(void *data)
>> +{
>> + struct iio_hwmon_listener *listener = data;
>> +
>> + scoped_guard(mutex, &iio_hwmon_listener_lock) {
>> + if (unlikely(listener->refcnt == UINT_MAX))
>> + return;
>> +
>> + if (--listener->refcnt)
>> + return;
>> +
>> + list_del(&listener->list);
>> + iio_event_unregister(listener->indio_dev, &listener->block);
>> + }
>> +
>> + kfree(listener->alarms);
>> + kfree(listener->ids);
>> + kfree(listener);
>> +}
>>
>> /**
>> * struct iio_hwmon_state - device instance state
>> @@ -143,6 +329,68 @@ static ssize_t iio_hwmon_write_event(struct device *dev,
>> return count;
>> }
>>
>> +/**
>> + * struct iio_hwmon_alarm_attribute - IIO HWMON alarm attribute
>> + * @dev_attr: Base device attribute
>> + * @listener: Listener for this alarm
>> + * @index: Index of the channel in the IIO HWMON
>> + * @alarm: Index of the alarm within @listener
>> + */
>> +struct iio_hwmon_alarm_attribute {
>> + struct device_attribute dev_attr;
>> + struct iio_hwmon_listener *listener;
>> + size_t index;
>> + size_t alarm;
>> +};
>> +#define to_alarm_attr(_dev_attr) \
>> + container_of(_dev_attr, struct iio_hwmon_alarm_attribute, dev_attr)
>> +
>> +/**
>> + * iio_hwmon_alarm_toggle() - Turn an event off and back on again
>> + * @chan: Channel of the event
>> + * @dir: Event direction (rising, falling, etc.)
>> + *
>> + * Toggle an event's enable so we get notified if the alarm is already
>> + * triggered. We use this to convert IIO's event-triggered events into
>> + * level-triggered alarms.
>> + *
>> + * Return: 0 on success or negative error on failure
>> + */
>> +static int iio_hwmon_alarm_toggle(struct iio_channel *chan,
>> + enum iio_event_direction dir)
>> +{
>> + int ret;
>> +
>> + ret = iio_write_event_processed_scale(chan, IIO_EV_TYPE_THRESH, dir,
>> + IIO_EV_INFO_ENABLE, 0, 1);
>> + if (ret)
>> + return ret;
>> +
>> + return iio_write_event_processed_scale(chan, IIO_EV_TYPE_THRESH, dir,
>> + IIO_EV_INFO_ENABLE, 1, 1);
>> +}
>> +
>> +static ssize_t iio_hwmon_read_alarm(struct device *dev,
>> + struct device_attribute *attr,
>> + char *buf)
>> +{
>> + struct iio_hwmon_alarm_attribute *sattr = to_alarm_attr(attr);
>> + struct iio_hwmon_state *state = dev_get_drvdata(dev);
>> + struct iio_channel *chan = &state->channels[sattr->index];
>> +
>> + if (test_and_clear_bit(sattr->alarm, sattr->listener->alarms)) {
>> + u64 id = sattr->listener->ids[sattr->alarm];
>> + enum iio_event_direction dir =
>> IIO_EVENT_CODE_EXTRACT_DIR(id);
>> +
>> + WARN_ON(iio_hwmon_alarm_toggle(chan, dir));
>
> WARN_ON() is highly discouraged... Also do we really need a "scary" splat in
> this case?
OK, maybe dev_warn then. I don't want to propagate the error because I think
it's more important to tell userspace that the alarm went off than if there
was a problem determining if the alarm is still active.
>> + strcpy(buf, "1\n");
>> + return 2;
>> + }
>> +
>> + strcpy(buf, "0\n");
>> + return 2;
>
> As stated, sysfs_emit()
>
>> +}
>> +
>> static int add_device_attr(struct device *dev, struct iio_hwmon_state *st,
>> ssize_t (*show)(struct device *dev,
>> struct device_attribute *attr,
>> @@ -205,6 +453,63 @@ static int add_event_attr(struct device *dev, struct
>> iio_hwmon_state *st,
>> return 0;
>> }
>>
>> +static int add_alarm_attr(struct device *dev, struct iio_hwmon_state *st,
>> + int i, enum iio_event_direction dir,
>> + const char *fmt, ...)
>> +{
>> + struct iio_hwmon_alarm_attribute *a;
>> + struct iio_hwmon_listener *listener;
>> + ssize_t alarm;
>> + umode_t mode;
>> + va_list ap;
>> + int ret;
>> +
>> + mode = iio_event_mode(&st->channels[i], IIO_EV_TYPE_THRESH, dir,
>> + IIO_EV_INFO_ENABLE);
>> + if (!(mode & 0200))
>> + return 0;
>> +
>> + listener = iio_hwmon_listener_get(st->channels[i].indio_dev);
>> + if (listener == ERR_PTR(-EBUSY))
>> + return 0;
>
> Maybe I missed something, where can we get -EBUSY? And should we ignore it?
Oh, this was from before I refactored the notification API to allow kernel
consumers to co-exist with userspace ones. So this can't occur.
>> + if (IS_ERR(listener))
>> + return PTR_ERR(listener);
>> +
>> + ret = devm_add_action_or_reset(dev, iio_hwmon_listener_put,
>> listener);
>> + if (ret)
>> + return ret;
>> +
>> + alarm = iio_hwmon_lookup_alarm(listener,
>> + iio_event_id(st->channels[i].channel,
>> + IIO_EV_TYPE_THRESH,
>> dir));
>> + if (WARN_ON_ONCE(alarm < 0))
>> + return -ENOENT;
>> +
>
> Again, I would drop WARN_ON_ONCE()
This can only occur if there is a bug in the kernel. We should have returned
0 from iio_event_mode() before we get to this point.
--Sean
^ permalink raw reply [flat|nested] 62+ messages in thread
* Re: [PATCH 1/7] math64: Add div64_s64_rem
2025-07-15 8:03 ` Andy Shevchenko
@ 2025-07-15 17:36 ` Sean Anderson
2025-07-16 10:15 ` Andy Shevchenko
0 siblings, 1 reply; 62+ messages in thread
From: Sean Anderson @ 2025-07-15 17:36 UTC (permalink / raw)
To: Andy Shevchenko
Cc: Jonathan Cameron, Jean Delvare, Guenter Roeck, linux-iio,
linux-hwmon, Andy Shevchenko, Nuno Sá, linux-kernel,
David Lechner
On 7/15/25 04:03, Andy Shevchenko wrote:
> On Mon, Jul 14, 2025 at 09:20:17PM -0400, Sean Anderson wrote:
>> Add a function to do signed 64-bit division with remainder. This is
>> implemented using div64_u64_rem in the same way that div_s64_rem is
>> implemented using div_u64_rem.
>
> LGTM, but one important Q. Can we (start to) add the test cases, please?
>
Well, this just calls div64_u64_rem. So I am inclined to make the test something
like
#define test(n, d, q, r) ({ \
u64 _q, _r; \
_q = div64_u64_rem(n, d, &r); \
assert(_q == q); \
assert(_r == r); \
})
test( 3, 2, 1, 1);
test( 3, -2, -1, 1);
test(-3, 2, -1, -1);
test(-3, -2, 1, -1);
--Sean
^ permalink raw reply [flat|nested] 62+ messages in thread
* Re: [PATCH 7/7] hwmon: iio: Add alarm support
2025-07-15 17:02 ` Sean Anderson
@ 2025-07-15 19:26 ` Guenter Roeck
2025-07-15 19:40 ` Sean Anderson
2025-07-16 6:37 ` Nuno Sá
1 sibling, 1 reply; 62+ messages in thread
From: Guenter Roeck @ 2025-07-15 19:26 UTC (permalink / raw)
To: Sean Anderson, Nuno Sá, Jonathan Cameron, Jean Delvare,
linux-iio, linux-hwmon
Cc: Andy Shevchenko, Nuno Sá, linux-kernel, David Lechner
On 7/15/25 10:02, Sean Anderson wrote:
> On 7/15/25 07:28, Nuno Sá wrote:
>> On Mon, 2025-07-14 at 21:20 -0400, Sean Anderson wrote:
>>> Add alarm support based on IIO threshold events. The alarm is cleared on
>>> read, but will be set again if the condition is still present. This is
>>> detected by disabling and re-enabling the event. The same trick is done
>>> when creating the attribute to detect already-triggered events.
>>>
>>> The alarms are updated by an event listener. To keep the notifier call
>>> chain short, we create one listener per iio device, shared across all
>>> hwmon devices.
>>>
>>> To avoid dynamic creation of alarms, alarms for all possible events are
>>> allocated at creation. Lookup is done by a linear scan, as I expect
>>> events to occur rarely. If performance becomes an issue, a binary search
>>> could be done instead (or some kind of hash lookup).
>>>
>>> Signed-off-by: Sean Anderson <sean.anderson@linux.dev>
>>> ---
>>>
>>> drivers/hwmon/iio_hwmon.c | 322 +++++++++++++++++++++++++++++++++++++-
>>> 1 file changed, 321 insertions(+), 1 deletion(-)
>>>
>>> diff --git a/drivers/hwmon/iio_hwmon.c b/drivers/hwmon/iio_hwmon.c
>>> index 3db4d4b30022..c963bc5452ba 100644
>>> --- a/drivers/hwmon/iio_hwmon.c
>>> +++ b/drivers/hwmon/iio_hwmon.c
>>> @@ -8,6 +8,7 @@
>>> #include <linux/slab.h>
>>> #include <linux/mod_devicetable.h>
>>> #include <linux/module.h>
>>> +#include <linux/notifier.h>
>>> #include <linux/err.h>
>>> #include <linux/platform_device.h>
>>> #include <linux/property.h>
>>> @@ -15,7 +16,192 @@
>>> #include <linux/hwmon.h>
>>> #include <linux/hwmon-sysfs.h>
>>> #include <linux/iio/consumer.h>
>>> +#include <linux/iio/events.h>
>>> +#include <linux/iio/iio.h>
>>> #include <linux/iio/types.h>
>>> +#include <uapi/linux/iio/events.h>
>>> +
>>> +/* Protects iio_hwmon_listeners and listeners' refcnt */
>>> +DEFINE_MUTEX(iio_hwmon_listener_lock);
>>> +LIST_HEAD(iio_hwmon_listeners);
>>> +
>>> +/**
>>> + * struct iio_hwmon_listener - Listener for IIO events
>>> + * @block: Notifier for events
>>> + * @ids: Array of IIO event ids, one per alarm
>>> + * @alarms: Bitmap of alarms
>>> + * @num_alarms: Length of @ids and @alarms
>>> + * @indio_dev: Device we are listening to
>>> + * @list: List of all listeners
>>> + * @refcnt: Reference count
>>> + */
>>> +struct iio_hwmon_listener {
>>> + struct notifier_block block;
>>> + u64 *ids;
>>> + unsigned long *alarms;
>>> + size_t num_alarms;
>>> +
>>> + struct iio_dev *indio_dev;
>>> + struct list_head list;
>>> + unsigned int refcnt;
>>> +};
>>> +
>>> +/**
>>> + * iio_hwmon_lookup_alarm() - Find an alarm by id
>>> + * @listener: Event listener
>>> + * @id: IIO event id
>>> + *
>>> + * Return: index of @id in @listener->ids, or -1 if not found
>>> + */
>>> +static ssize_t iio_hwmon_lookup_alarm(struct iio_hwmon_listener *listener,
>>> + u64 id)
>>> +{
>>> + ssize_t i;
>>> +
>>> + for (i = 0; i < listener->num_alarms; i++)
>>> + if (listener->ids[i] == id)
>>> + return i;
>>> +
>>> + return -1;
>>> +}
>>> +
>>> +static int iio_hwmon_listener_callback(struct notifier_block *block,
>>> + unsigned long action, void *data)
>>> +{
>>> + struct iio_hwmon_listener *listener =
>>> + container_of(block, struct iio_hwmon_listener, block);
>>> + struct iio_event_data *ev = data;
>>> + ssize_t i;
>>> +
>>> + if (action != IIO_NOTIFY_EVENT)
>>> + return NOTIFY_DONE;
>>> +
>>> + i = iio_hwmon_lookup_alarm(listener, ev->id);
>>> + if (i >= 0)
>>> + set_bit(i, listener->alarms);
>>> + else
>>> + dev_warn_once(&listener->indio_dev->dev,
>>> + "unknown event %016llx\n", ev->id);
>>> +
>>> + return NOTIFY_DONE;
>>> +}
>>> +
>>> +/**
>>> + * iio_event_id() - Calculate an IIO event id
>>> + * @channel: IIO channel for this event
>>> + * @type: Event type (theshold, rate-of-change, etc.)
>>> + * @dir: Event direction (rising, falling, etc.)
>>> + *
>>> + * Return: IIO event id corresponding to this event's IIO id
>>> + */
>>> +static u64 iio_event_id(struct iio_chan_spec const *chan,
>>> + enum iio_event_type type,
>>> + enum iio_event_direction dir)
>>> +{
>>> + if (chan->differential)
>>> + return IIO_DIFF_EVENT_CODE(chan->type, chan->channel,
>>> + chan->channel2, type, dir);
>>> + if (chan->modified)
>>> + return IIO_MOD_EVENT_CODE(chan->type, chan->channel,
>>> + chan->channel2, type, dir);
>>> + return IIO_UNMOD_EVENT_CODE(chan->type, chan->channel, type, dir);
>>> +}
>>> +
>>> +/**
>>> + * iio_hwmon_listener_get() - Get a listener for an IIO device
>>> + * @indio_dev: IIO device to listen to
>>> + *
>>> + * Look up or create a new listener for @indio_dev. The returned listener is
>>> + * registered with @indio_dev, but events still need to be manually enabled.
>>> + * You must call iio_hwmon_listener_put() when you are done.
>>> + *
>>> + * Return: Listener for @indio_dev, or an error pointer
>>> + */
>>> +static struct iio_hwmon_listener *iio_hwmon_listener_get(struct iio_dev
>>> *indio_dev)
>>> +{
>>> + struct iio_hwmon_listener *listener;
>>> + int err = -ENOMEM;
>>> + size_t i, j;
>>> +
>>> + guard(mutex)(&iio_hwmon_listener_lock);
>>> + list_for_each_entry(listener, &iio_hwmon_listeners, list) {
>>> + if (listener->indio_dev == indio_dev) {
>>> + if (likely(listener->refcnt != UINT_MAX))
>>> + listener->refcnt++;
>>
>> I dunno for the above to ever happen :).
>
> Well, I can remove it if you like.
>
>> And as Andy stated, let's just use proper refcount APIs.
>
> No point in using atomic ops if they are only accessed under a mutex.
>
>>> + return listener;
>>> + }
>>> + }
>>> +
>>> + listener = kzalloc(sizeof(*listener), GFP_KERNEL);
>>> + if (!listener)
>>> + goto err_unlock;
>>> +
>>> + listener->refcnt = 1;
>>> + listener->indio_dev = indio_dev;
>>> + listener->block.notifier_call = iio_hwmon_listener_callback;
>>> + for (i = 0; i < indio_dev->num_channels; i++)
>>> + listener->num_alarms += indio_dev-
>>>> channels[i].num_event_specs;
>>> +
>>> + listener->ids = kcalloc(listener->num_alarms, sizeof(*listener->ids),
>>> + GFP_KERNEL);
>>> + listener->alarms = bitmap_zalloc(listener->num_alarms, GFP_KERNEL);
>>> + if (!listener->ids || !listener->alarms)
>>> + goto err_listener;
>>> +
>>> + i = 0;
>>> + for (j = 0; j < indio_dev->num_channels; j++) {
>>> + struct iio_chan_spec const *chan = &indio_dev->channels[j];
>>> + size_t k;
>>> +
>>> + for (k = 0; k < chan->num_event_specs; k++)
>>> + listener->ids[i++] =
>>> + iio_event_id(chan, chan->event_spec[k].type,
>>> + chan->event_spec[k].dir);
>>> + }
>>> +
>>> + err = iio_event_register(indio_dev, &listener->block);
>>> + if (err)
>>> + goto err_alarms;
>>> +
>>> + list_add(&listener->list, &iio_hwmon_listeners);
>>> + mutex_unlock(&iio_hwmon_listener_lock);
>>> + return listener;
>>> +
>>> +err_alarms:
>>> + kfree(listener->alarms);
>>> + kfree(listener->ids);
>>> +err_listener:
>>> + kfree(listener);
>>> +err_unlock:
>>> + mutex_unlock(&iio_hwmon_listener_lock);
>>> + return ERR_PTR(err);
>>> +}
>>> +
>>> +/**
>>> + * iio_hwmon_listener_put() - Release a listener
>>> + * @data: &struct iio_hwmon_listener to release
>>> + *
>>> + * For convenience, @data is void.
>>> + */
>>> +static void iio_hwmon_listener_put(void *data)
>>> +{
>>> + struct iio_hwmon_listener *listener = data;
>>> +
>>> + scoped_guard(mutex, &iio_hwmon_listener_lock) {
>>> + if (unlikely(listener->refcnt == UINT_MAX))
>>> + return;
>>> +
>>> + if (--listener->refcnt)
>>> + return;
>>> +
>>> + list_del(&listener->list);
>>> + iio_event_unregister(listener->indio_dev, &listener->block);
>>> + }
>>> +
>>> + kfree(listener->alarms);
>>> + kfree(listener->ids);
>>> + kfree(listener);
>>> +}
>>>
>>> /**
>>> * struct iio_hwmon_state - device instance state
>>> @@ -143,6 +329,68 @@ static ssize_t iio_hwmon_write_event(struct device *dev,
>>> return count;
>>> }
>>>
>>> +/**
>>> + * struct iio_hwmon_alarm_attribute - IIO HWMON alarm attribute
>>> + * @dev_attr: Base device attribute
>>> + * @listener: Listener for this alarm
>>> + * @index: Index of the channel in the IIO HWMON
>>> + * @alarm: Index of the alarm within @listener
>>> + */
>>> +struct iio_hwmon_alarm_attribute {
>>> + struct device_attribute dev_attr;
>>> + struct iio_hwmon_listener *listener;
>>> + size_t index;
>>> + size_t alarm;
>>> +};
>>> +#define to_alarm_attr(_dev_attr) \
>>> + container_of(_dev_attr, struct iio_hwmon_alarm_attribute, dev_attr)
>>> +
>>> +/**
>>> + * iio_hwmon_alarm_toggle() - Turn an event off and back on again
>>> + * @chan: Channel of the event
>>> + * @dir: Event direction (rising, falling, etc.)
>>> + *
>>> + * Toggle an event's enable so we get notified if the alarm is already
>>> + * triggered. We use this to convert IIO's event-triggered events into
>>> + * level-triggered alarms.
>>> + *
>>> + * Return: 0 on success or negative error on failure
>>> + */
>>> +static int iio_hwmon_alarm_toggle(struct iio_channel *chan,
>>> + enum iio_event_direction dir)
>>> +{
>>> + int ret;
>>> +
>>> + ret = iio_write_event_processed_scale(chan, IIO_EV_TYPE_THRESH, dir,
>>> + IIO_EV_INFO_ENABLE, 0, 1);
>>> + if (ret)
>>> + return ret;
>>> +
>>> + return iio_write_event_processed_scale(chan, IIO_EV_TYPE_THRESH, dir,
>>> + IIO_EV_INFO_ENABLE, 1, 1);
>>> +}
>>> +
>>> +static ssize_t iio_hwmon_read_alarm(struct device *dev,
>>> + struct device_attribute *attr,
>>> + char *buf)
>>> +{
>>> + struct iio_hwmon_alarm_attribute *sattr = to_alarm_attr(attr);
>>> + struct iio_hwmon_state *state = dev_get_drvdata(dev);
>>> + struct iio_channel *chan = &state->channels[sattr->index];
>>> +
>>> + if (test_and_clear_bit(sattr->alarm, sattr->listener->alarms)) {
>>> + u64 id = sattr->listener->ids[sattr->alarm];
>>> + enum iio_event_direction dir =
>>> IIO_EVENT_CODE_EXTRACT_DIR(id);
>>> +
>>> + WARN_ON(iio_hwmon_alarm_toggle(chan, dir));
>>
>> WARN_ON() is highly discouraged... Also do we really need a "scary" splat in
>> this case?
>
> OK, maybe dev_warn then. I don't want to propagate the error because I think
> it's more important to tell userspace that the alarm went off than if there
> was a problem determining if the alarm is still active.
>
Sorry, I will neither accept backtraces not warning messages in hwmon code.
That risks polluting the kernel log. Propagate the error. I fail to see
the problem with that.
>>> + strcpy(buf, "1\n");
>>> + return 2;
>>> + }
>>> +
>>> + strcpy(buf, "0\n");
>>> + return 2;
>>
>> As stated, sysfs_emit()
>>
>>> +}
>>> +
>>> static int add_device_attr(struct device *dev, struct iio_hwmon_state *st,
>>> ssize_t (*show)(struct device *dev,
>>> struct device_attribute *attr,
>>> @@ -205,6 +453,63 @@ static int add_event_attr(struct device *dev, struct
>>> iio_hwmon_state *st,
>>> return 0;
>>> }
>>>
>>> +static int add_alarm_attr(struct device *dev, struct iio_hwmon_state *st,
>>> + int i, enum iio_event_direction dir,
>>> + const char *fmt, ...)
>>> +{
>>> + struct iio_hwmon_alarm_attribute *a;
>>> + struct iio_hwmon_listener *listener;
>>> + ssize_t alarm;
>>> + umode_t mode;
>>> + va_list ap;
>>> + int ret;
>>> +
>>> + mode = iio_event_mode(&st->channels[i], IIO_EV_TYPE_THRESH, dir,
>>> + IIO_EV_INFO_ENABLE);
>>> + if (!(mode & 0200))
>>> + return 0;
>>> +
>>> + listener = iio_hwmon_listener_get(st->channels[i].indio_dev);
>>> + if (listener == ERR_PTR(-EBUSY))
>>> + return 0;
>>
>> Maybe I missed something, where can we get -EBUSY? And should we ignore it?
>
> Oh, this was from before I refactored the notification API to allow kernel
> consumers to co-exist with userspace ones. So this can't occur.
>
>>> + if (IS_ERR(listener))
>>> + return PTR_ERR(listener);
>>> +
>>> + ret = devm_add_action_or_reset(dev, iio_hwmon_listener_put,
>>> listener);
>>> + if (ret)
>>> + return ret;
>>> +
>>> + alarm = iio_hwmon_lookup_alarm(listener,
>>> + iio_event_id(st->channels[i].channel,
>>> + IIO_EV_TYPE_THRESH,
>>> dir));
>>> + if (WARN_ON_ONCE(alarm < 0))
>>> + return -ENOENT;
>>> +
>>
>> Again, I would drop WARN_ON_ONCE()
>
> This can only occur if there is a bug in the kernel. We should have returned
> 0 from iio_event_mode() before we get to this point.
>
Just return the error to the caller (without replacing the error).
Guenter
^ permalink raw reply [flat|nested] 62+ messages in thread
* Re: [PATCH 7/7] hwmon: iio: Add alarm support
2025-07-15 1:20 ` [PATCH 7/7] hwmon: iio: Add alarm support Sean Anderson
` (2 preceding siblings ...)
2025-07-15 16:13 ` kernel test robot
@ 2025-07-15 19:34 ` Guenter Roeck
2025-07-15 20:08 ` Sean Anderson
2025-07-16 7:44 ` kernel test robot
2025-07-27 16:50 ` Jonathan Cameron
5 siblings, 1 reply; 62+ messages in thread
From: Guenter Roeck @ 2025-07-15 19:34 UTC (permalink / raw)
To: Sean Anderson, Jonathan Cameron, Jean Delvare, linux-iio,
linux-hwmon
Cc: Andy Shevchenko, Nuno Sá, linux-kernel, David Lechner
On 7/14/25 18:20, Sean Anderson wrote:
> Add alarm support based on IIO threshold events. The alarm is cleared on
> read, but will be set again if the condition is still present. This is
> detected by disabling and re-enabling the event. The same trick is done
> when creating the attribute to detect already-triggered events.
>
> The alarms are updated by an event listener. To keep the notifier call
> chain short, we create one listener per iio device, shared across all
> hwmon devices.
>
> To avoid dynamic creation of alarms, alarms for all possible events are
> allocated at creation. Lookup is done by a linear scan, as I expect
> events to occur rarely. If performance becomes an issue, a binary search
> could be done instead (or some kind of hash lookup).
>
I am very concerned about this. The context suggests that the iio events
are just that - events without specific association to hardware or system
limits. Hardware monitoring limits are system specific limits, which are not
supposed to change at runtime. A high voltage or temperature warning is
just that - it is not supposed to trigger a change in the event limit.
If anything, it is supposed to trigger some action to bring the observed
value back to normal.
For this series to move forward, there needs to be some guarantee that
the limits are used and usable only as intended, and can not be used for
random thresholds. The idea of "if a temperature alarm is triggered, do
something and change the threshold temperature" is not an acceptable use
for hardware monitoring alarms.
Guenter
^ permalink raw reply [flat|nested] 62+ messages in thread
* Re: [PATCH 7/7] hwmon: iio: Add alarm support
2025-07-15 19:26 ` Guenter Roeck
@ 2025-07-15 19:40 ` Sean Anderson
0 siblings, 0 replies; 62+ messages in thread
From: Sean Anderson @ 2025-07-15 19:40 UTC (permalink / raw)
To: Guenter Roeck, Nuno Sá, Jonathan Cameron, Jean Delvare,
linux-iio, linux-hwmon
Cc: Andy Shevchenko, Nuno Sá, linux-kernel, David Lechner
On 7/15/25 15:26, Guenter Roeck wrote:
> On 7/15/25 10:02, Sean Anderson wrote:
>> On 7/15/25 07:28, Nuno Sá wrote:
>>> On Mon, 2025-07-14 at 21:20 -0400, Sean Anderson wrote:
>>>> Add alarm support based on IIO threshold events. The alarm is cleared on
>>>> read, but will be set again if the condition is still present. This is
>>>> detected by disabling and re-enabling the event. The same trick is done
>>>> when creating the attribute to detect already-triggered events.
>>>>
>>>> The alarms are updated by an event listener. To keep the notifier call
>>>> chain short, we create one listener per iio device, shared across all
>>>> hwmon devices.
>>>>
>>>> To avoid dynamic creation of alarms, alarms for all possible events are
>>>> allocated at creation. Lookup is done by a linear scan, as I expect
>>>> events to occur rarely. If performance becomes an issue, a binary search
>>>> could be done instead (or some kind of hash lookup).
>>>>
>>>> Signed-off-by: Sean Anderson <sean.anderson@linux.dev>
>>>> ---
>>>>
>>>> drivers/hwmon/iio_hwmon.c | 322 +++++++++++++++++++++++++++++++++++++-
>>>> 1 file changed, 321 insertions(+), 1 deletion(-)
>>>>
>>>> diff --git a/drivers/hwmon/iio_hwmon.c b/drivers/hwmon/iio_hwmon.c
>>>> index 3db4d4b30022..c963bc5452ba 100644
>>>> --- a/drivers/hwmon/iio_hwmon.c
>>>> +++ b/drivers/hwmon/iio_hwmon.c
>>>> @@ -8,6 +8,7 @@
>>>> #include <linux/slab.h>
>>>> #include <linux/mod_devicetable.h>
>>>> #include <linux/module.h>
>>>> +#include <linux/notifier.h>
>>>> #include <linux/err.h>
>>>> #include <linux/platform_device.h>
>>>> #include <linux/property.h>
>>>> @@ -15,7 +16,192 @@
>>>> #include <linux/hwmon.h>
>>>> #include <linux/hwmon-sysfs.h>
>>>> #include <linux/iio/consumer.h>
>>>> +#include <linux/iio/events.h>
>>>> +#include <linux/iio/iio.h>
>>>> #include <linux/iio/types.h>
>>>> +#include <uapi/linux/iio/events.h>
>>>> +
>>>> +/* Protects iio_hwmon_listeners and listeners' refcnt */
>>>> +DEFINE_MUTEX(iio_hwmon_listener_lock);
>>>> +LIST_HEAD(iio_hwmon_listeners);
>>>> +
>>>> +/**
>>>> + * struct iio_hwmon_listener - Listener for IIO events
>>>> + * @block: Notifier for events
>>>> + * @ids: Array of IIO event ids, one per alarm
>>>> + * @alarms: Bitmap of alarms
>>>> + * @num_alarms: Length of @ids and @alarms
>>>> + * @indio_dev: Device we are listening to
>>>> + * @list: List of all listeners
>>>> + * @refcnt: Reference count
>>>> + */
>>>> +struct iio_hwmon_listener {
>>>> + struct notifier_block block;
>>>> + u64 *ids;
>>>> + unsigned long *alarms;
>>>> + size_t num_alarms;
>>>> +
>>>> + struct iio_dev *indio_dev;
>>>> + struct list_head list;
>>>> + unsigned int refcnt;
>>>> +};
>>>> +
>>>> +/**
>>>> + * iio_hwmon_lookup_alarm() - Find an alarm by id
>>>> + * @listener: Event listener
>>>> + * @id: IIO event id
>>>> + *
>>>> + * Return: index of @id in @listener->ids, or -1 if not found
>>>> + */
>>>> +static ssize_t iio_hwmon_lookup_alarm(struct iio_hwmon_listener *listener,
>>>> + u64 id)
>>>> +{
>>>> + ssize_t i;
>>>> +
>>>> + for (i = 0; i < listener->num_alarms; i++)
>>>> + if (listener->ids[i] == id)
>>>> + return i;
>>>> +
>>>> + return -1;
>>>> +}
>>>> +
>>>> +static int iio_hwmon_listener_callback(struct notifier_block *block,
>>>> + unsigned long action, void *data)
>>>> +{
>>>> + struct iio_hwmon_listener *listener =
>>>> + container_of(block, struct iio_hwmon_listener, block);
>>>> + struct iio_event_data *ev = data;
>>>> + ssize_t i;
>>>> +
>>>> + if (action != IIO_NOTIFY_EVENT)
>>>> + return NOTIFY_DONE;
>>>> +
>>>> + i = iio_hwmon_lookup_alarm(listener, ev->id);
>>>> + if (i >= 0)
>>>> + set_bit(i, listener->alarms);
>>>> + else
>>>> + dev_warn_once(&listener->indio_dev->dev,
>>>> + "unknown event %016llx\n", ev->id);
>>>> +
>>>> + return NOTIFY_DONE;
>>>> +}
>>>> +
>>>> +/**
>>>> + * iio_event_id() - Calculate an IIO event id
>>>> + * @channel: IIO channel for this event
>>>> + * @type: Event type (theshold, rate-of-change, etc.)
>>>> + * @dir: Event direction (rising, falling, etc.)
>>>> + *
>>>> + * Return: IIO event id corresponding to this event's IIO id
>>>> + */
>>>> +static u64 iio_event_id(struct iio_chan_spec const *chan,
>>>> + enum iio_event_type type,
>>>> + enum iio_event_direction dir)
>>>> +{
>>>> + if (chan->differential)
>>>> + return IIO_DIFF_EVENT_CODE(chan->type, chan->channel,
>>>> + chan->channel2, type, dir);
>>>> + if (chan->modified)
>>>> + return IIO_MOD_EVENT_CODE(chan->type, chan->channel,
>>>> + chan->channel2, type, dir);
>>>> + return IIO_UNMOD_EVENT_CODE(chan->type, chan->channel, type, dir);
>>>> +}
>>>> +
>>>> +/**
>>>> + * iio_hwmon_listener_get() - Get a listener for an IIO device
>>>> + * @indio_dev: IIO device to listen to
>>>> + *
>>>> + * Look up or create a new listener for @indio_dev. The returned listener is
>>>> + * registered with @indio_dev, but events still need to be manually enabled.
>>>> + * You must call iio_hwmon_listener_put() when you are done.
>>>> + *
>>>> + * Return: Listener for @indio_dev, or an error pointer
>>>> + */
>>>> +static struct iio_hwmon_listener *iio_hwmon_listener_get(struct iio_dev
>>>> *indio_dev)
>>>> +{
>>>> + struct iio_hwmon_listener *listener;
>>>> + int err = -ENOMEM;
>>>> + size_t i, j;
>>>> +
>>>> + guard(mutex)(&iio_hwmon_listener_lock);
>>>> + list_for_each_entry(listener, &iio_hwmon_listeners, list) {
>>>> + if (listener->indio_dev == indio_dev) {
>>>> + if (likely(listener->refcnt != UINT_MAX))
>>>> + listener->refcnt++;
>>>
>>> I dunno for the above to ever happen :).
>>
>> Well, I can remove it if you like.
>>
>>> And as Andy stated, let's just use proper refcount APIs.
>>
>> No point in using atomic ops if they are only accessed under a mutex.
>>
>>>> + return listener;
>>>> + }
>>>> + }
>>>> +
>>>> + listener = kzalloc(sizeof(*listener), GFP_KERNEL);
>>>> + if (!listener)
>>>> + goto err_unlock;
>>>> +
>>>> + listener->refcnt = 1;
>>>> + listener->indio_dev = indio_dev;
>>>> + listener->block.notifier_call = iio_hwmon_listener_callback;
>>>> + for (i = 0; i < indio_dev->num_channels; i++)
>>>> + listener->num_alarms += indio_dev-
>>>>> channels[i].num_event_specs;
>>>> +
>>>> + listener->ids = kcalloc(listener->num_alarms, sizeof(*listener->ids),
>>>> + GFP_KERNEL);
>>>> + listener->alarms = bitmap_zalloc(listener->num_alarms, GFP_KERNEL);
>>>> + if (!listener->ids || !listener->alarms)
>>>> + goto err_listener;
>>>> +
>>>> + i = 0;
>>>> + for (j = 0; j < indio_dev->num_channels; j++) {
>>>> + struct iio_chan_spec const *chan = &indio_dev->channels[j];
>>>> + size_t k;
>>>> +
>>>> + for (k = 0; k < chan->num_event_specs; k++)
>>>> + listener->ids[i++] =
>>>> + iio_event_id(chan, chan->event_spec[k].type,
>>>> + chan->event_spec[k].dir);
>>>> + }
>>>> +
>>>> + err = iio_event_register(indio_dev, &listener->block);
>>>> + if (err)
>>>> + goto err_alarms;
>>>> +
>>>> + list_add(&listener->list, &iio_hwmon_listeners);
>>>> + mutex_unlock(&iio_hwmon_listener_lock);
>>>> + return listener;
>>>> +
>>>> +err_alarms:
>>>> + kfree(listener->alarms);
>>>> + kfree(listener->ids);
>>>> +err_listener:
>>>> + kfree(listener);
>>>> +err_unlock:
>>>> + mutex_unlock(&iio_hwmon_listener_lock);
>>>> + return ERR_PTR(err);
>>>> +}
>>>> +
>>>> +/**
>>>> + * iio_hwmon_listener_put() - Release a listener
>>>> + * @data: &struct iio_hwmon_listener to release
>>>> + *
>>>> + * For convenience, @data is void.
>>>> + */
>>>> +static void iio_hwmon_listener_put(void *data)
>>>> +{
>>>> + struct iio_hwmon_listener *listener = data;
>>>> +
>>>> + scoped_guard(mutex, &iio_hwmon_listener_lock) {
>>>> + if (unlikely(listener->refcnt == UINT_MAX))
>>>> + return;
>>>> +
>>>> + if (--listener->refcnt)
>>>> + return;
>>>> +
>>>> + list_del(&listener->list);
>>>> + iio_event_unregister(listener->indio_dev, &listener->block);
>>>> + }
>>>> +
>>>> + kfree(listener->alarms);
>>>> + kfree(listener->ids);
>>>> + kfree(listener);
>>>> +}
>>>> /**
>>>> * struct iio_hwmon_state - device instance state
>>>> @@ -143,6 +329,68 @@ static ssize_t iio_hwmon_write_event(struct device *dev,
>>>> return count;
>>>> }
>>>> +/**
>>>> + * struct iio_hwmon_alarm_attribute - IIO HWMON alarm attribute
>>>> + * @dev_attr: Base device attribute
>>>> + * @listener: Listener for this alarm
>>>> + * @index: Index of the channel in the IIO HWMON
>>>> + * @alarm: Index of the alarm within @listener
>>>> + */
>>>> +struct iio_hwmon_alarm_attribute {
>>>> + struct device_attribute dev_attr;
>>>> + struct iio_hwmon_listener *listener;
>>>> + size_t index;
>>>> + size_t alarm;
>>>> +};
>>>> +#define to_alarm_attr(_dev_attr) \
>>>> + container_of(_dev_attr, struct iio_hwmon_alarm_attribute, dev_attr)
>>>> +
>>>> +/**
>>>> + * iio_hwmon_alarm_toggle() - Turn an event off and back on again
>>>> + * @chan: Channel of the event
>>>> + * @dir: Event direction (rising, falling, etc.)
>>>> + *
>>>> + * Toggle an event's enable so we get notified if the alarm is already
>>>> + * triggered. We use this to convert IIO's event-triggered events into
>>>> + * level-triggered alarms.
>>>> + *
>>>> + * Return: 0 on success or negative error on failure
>>>> + */
>>>> +static int iio_hwmon_alarm_toggle(struct iio_channel *chan,
>>>> + enum iio_event_direction dir)
>>>> +{
>>>> + int ret;
>>>> +
>>>> + ret = iio_write_event_processed_scale(chan, IIO_EV_TYPE_THRESH, dir,
>>>> + IIO_EV_INFO_ENABLE, 0, 1);
>>>> + if (ret)
>>>> + return ret;
>>>> +
>>>> + return iio_write_event_processed_scale(chan, IIO_EV_TYPE_THRESH, dir,
>>>> + IIO_EV_INFO_ENABLE, 1, 1);
>>>> +}
>>>> +
>>>> +static ssize_t iio_hwmon_read_alarm(struct device *dev,
>>>> + struct device_attribute *attr,
>>>> + char *buf)
>>>> +{
>>>> + struct iio_hwmon_alarm_attribute *sattr = to_alarm_attr(attr);
>>>> + struct iio_hwmon_state *state = dev_get_drvdata(dev);
>>>> + struct iio_channel *chan = &state->channels[sattr->index];
>>>> +
>>>> + if (test_and_clear_bit(sattr->alarm, sattr->listener->alarms)) {
>>>> + u64 id = sattr->listener->ids[sattr->alarm];
>>>> + enum iio_event_direction dir =
>>>> IIO_EVENT_CODE_EXTRACT_DIR(id);
>>>> +
>>>> + WARN_ON(iio_hwmon_alarm_toggle(chan, dir));
>>>
>>> WARN_ON() is highly discouraged... Also do we really need a "scary" splat in
>>> this case?
>>
>> OK, maybe dev_warn then. I don't want to propagate the error because I think
>> it's more important to tell userspace that the alarm went off than if there
>> was a problem determining if the alarm is still active.
>>
>
> Sorry, I will neither accept backtraces not warning messages in hwmon code.
> That risks polluting the kernel log. Propagate the error. I fail to see
> the problem with that.
If userspace is interested in the alarm it certainly is more useful to
say "yes, there was an alarm!" than "sorry, something went wrong, but we
can't tell you what and if you retry there will no longer be an alarm or
an error so it will look some spurious problem."
>>>> + strcpy(buf, "1\n");
>>>> + return 2;
>>>> + }
>>>> +
>>>> + strcpy(buf, "0\n");
>>>> + return 2;
>>>
>>> As stated, sysfs_emit()
>>>
>>>> +}
>>>> +
>>>> static int add_device_attr(struct device *dev, struct iio_hwmon_state *st,
>>>> ssize_t (*show)(struct device *dev,
>>>> struct device_attribute *attr,
>>>> @@ -205,6 +453,63 @@ static int add_event_attr(struct device *dev, struct
>>>> iio_hwmon_state *st,
>>>> return 0;
>>>> }
>>>> +static int add_alarm_attr(struct device *dev, struct iio_hwmon_state *st,
>>>> + int i, enum iio_event_direction dir,
>>>> + const char *fmt, ...)
>>>> +{
>>>> + struct iio_hwmon_alarm_attribute *a;
>>>> + struct iio_hwmon_listener *listener;
>>>> + ssize_t alarm;
>>>> + umode_t mode;
>>>> + va_list ap;
>>>> + int ret;
>>>> +
>>>> + mode = iio_event_mode(&st->channels[i], IIO_EV_TYPE_THRESH, dir,
>>>> + IIO_EV_INFO_ENABLE);
>>>> + if (!(mode & 0200))
>>>> + return 0;
>>>> +
>>>> + listener = iio_hwmon_listener_get(st->channels[i].indio_dev);
>>>> + if (listener == ERR_PTR(-EBUSY))
>>>> + return 0;
>>>
>>> Maybe I missed something, where can we get -EBUSY? And should we ignore it?
>>
>> Oh, this was from before I refactored the notification API to allow kernel
>> consumers to co-exist with userspace ones. So this can't occur.
>>
>>>> + if (IS_ERR(listener))
>>>> + return PTR_ERR(listener);
>>>> +
>>>> + ret = devm_add_action_or_reset(dev, iio_hwmon_listener_put,
>>>> listener);
>>>> + if (ret)
>>>> + return ret;
>>>> +
>>>> + alarm = iio_hwmon_lookup_alarm(listener,
>>>> + iio_event_id(st->channels[i].channel,
>>>> + IIO_EV_TYPE_THRESH,
>>>> dir));
>>>> + if (WARN_ON_ONCE(alarm < 0))
>>>> + return -ENOENT;
>>>> +
>>>
>>> Again, I would drop WARN_ON_ONCE()
>>
>> This can only occur if there is a bug in the kernel. We should have returned
>> 0 from iio_event_mode() before we get to this point.
>>
>
> Just return the error to the caller (without replacing the error).
This should be a BUG() but that sort of thing is no longer allowed.
It cannot occur except if something has gone catastrophically wrong (e.g. bit
flips due to radiation or something similar). Returning an error is just
us being nice; at this point the system unstable in some unknown way
that may not allow for recovery.
--Sean
^ permalink raw reply [flat|nested] 62+ messages in thread
* Re: [PATCH 7/7] hwmon: iio: Add alarm support
2025-07-15 19:34 ` Guenter Roeck
@ 2025-07-15 20:08 ` Sean Anderson
0 siblings, 0 replies; 62+ messages in thread
From: Sean Anderson @ 2025-07-15 20:08 UTC (permalink / raw)
To: Guenter Roeck, Jonathan Cameron, Jean Delvare, linux-iio,
linux-hwmon
Cc: Andy Shevchenko, Nuno Sá, linux-kernel, David Lechner
On 7/15/25 15:34, Guenter Roeck wrote:
> On 7/14/25 18:20, Sean Anderson wrote:
>> Add alarm support based on IIO threshold events. The alarm is cleared on
>> read, but will be set again if the condition is still present. This is
>> detected by disabling and re-enabling the event. The same trick is done
>> when creating the attribute to detect already-triggered events.
>>
>> The alarms are updated by an event listener. To keep the notifier call
>> chain short, we create one listener per iio device, shared across all
>> hwmon devices.
>>
>> To avoid dynamic creation of alarms, alarms for all possible events are
>> allocated at creation. Lookup is done by a linear scan, as I expect
>> events to occur rarely. If performance becomes an issue, a binary search
>> could be done instead (or some kind of hash lookup).
>>
>
> I am very concerned about this. The context suggests that the iio events
> are just that - events without specific association to hardware or system
> limits. Hardware monitoring limits are system specific limits, which are not
> supposed to change at runtime. A high voltage or temperature warning is
> just that - it is not supposed to trigger a change in the event limit.
> If anything, it is supposed to trigger some action to bring the observed
> value back to normal.
If the system integrator has instantiated this driver, then the associated
IIO channels correspond to physical values related to the health of the
system. Other IIO channels should not be attached to the iio-hwmon driver.
For example, in my use case the Xilinx AMS was implemented as an IIO
device because it's a generic ADC, and several of the channels can
monitor arbitrary analog voltages. However, many channels are
permanently connected to the SoC's power rails and to internal
temperature probes. These channels are best exposed as an hwmon device
to take advantage of existing userspace tooling (e.g. lm-sensors).
The above paragraph in the commit message specifically refers to the
approach taken to handle IIO events for a given device. As we process
the hwmon's IIO channels, we create alarm attributes for the
corresponding events. Because we don't know which IIO events we are
interested in when we create the IIO listener, there are two general
approaches:
- We could allocate some memory for the alarm and then add it to a list
or hash table in the listener. When the listener gets an event it
would then search the list or hash table for the appropriate alarm.
- We can allocate memory for all possible events up front. When we want
to create an alarm we look up the appropriate event.
I chose the latter approach because I believe that there are typically
not too many events on a given IIO device (i.e. dozens) and it makes the
lookup simpler, since we can just iterate through an array (or do a
binary search).
> For this series to move forward, there needs to be some guarantee that
> the limits are used and usable only as intended, and can not be used for
> random thresholds. The idea of "if a temperature alarm is triggered, do
> something and change the threshold temperature" is not an acceptable use
> for hardware monitoring alarms.
What userspace sets the limits to or does in response to an alarm is not
the kernel's concern. That said, I suspect the most-likely userspace response
is to log the alarm, possibly to some remote system.
--Sean
^ permalink raw reply [flat|nested] 62+ messages in thread
* Re: [PATCH 2/7] iio: inkern: Add API for reading/writing events
2025-07-15 15:43 ` Sean Anderson
@ 2025-07-16 6:23 ` Nuno Sá
0 siblings, 0 replies; 62+ messages in thread
From: Nuno Sá @ 2025-07-16 6:23 UTC (permalink / raw)
To: Sean Anderson, Jonathan Cameron, Jean Delvare, Guenter Roeck,
linux-iio, linux-hwmon
Cc: Andy Shevchenko, Nuno Sá, linux-kernel, David Lechner
On Tue, 2025-07-15 at 11:43 -0400, Sean Anderson wrote:
> On 7/15/25 06:35, Nuno Sá wrote:
> > On Mon, 2025-07-14 at 21:20 -0400, Sean Anderson wrote:
> > > Add an in-kernel API for reading/writing event properties. Like the
> > > raw-to-processed conversion, with processed-to-raw we only convert the
> > > integer part, introducing some round-off error.
> > >
> > > A common case is for other drivers to re-expose IIO events as sysfs
> > > properties with a different API. To help out with this, iio_event_mode
> > > returns the appropriate mode. It can also be used to test for existence
> > > if the consumer doesn't care about read/write capability.
> > >
> > > Signed-off-by: Sean Anderson <sean.anderson@linux.dev>
> > > ---
> >
> > Just one comment on top of Andy's review
> >
> > >
> > > drivers/iio/inkern.c | 198 +++++++++++++++++++++++++++++++++++
> > > include/linux/iio/consumer.h | 56 ++++++++++
> > > 2 files changed, 254 insertions(+)
> > >
> > > diff --git a/drivers/iio/inkern.c b/drivers/iio/inkern.c
> > > index c174ebb7d5e6..d3bbd2444fb5 100644
> > > --- a/drivers/iio/inkern.c
> > > +++ b/drivers/iio/inkern.c
> > > @@ -1028,3 +1028,201 @@ ssize_t iio_read_channel_label(struct iio_channel
> > > *chan, char *buf)
> > > return do_iio_read_channel_label(chan->indio_dev, chan->channel,
> > > buf);
> > > }
> > > EXPORT_SYMBOL_GPL(iio_read_channel_label);
> > > +
> > > +static bool iio_event_exists(struct iio_channel *channel,
> > > + enum iio_event_type type,
> > > + enum iio_event_direction dir,
> > > + enum iio_event_info info)
> > > +{
> > > + struct iio_chan_spec const *chan = channel->channel;
> > > + int i;
> > > +
> >
> > Can we iio_event_exists() -> iio_event_exists_locked()? Or likely the best way
>
> wouldn't _unlocked be the convention for this file?
Oh, indeed!
>
> > would be to annotate it with lockdep (though that would mean some dance to get
> > the opaque object.
>
> I will add a lockdep annotation.
>
> --Sean
>
> > Anyways, bottom line is it should clear that iio_event_exists() is to be called
> > with the lock held.
> >
> > - Nuno Sá
> >
> > > + if (!channel->indio_dev->info)
> > > + return false;
> > > +
> > > + for (i = 0; i < chan->num_event_specs; i++) {
> > > + if (chan->event_spec[i].type != type)
> > > + continue;
> > > + if (chan->event_spec[i].dir != dir)
> > > + continue;
> > > + if (chan->event_spec[i].mask_separate & BIT(info))
> > > + return true;
> > > + }
> > > +
> > > + return false;
> > > +}
> > > +
> > > +umode_t iio_event_mode(struct iio_channel *chan, enum iio_event_type type,
> > > + enum iio_event_direction dir, enum iio_event_info
> > > info)
> > > +{
> > > + struct iio_dev *indio_dev = chan->indio_dev;
> > > + struct iio_dev_opaque *iio_dev_opaque = to_iio_dev_opaque(indio_dev);
> > > + umode_t mode = 0;
> > > +
> > > + guard(mutex)(&iio_dev_opaque->info_exist_lock);
> > > + if (!iio_event_exists(chan, type, dir, info))
> > > + return 0;
> > > +
> > > + if (info == IIO_EV_INFO_ENABLE) {
> > > + if (indio_dev->info->read_event_config)
> > > + mode |= 0444;
> > > +
> > > + if (indio_dev->info->write_event_config)
> > > + mode |= 0200;
> > > + } else {
> > > + if (indio_dev->info->read_event_value)
> > > + mode |= 0444;
> > > +
> > > + if (indio_dev->info->write_event_value)
> > > + mode |= 0200;
> > > + }
> > > +
> > > + return mode;
> > > +}
> > > +EXPORT_SYMBOL_GPL(iio_event_mode);
> > > +
> > > +int iio_read_event_processed_scale(struct iio_channel *chan,
> > > + enum iio_event_type type,
> > > + enum iio_event_direction dir,
> > > + enum iio_event_info info, int *val,
> > > + unsigned int scale)
> > > +{
> > > + struct iio_dev *indio_dev = chan->indio_dev;
> > > + struct iio_dev_opaque *iio_dev_opaque = to_iio_dev_opaque(indio_dev);
> > > + int ret, raw;
> > > +
> > > + guard(mutex)(&iio_dev_opaque->info_exist_lock);
> > > + if (!iio_event_exists(chan, type, dir, info))
> > > + return -ENODEV;
> > > +
> > > + if (info == IIO_EV_INFO_ENABLE) {
> > > + if (!indio_dev->info->read_event_config)
> > > + return -EINVAL;
> > > +
> > > + raw = indio_dev->info->read_event_config(indio_dev,
> > > + chan->channel, type,
> > > + dir);
> > > + if (raw < 0)
> > > + return raw;
> > > +
> > > + *val = raw;
> > > + return 0;
> > > + }
> > > +
> > > + if (!indio_dev->info->read_event_value)
> > > + return -EINVAL;
> > > +
> > > + ret = indio_dev->info->read_event_value(indio_dev, chan->channel,
> > > type,
> > > + dir, info, &raw, NULL);
> > > + if (ret < 0)
> > > + return ret;
> > > +
> > > + return iio_convert_raw_to_processed_unlocked(chan, raw, val, scale);
> > > +}
> > > +EXPORT_SYMBOL_GPL(iio_read_event_processed_scale);
> > > +
> > > +static int iio_convert_processed_to_raw_unlocked(struct iio_channel *chan,
> > > + int processed, int *raw,
> > > + unsigned int scale)
> > > +{
> > > + int scale_type, scale_val, scale_val2;
> > > + int offset_type, offset_val, offset_val2;
> > > + s64 r, scale64, raw64;
> > > +
> > > + scale_type = iio_channel_read(chan, &scale_val, &scale_val2,
> > > + IIO_CHAN_INFO_SCALE);
> > > + if (scale_type < 0) {
> > > + raw64 = processed / scale;
> > > + } else {
> > > + switch (scale_type) {
> > > + case IIO_VAL_INT:
> > > + scale64 = (s64)scale_val * scale;
> > > + if (scale64 <= INT_MAX && scale64 >= INT_MIN)
> > > + raw64 = processed / (int)scale64;
> > > + else
> > > + raw64 = 0;
> > > + break;
> > > + case IIO_VAL_INT_PLUS_MICRO:
> > > + scale64 = scale_val * scale * 1000000LL + scale_val2;
> > > + raw64 = div64_s64_rem(processed, scale64, &r);
> > > + raw64 = raw64 * 1000000 +
> > > + div64_s64(r * 1000000, scale64);
> > > + break;
> > > + case IIO_VAL_INT_PLUS_NANO:
> > > + scale64 = scale_val * scale * 1000000000LL +
> > > scale_val2;
> > > + raw64 = div64_s64_rem(processed, scale64, &r);
> > > + raw64 = raw64 * 1000000000 +
> > > + div64_s64(r * 1000000000, scale64);
> > > + break;
> > > + case IIO_VAL_FRACTIONAL:
> > > + raw64 = div64_s64((s64)processed * scale_val2,
> > > + (s64)scale_val * scale);
> > > + break;
> > > + case IIO_VAL_FRACTIONAL_LOG2:
> > > + raw64 = div64_s64((s64)processed << scale_val2,
> > > + (s64)scale_val * scale);
> > > + break;
> > > + default:
> > > + return -EINVAL;
> > > + }
> > > + }
> > > +
> > > + offset_type = iio_channel_read(chan, &offset_val, &offset_val2,
> > > + IIO_CHAN_INFO_OFFSET);
> > > + if (offset_type >= 0) {
> > > + switch (offset_type) {
> > > + case IIO_VAL_INT:
> > > + case IIO_VAL_INT_PLUS_MICRO:
> > > + case IIO_VAL_INT_PLUS_NANO:
> > > + raw64 -= offset_val;
> > > + break;
> > > + case IIO_VAL_FRACTIONAL:
> > > + raw64 -= offset_val / offset_val2;
> > > + break;
> > > + case IIO_VAL_FRACTIONAL_LOG2:
> > > + raw64 -= offset_val >> offset_val2;
> > > + break;
> > > + default:
> > > + return -EINVAL;
> > > + }
> > > + }
> > > +
> > > + *raw = clamp(raw64, (s64)INT_MIN, (s64)INT_MAX);
> > > + return 0;
> > > +}
> > > +
> > > +int iio_write_event_processed_scale(struct iio_channel *chan,
> > > + enum iio_event_type type,
> > > + enum iio_event_direction dir,
> > > + enum iio_event_info info, int processed,
> > > + unsigned int scale)
> > > +{
> > > + struct iio_dev *indio_dev = chan->indio_dev;
> > > + struct iio_dev_opaque *iio_dev_opaque = to_iio_dev_opaque(chan-
> > > > indio_dev);
> > > + int ret, raw;
> > > +
> > > + guard(mutex)(&iio_dev_opaque->info_exist_lock);
> > > + if (!iio_event_exists(chan, type, dir, info))
> > > + return -ENODEV;
> > > +
> > > + if (info == IIO_EV_INFO_ENABLE) {
> > > + if (!indio_dev->info->write_event_config)
> > > + return -EINVAL;
> > > +
> > > + return indio_dev->info->write_event_config(indio_dev,
> > > + chan->channel,
> > > type,
> > > + dir, processed);
> > > + }
> > > +
> > > + if (!indio_dev->info->write_event_value)
> > > + return -EINVAL;
> > > +
> > > + ret = iio_convert_processed_to_raw_unlocked(chan, processed, &raw,
> > > + scale);
> > > + if (ret < 0)
> > > + return ret;
> > > +
> > > + return indio_dev->info->write_event_value(indio_dev, chan->channel,
> > > + type, dir, info, raw, 0);
> > > +}
> > > +EXPORT_SYMBOL_GPL(iio_write_event_processed_scale);
> > > diff --git a/include/linux/iio/consumer.h b/include/linux/iio/consumer.h
> > > index 6a4479616479..16e7682474f3 100644
> > > --- a/include/linux/iio/consumer.h
> > > +++ b/include/linux/iio/consumer.h
> > > @@ -451,4 +451,60 @@ ssize_t iio_write_channel_ext_info(struct iio_channel
> > > *chan, const char *attr,
> > > */
> > > ssize_t iio_read_channel_label(struct iio_channel *chan, char *buf);
> > >
> > > +/**
> > > + * iio_event_mode() - get file mode for an event property
> > > + * @chan: Channel being queried
> > > + * @type: Event type (theshold, rate-of-change, etc.)
> > > + * @dir: Event direction (rising, falling, etc.)
> > > + * @info: Event property (enable, value, etc.)
> > > + *
> > > + * Determine an appropriate mode for sysfs files derived from this event.
> > > + *
> > > + * Return:
> > > + * - `0000` if the event is unsupported or otherwise unavailable
> > > + * - `0444` if the event is read-only
> > > + * - `0200` if the event is write-only
> > > + * - `0644` if the event is read-write
> > > + */
> > > +umode_t iio_event_mode(struct iio_channel *chan, enum iio_event_type type,
> > > + enum iio_event_direction dir, enum iio_event_info
> > > info);
> > > +
> > > +/**
> > > + * iio_read_event_processed_scale() - Read an event property
> > > + * @chan: Channel being queried
> > > + * @type: Event type (theshold, rate-of-change, etc.)
> > > + * @dir: Event direction (rising, falling, etc.)
> > > + * @info: Event property (enable, value, etc.)
> > > + * @val: Processed property value
> > > + * @scale: Factor to scale @val by
> > > + *
> > > + * Read a processed (scaled and offset) event property of a given channel.
> > > + *
> > > + * Return: 0 on success, or negative error on failure
> > > + */
> > > +int iio_read_event_processed_scale(struct iio_channel *chan,
> > > + enum iio_event_type type,
> > > + enum iio_event_direction dir,
> > > + enum iio_event_info info, int *val,
> > > + unsigned int scale);
> > > +
> > > +/**
> > > + * iio_write_event_processed_scale() - Read an event property
> > > + * @chan: Channel being queried
> > > + * @type: Event type (theshold, rate-of-change, etc.)
> > > + * @dir: Event direction (rising, falling, etc.)
> > > + * @info: Event property (enable, value, etc.)
> > > + * @processed: Processed property value
> > > + * @scale: Factor to scale @processed by
> > > + *
> > > + * Write a processed (scaled and offset) event property of a given channel.
> > > + *
> > > + * Return: 0 on success, or negative error on failure
> > > + */
> > > +int iio_write_event_processed_scale(struct iio_channel *chan,
> > > + enum iio_event_type type,
> > > + enum iio_event_direction dir,
> > > + enum iio_event_info info, int processed,
> > > + unsigned int scale);
> > > +
> > > #endif
>
^ permalink raw reply [flat|nested] 62+ messages in thread
* Re: [PATCH 7/7] hwmon: iio: Add alarm support
2025-07-15 17:02 ` Sean Anderson
2025-07-15 19:26 ` Guenter Roeck
@ 2025-07-16 6:37 ` Nuno Sá
2025-07-17 16:00 ` Sean Anderson
1 sibling, 1 reply; 62+ messages in thread
From: Nuno Sá @ 2025-07-16 6:37 UTC (permalink / raw)
To: Sean Anderson, Jonathan Cameron, Jean Delvare, Guenter Roeck,
linux-iio, linux-hwmon
Cc: Andy Shevchenko, Nuno Sá, linux-kernel, David Lechner
On Tue, 2025-07-15 at 13:02 -0400, Sean Anderson wrote:
> On 7/15/25 07:28, Nuno Sá wrote:
> > On Mon, 2025-07-14 at 21:20 -0400, Sean Anderson wrote:
> > > Add alarm support based on IIO threshold events. The alarm is cleared on
> > > read, but will be set again if the condition is still present. This is
> > > detected by disabling and re-enabling the event. The same trick is done
> > > when creating the attribute to detect already-triggered events.
> > >
> > > The alarms are updated by an event listener. To keep the notifier call
> > > chain short, we create one listener per iio device, shared across all
> > > hwmon devices.
> > >
> > > To avoid dynamic creation of alarms, alarms for all possible events are
> > > allocated at creation. Lookup is done by a linear scan, as I expect
> > > events to occur rarely. If performance becomes an issue, a binary search
> > > could be done instead (or some kind of hash lookup).
> > >
> > > Signed-off-by: Sean Anderson <sean.anderson@linux.dev>
> > > ---
> > >
> > > drivers/hwmon/iio_hwmon.c | 322 +++++++++++++++++++++++++++++++++++++-
> > > 1 file changed, 321 insertions(+), 1 deletion(-)
> > >
> > > diff --git a/drivers/hwmon/iio_hwmon.c b/drivers/hwmon/iio_hwmon.c
> > > index 3db4d4b30022..c963bc5452ba 100644
> > > --- a/drivers/hwmon/iio_hwmon.c
> > > +++ b/drivers/hwmon/iio_hwmon.c
> > > @@ -8,6 +8,7 @@
> > > #include <linux/slab.h>
> > > #include <linux/mod_devicetable.h>
> > > #include <linux/module.h>
> > > +#include <linux/notifier.h>
> > > #include <linux/err.h>
> > > #include <linux/platform_device.h>
> > > #include <linux/property.h>
> > > @@ -15,7 +16,192 @@
> > > #include <linux/hwmon.h>
> > > #include <linux/hwmon-sysfs.h>
> > > #include <linux/iio/consumer.h>
> > > +#include <linux/iio/events.h>
> > > +#include <linux/iio/iio.h>
> > > #include <linux/iio/types.h>
> > > +#include <uapi/linux/iio/events.h>
> > > +
> > > +/* Protects iio_hwmon_listeners and listeners' refcnt */
> > > +DEFINE_MUTEX(iio_hwmon_listener_lock);
> > > +LIST_HEAD(iio_hwmon_listeners);
> > > +
> > > +/**
> > > + * struct iio_hwmon_listener - Listener for IIO events
> > > + * @block: Notifier for events
> > > + * @ids: Array of IIO event ids, one per alarm
> > > + * @alarms: Bitmap of alarms
> > > + * @num_alarms: Length of @ids and @alarms
> > > + * @indio_dev: Device we are listening to
> > > + * @list: List of all listeners
> > > + * @refcnt: Reference count
> > > + */
> > > +struct iio_hwmon_listener {
> > > + struct notifier_block block;
> > > + u64 *ids;
> > > + unsigned long *alarms;
> > > + size_t num_alarms;
> > > +
> > > + struct iio_dev *indio_dev;
> > > + struct list_head list;
> > > + unsigned int refcnt;
> > > +};
> > > +
> > > +/**
> > > + * iio_hwmon_lookup_alarm() - Find an alarm by id
> > > + * @listener: Event listener
> > > + * @id: IIO event id
> > > + *
> > > + * Return: index of @id in @listener->ids, or -1 if not found
> > > + */
> > > +static ssize_t iio_hwmon_lookup_alarm(struct iio_hwmon_listener *listener,
> > > + u64 id)
> > > +{
> > > + ssize_t i;
> > > +
> > > + for (i = 0; i < listener->num_alarms; i++)
> > > + if (listener->ids[i] == id)
> > > + return i;
> > > +
> > > + return -1;
> > > +}
> > > +
> > > +static int iio_hwmon_listener_callback(struct notifier_block *block,
> > > + unsigned long action, void *data)
> > > +{
> > > + struct iio_hwmon_listener *listener =
> > > + container_of(block, struct iio_hwmon_listener, block);
> > > + struct iio_event_data *ev = data;
> > > + ssize_t i;
> > > +
> > > + if (action != IIO_NOTIFY_EVENT)
> > > + return NOTIFY_DONE;
> > > +
> > > + i = iio_hwmon_lookup_alarm(listener, ev->id);
> > > + if (i >= 0)
> > > + set_bit(i, listener->alarms);
> > > + else
> > > + dev_warn_once(&listener->indio_dev->dev,
> > > + "unknown event %016llx\n", ev->id);
> > > +
> > > + return NOTIFY_DONE;
> > > +}
> > > +
> > > +/**
> > > + * iio_event_id() - Calculate an IIO event id
> > > + * @channel: IIO channel for this event
> > > + * @type: Event type (theshold, rate-of-change, etc.)
> > > + * @dir: Event direction (rising, falling, etc.)
> > > + *
> > > + * Return: IIO event id corresponding to this event's IIO id
> > > + */
> > > +static u64 iio_event_id(struct iio_chan_spec const *chan,
> > > + enum iio_event_type type,
> > > + enum iio_event_direction dir)
> > > +{
> > > + if (chan->differential)
> > > + return IIO_DIFF_EVENT_CODE(chan->type, chan->channel,
> > > + chan->channel2, type, dir);
> > > + if (chan->modified)
> > > + return IIO_MOD_EVENT_CODE(chan->type, chan->channel,
> > > + chan->channel2, type, dir);
> > > + return IIO_UNMOD_EVENT_CODE(chan->type, chan->channel, type, dir);
> > > +}
> > > +
> > > +/**
> > > + * iio_hwmon_listener_get() - Get a listener for an IIO device
> > > + * @indio_dev: IIO device to listen to
> > > + *
> > > + * Look up or create a new listener for @indio_dev. The returned listener is
> > > + * registered with @indio_dev, but events still need to be manually enabled.
> > > + * You must call iio_hwmon_listener_put() when you are done.
> > > + *
> > > + * Return: Listener for @indio_dev, or an error pointer
> > > + */
> > > +static struct iio_hwmon_listener *iio_hwmon_listener_get(struct iio_dev
> > > *indio_dev)
> > > +{
> > > + struct iio_hwmon_listener *listener;
> > > + int err = -ENOMEM;
> > > + size_t i, j;
> > > +
> > > + guard(mutex)(&iio_hwmon_listener_lock);
> > > + list_for_each_entry(listener, &iio_hwmon_listeners, list) {
> > > + if (listener->indio_dev == indio_dev) {
> > > + if (likely(listener->refcnt != UINT_MAX))
> > > + listener->refcnt++;
> >
> > I dunno for the above to ever happen :).
>
> Well, I can remove it if you like.
>
> > And as Andy stated, let's just use proper refcount APIs.
>
> No point in using atomic ops if they are only accessed under a mutex.
Not the point... If there are proper APIs for handling things like this, not sure why
not using and then coming up with things like the above? And the same goes to the
release path.
- Nuno Sá
>
> > > + return listener;
> > > + }
> > > + }
> > > +
> > > + listener = kzalloc(sizeof(*listener), GFP_KERNEL);
> > > + if (!listener)
> > > + goto err_unlock;
> > > +
> > > + listener->refcnt = 1;
> > > + listener->indio_dev = indio_dev;
> > > + listener->block.notifier_call = iio_hwmon_listener_callback;
> > > + for (i = 0; i < indio_dev->num_channels; i++)
> > > + listener->num_alarms += indio_dev-
> > > > channels[i].num_event_specs;
> > > +
> > > + listener->ids = kcalloc(listener->num_alarms, sizeof(*listener->ids),
> > > + GFP_KERNEL);
> > > + listener->alarms = bitmap_zalloc(listener->num_alarms, GFP_KERNEL);
> > > + if (!listener->ids || !listener->alarms)
> > > + goto err_listener;
> > > +
> > > + i = 0;
> > > + for (j = 0; j < indio_dev->num_channels; j++) {
> > > + struct iio_chan_spec const *chan = &indio_dev->channels[j];
> > > + size_t k;
> > > +
> > > + for (k = 0; k < chan->num_event_specs; k++)
> > > + listener->ids[i++] =
> > > + iio_event_id(chan, chan->event_spec[k].type,
> > > + chan->event_spec[k].dir);
> > > + }
> > > +
> > > + err = iio_event_register(indio_dev, &listener->block);
> > > + if (err)
> > > + goto err_alarms;
> > > +
> > > + list_add(&listener->list, &iio_hwmon_listeners);
> > > + mutex_unlock(&iio_hwmon_listener_lock);
> > > + return listener;
> > > +
> > > +err_alarms:
> > > + kfree(listener->alarms);
> > > + kfree(listener->ids);
> > > +err_listener:
> > > + kfree(listener);
> > > +err_unlock:
> > > + mutex_unlock(&iio_hwmon_listener_lock);
> > > + return ERR_PTR(err);
> > > +}
> > > +
> > > +/**
> > > + * iio_hwmon_listener_put() - Release a listener
> > > + * @data: &struct iio_hwmon_listener to release
> > > + *
> > > + * For convenience, @data is void.
> > > + */
> > > +static void iio_hwmon_listener_put(void *data)
> > > +{
> > > + struct iio_hwmon_listener *listener = data;
> > > +
> > > + scoped_guard(mutex, &iio_hwmon_listener_lock) {
> > > + if (unlikely(listener->refcnt == UINT_MAX))
> > > + return;
> > > +
> > > + if (--listener->refcnt)
> > > + return;
> > > +
> > > + list_del(&listener->list);
> > > + iio_event_unregister(listener->indio_dev, &listener->block);
> > > + }
> > > +
> > > + kfree(listener->alarms);
> > > + kfree(listener->ids);
> > > + kfree(listener);
> > > +}
> > >
> > > /**
> > > * struct iio_hwmon_state - device instance state
> > > @@ -143,6 +329,68 @@ static ssize_t iio_hwmon_write_event(struct device *dev,
> > > return count;
> > > }
> > >
> > > +/**
> > > + * struct iio_hwmon_alarm_attribute - IIO HWMON alarm attribute
> > > + * @dev_attr: Base device attribute
> > > + * @listener: Listener for this alarm
> > > + * @index: Index of the channel in the IIO HWMON
> > > + * @alarm: Index of the alarm within @listener
> > > + */
> > > +struct iio_hwmon_alarm_attribute {
> > > + struct device_attribute dev_attr;
> > > + struct iio_hwmon_listener *listener;
> > > + size_t index;
> > > + size_t alarm;
> > > +};
> > > +#define to_alarm_attr(_dev_attr) \
> > > + container_of(_dev_attr, struct iio_hwmon_alarm_attribute, dev_attr)
> > > +
> > > +/**
> > > + * iio_hwmon_alarm_toggle() - Turn an event off and back on again
> > > + * @chan: Channel of the event
> > > + * @dir: Event direction (rising, falling, etc.)
> > > + *
> > > + * Toggle an event's enable so we get notified if the alarm is already
> > > + * triggered. We use this to convert IIO's event-triggered events into
> > > + * level-triggered alarms.
> > > + *
> > > + * Return: 0 on success or negative error on failure
> > > + */
> > > +static int iio_hwmon_alarm_toggle(struct iio_channel *chan,
> > > + enum iio_event_direction dir)
> > > +{
> > > + int ret;
> > > +
> > > + ret = iio_write_event_processed_scale(chan, IIO_EV_TYPE_THRESH, dir,
> > > + IIO_EV_INFO_ENABLE, 0, 1);
> > > + if (ret)
> > > + return ret;
> > > +
> > > + return iio_write_event_processed_scale(chan, IIO_EV_TYPE_THRESH, dir,
> > > + IIO_EV_INFO_ENABLE, 1, 1);
> > > +}
> > > +
> > > +static ssize_t iio_hwmon_read_alarm(struct device *dev,
> > > + struct device_attribute *attr,
> > > + char *buf)
> > > +{
> > > + struct iio_hwmon_alarm_attribute *sattr = to_alarm_attr(attr);
> > > + struct iio_hwmon_state *state = dev_get_drvdata(dev);
> > > + struct iio_channel *chan = &state->channels[sattr->index];
> > > +
> > > + if (test_and_clear_bit(sattr->alarm, sattr->listener->alarms)) {
> > > + u64 id = sattr->listener->ids[sattr->alarm];
> > > + enum iio_event_direction dir =
> > > IIO_EVENT_CODE_EXTRACT_DIR(id);
> > > +
> > > + WARN_ON(iio_hwmon_alarm_toggle(chan, dir));
> >
> > WARN_ON() is highly discouraged... Also do we really need a "scary" splat in
> > this case?
>
> OK, maybe dev_warn then. I don't want to propagate the error because I think
> it's more important to tell userspace that the alarm went off than if there
> was a problem determining if the alarm is still active.
>
> > > + strcpy(buf, "1\n");
> > > + return 2;
> > > + }
> > > +
> > > + strcpy(buf, "0\n");
> > > + return 2;
> >
> > As stated, sysfs_emit()
> >
> > > +}
> > > +
> > > static int add_device_attr(struct device *dev, struct iio_hwmon_state *st,
> > > ssize_t (*show)(struct device *dev,
> > > struct device_attribute *attr,
> > > @@ -205,6 +453,63 @@ static int add_event_attr(struct device *dev, struct
> > > iio_hwmon_state *st,
> > > return 0;
> > > }
> > >
> > > +static int add_alarm_attr(struct device *dev, struct iio_hwmon_state *st,
> > > + int i, enum iio_event_direction dir,
> > > + const char *fmt, ...)
> > > +{
> > > + struct iio_hwmon_alarm_attribute *a;
> > > + struct iio_hwmon_listener *listener;
> > > + ssize_t alarm;
> > > + umode_t mode;
> > > + va_list ap;
> > > + int ret;
> > > +
> > > + mode = iio_event_mode(&st->channels[i], IIO_EV_TYPE_THRESH, dir,
> > > + IIO_EV_INFO_ENABLE);
> > > + if (!(mode & 0200))
> > > + return 0;
> > > +
> > > + listener = iio_hwmon_listener_get(st->channels[i].indio_dev);
> > > + if (listener == ERR_PTR(-EBUSY))
> > > + return 0;
> >
> > Maybe I missed something, where can we get -EBUSY? And should we ignore it?
>
> Oh, this was from before I refactored the notification API to allow kernel
> consumers to co-exist with userspace ones. So this can't occur.
>
> > > + if (IS_ERR(listener))
> > > + return PTR_ERR(listener);
> > > +
> > > + ret = devm_add_action_or_reset(dev, iio_hwmon_listener_put,
> > > listener);
> > > + if (ret)
> > > + return ret;
> > > +
> > > + alarm = iio_hwmon_lookup_alarm(listener,
> > > + iio_event_id(st->channels[i].channel,
> > > + IIO_EV_TYPE_THRESH,
> > > dir));
> > > + if (WARN_ON_ONCE(alarm < 0))
> > > + return -ENOENT;
> > > +
> >
> > Again, I would drop WARN_ON_ONCE()
>
> This can only occur if there is a bug in the kernel. We should have returned
> 0 from iio_event_mode() before we get to this point.
>
> --Sean
^ permalink raw reply [flat|nested] 62+ messages in thread
* Re: [PATCH 7/7] hwmon: iio: Add alarm support
2025-07-15 1:20 ` [PATCH 7/7] hwmon: iio: Add alarm support Sean Anderson
` (3 preceding siblings ...)
2025-07-15 19:34 ` Guenter Roeck
@ 2025-07-16 7:44 ` kernel test robot
2025-07-27 16:50 ` Jonathan Cameron
5 siblings, 0 replies; 62+ messages in thread
From: kernel test robot @ 2025-07-16 7:44 UTC (permalink / raw)
To: Sean Anderson, Jonathan Cameron, Jean Delvare, Guenter Roeck,
linux-iio, linux-hwmon
Cc: oe-kbuild-all, Andy Shevchenko, Nuno Sá, linux-kernel,
David Lechner, Sean Anderson
Hi Sean,
kernel test robot noticed the following build warnings:
[auto build test WARNING on jic23-iio/togreg]
[also build test WARNING on groeck-staging/hwmon-next akpm-mm/mm-nonmm-unstable linus/master v6.16-rc6 next-20250715]
[If your patch is applied to the wrong git tree, kindly drop us a note.
And when submitting patch, we suggest to use '--base' as documented in
https://git-scm.com/docs/git-format-patch#_base_tree_information]
url: https://github.com/intel-lab-lkp/linux/commits/Sean-Anderson/math64-Add-div64_s64_rem/20250715-092337
base: https://git.kernel.org/pub/scm/linux/kernel/git/jic23/iio.git togreg
patch link: https://lore.kernel.org/r/20250715012023.2050178-8-sean.anderson%40linux.dev
patch subject: [PATCH 7/7] hwmon: iio: Add alarm support
config: i386-randconfig-063-20250716 (https://download.01.org/0day-ci/archive/20250716/202507161550.frzFNyCa-lkp@intel.com/config)
compiler: clang version 20.1.8 (https://github.com/llvm/llvm-project 87f0227cb60147a26a1eeb4fb06e3b505e9c7261)
reproduce (this is a W=1 build): (https://download.01.org/0day-ci/archive/20250716/202507161550.frzFNyCa-lkp@intel.com/reproduce)
If you fix the issue in a separate patch/commit (i.e. not just a new version of
the same patch/commit), kindly add following tags
| Reported-by: kernel test robot <lkp@intel.com>
| Closes: https://lore.kernel.org/oe-kbuild-all/202507161550.frzFNyCa-lkp@intel.com/
sparse warnings: (new ones prefixed by >>)
>> drivers/hwmon/iio_hwmon.c:25:1: sparse: sparse: symbol 'iio_hwmon_listener_lock' was not declared. Should it be static?
>> drivers/hwmon/iio_hwmon.c:26:1: sparse: sparse: symbol 'iio_hwmon_listeners' was not declared. Should it be static?
vim +/iio_hwmon_listener_lock +25 drivers/hwmon/iio_hwmon.c
23
24 /* Protects iio_hwmon_listeners and listeners' refcnt */
> 25 DEFINE_MUTEX(iio_hwmon_listener_lock);
> 26 LIST_HEAD(iio_hwmon_listeners);
27
--
0-DAY CI Kernel Test Service
https://github.com/intel/lkp-tests/wiki
^ permalink raw reply [flat|nested] 62+ messages in thread
* Re: [PATCH 2/7] iio: inkern: Add API for reading/writing events
2025-07-15 15:42 ` Sean Anderson
@ 2025-07-16 9:28 ` Andy Shevchenko
2025-07-17 16:42 ` Sean Anderson
0 siblings, 1 reply; 62+ messages in thread
From: Andy Shevchenko @ 2025-07-16 9:28 UTC (permalink / raw)
To: Sean Anderson
Cc: Jonathan Cameron, Jean Delvare, Guenter Roeck, linux-iio,
linux-hwmon, Andy Shevchenko, Nuno Sá, linux-kernel,
David Lechner
On Tue, Jul 15, 2025 at 11:42:05AM -0400, Sean Anderson wrote:
> On 7/15/25 04:18, Andy Shevchenko wrote:
> > On Mon, Jul 14, 2025 at 09:20:18PM -0400, Sean Anderson wrote:
...
> >> +EXPORT_SYMBOL_GPL(iio_event_mode);
> >
> > Can we move this to namespace? Otherwise it will be never ending story...
> > Ditto for other new APIs.
>
> Never ending story of what?
Of converting IIO core to use exported namespaces.
...
> >> + if (scale64 <= INT_MAX && scale64 >= INT_MIN)
> >> + raw64 = processed / (int)scale64;
> >
> > Do you need the casting? (I mean if the compiler is dumb enough to not see this)
>
> AIUI 64-bit division is not available on 32-bit platforms. The cast
> ensures we get 32-bit division.
I put specifically a remark in the parentheses. So, the Q is if the compiler
doesn't recognize that. Can you confirm that 32-bit compilation without cast
is broken?
...
> >> + *raw = clamp(raw64, (s64)INT_MIN, (s64)INT_MAX);
> >
> > You already have similar approach here...
>
> Well, I can spell it 0x7fffffffLL if you'd like...
Nope, I like to have named constants instead of magics, but actually are those
castings needed for the clamp()?
--
With Best Regards,
Andy Shevchenko
^ permalink raw reply [flat|nested] 62+ messages in thread
* Re: [PATCH 3/7] iio: Add in-kernel API for events
2025-07-15 15:47 ` Sean Anderson
@ 2025-07-16 9:47 ` Andy Shevchenko
0 siblings, 0 replies; 62+ messages in thread
From: Andy Shevchenko @ 2025-07-16 9:47 UTC (permalink / raw)
To: Sean Anderson
Cc: Jonathan Cameron, Jean Delvare, Guenter Roeck, linux-iio,
linux-hwmon, Andy Shevchenko, Nuno Sá, linux-kernel,
David Lechner
On Tue, Jul 15, 2025 at 11:47:07AM -0400, Sean Anderson wrote:
> On 7/15/25 04:20, Andy Shevchenko wrote:
> > On Mon, Jul 14, 2025 at 09:20:19PM -0400, Sean Anderson wrote:
...
> >> + WARN_ON(atomic_notifier_chain_unregister(&ev_int->notifier, block));
> >
> > Is bug.h already included?
>
> I assume so. No build errors.
Explicitly? Otherwise it's against IWYU principle.
--
With Best Regards,
Andy Shevchenko
^ permalink raw reply [flat|nested] 62+ messages in thread
* Re: [PATCH 5/7] hwmon: iio: Add helper function for creating attributes
2025-07-15 15:55 ` Sean Anderson
@ 2025-07-16 10:00 ` Andy Shevchenko
0 siblings, 0 replies; 62+ messages in thread
From: Andy Shevchenko @ 2025-07-16 10:00 UTC (permalink / raw)
To: Sean Anderson
Cc: Jonathan Cameron, Jean Delvare, Guenter Roeck, linux-iio,
linux-hwmon, Andy Shevchenko, Nuno Sá, linux-kernel,
David Lechner
On Tue, Jul 15, 2025 at 11:55:08AM -0400, Sean Anderson wrote:
> On 7/15/25 04:38, Andy Shevchenko wrote:
> > On Mon, Jul 14, 2025 at 09:20:21PM -0400, Sean Anderson wrote:
...
> >> +static int add_device_attr(struct device *dev, struct iio_hwmon_state *st,
> >
> > This should hint that this is managed:
> >
> > add_device_managed_attr()
>
> That just makes it more difficult to format the calling code within 80 columns...
Choose your name, but important that it should hint the caller that it's only
for the ->probe() stages.
...
> >> + ssize_t (*show)(struct device *dev,
> >> + struct device_attribute *attr,
> >> + char *buf),
> >> + int i, const char *fmt, ...)
> >
> > __printf() attribute is missing.
>
> It's static, so I thought the compiler could infer it but I guess not.
TBH, I haven't checked, but I think it might be the good style to add.
--
With Best Regards,
Andy Shevchenko
^ permalink raw reply [flat|nested] 62+ messages in thread
* Re: [PATCH 6/7] hwmon: iio: Add min/max support
2025-07-15 16:05 ` Sean Anderson
@ 2025-07-16 10:01 ` Andy Shevchenko
2025-07-17 16:11 ` Sean Anderson
0 siblings, 1 reply; 62+ messages in thread
From: Andy Shevchenko @ 2025-07-16 10:01 UTC (permalink / raw)
To: Sean Anderson
Cc: Jonathan Cameron, Jean Delvare, Guenter Roeck, linux-iio,
linux-hwmon, Andy Shevchenko, Nuno Sá, linux-kernel,
David Lechner
On Tue, Jul 15, 2025 at 12:05:15PM -0400, Sean Anderson wrote:
> On 7/15/25 04:41, Andy Shevchenko wrote:
> > On Mon, Jul 14, 2025 at 09:20:22PM -0400, Sean Anderson wrote:
...
> >> + return sprintf(buf, "%d\n", result);
> >
> > Mustn't be sysfs_emit() ?
>
> It doesn't matter in this case (as %d can never emit more
> than 20ish characters), but that works too.
Have you read the documentation? It uses word 'must'...
--
With Best Regards,
Andy Shevchenko
^ permalink raw reply [flat|nested] 62+ messages in thread
* Re: [PATCH 7/7] hwmon: iio: Add alarm support
2025-07-15 16:20 ` Sean Anderson
@ 2025-07-16 10:08 ` Andy Shevchenko
2025-07-17 16:23 ` Sean Anderson
0 siblings, 1 reply; 62+ messages in thread
From: Andy Shevchenko @ 2025-07-16 10:08 UTC (permalink / raw)
To: Sean Anderson
Cc: Jonathan Cameron, Jean Delvare, Guenter Roeck, linux-iio,
linux-hwmon, Andy Shevchenko, Nuno Sá, linux-kernel,
David Lechner
On Tue, Jul 15, 2025 at 12:20:24PM -0400, Sean Anderson wrote:
> On 7/15/25 04:50, Andy Shevchenko wrote:
> > On Mon, Jul 14, 2025 at 09:20:23PM -0400, Sean Anderson wrote:
...
> >> #include <linux/hwmon-sysfs.h>
> >
> > + blank line here..
>
> why?
To group the subsystem related headers (which are more custom and less generic).
This allows to follow what the subsystems are in use and what APIs / types are
taken.
> >> #include <linux/iio/consumer.h>
> >> +#include <linux/iio/events.h>
> >> +#include <linux/iio/iio.h>
> >> #include <linux/iio/types.h>
> >
> > ...and here?
>
> OK
>
> >> +#include <uapi/linux/iio/events.h>
As similar here, to visually split uAPI and the rest. This increases
readability and maintenance.
...
> >> +static ssize_t iio_hwmon_lookup_alarm(struct iio_hwmon_listener *listener,
> >> + u64 id)
> >> +{
> >> + ssize_t i;
> >> +
> >> + for (i = 0; i < listener->num_alarms; i++)
> >> + if (listener->ids[i] == id)
> >> + return i;
> >
> >> + return -1;
> >
> > -ENOENT ?
> > This will allow to propagate an error code to the upper layer(s).
>
> I suppose. But I think
>
> alarm = iio_hwmon_lookup_alarm(...);
> if (alarm < 0)
> return -ENOENT;
>
> is clearer than
I disagree. This makes it worth as it shadows other possible code(s), if any,
and makes harder to follow as reader has to check the callee implementation.
The shadow error codes need a justification.
> alarm = iio_hwmon_lookup_alarm(...);
> if (alarm < 0)
> return alarm;
>
> because you don't have to read the definition of iio_hwmon_lookup_alarm
> to determine what the return value is.
Exactly my point!
> >> +}
...
> >> +err_alarms:
> >> + kfree(listener->alarms);
> >> + kfree(listener->ids);
> >> +err_listener:
> >> + kfree(listener);
> >> +err_unlock:
> >> + mutex_unlock(&iio_hwmon_listener_lock);
> >> + return ERR_PTR(err);
> >
> > What about using __free()?
>
> That works for listener, but not for alarms or ids.
Why not?
...
> >> +static void iio_hwmon_listener_put(void *data)
> >> +{
> >> + struct iio_hwmon_listener *listener = data;
> >> +
> >> + scoped_guard(mutex, &iio_hwmon_listener_lock) {
> >> + if (unlikely(listener->refcnt == UINT_MAX))
> >> + return;
> >> +
> >> + if (--listener->refcnt)
> >> + return;
> >
> > Can the refcount_t be used with the respective APIs? Or even kref?
>
> Why? We do all the manipulation under a mutex, so there is no point in
> atomic access. Instead of the games refcnt_t has to play to try and
> prevent overflow we can just check for it directly.
refcount_t provides a facility of overflow/underflow. Also it gives better
understanding from the data type to see which value and how does that.
> >> + list_del(&listener->list);
> >> + iio_event_unregister(listener->indio_dev, &listener->block);
> >> + }
> >> +
> >> + kfree(listener->alarms);
> >> + kfree(listener->ids);
> >> + kfree(listener);
> >> +}
...
> >> + if (test_and_clear_bit(sattr->alarm, sattr->listener->alarms)) {
> >> + u64 id = sattr->listener->ids[sattr->alarm];
> >> + enum iio_event_direction dir = IIO_EVENT_CODE_EXTRACT_DIR(id);
> >> +
> >> + WARN_ON(iio_hwmon_alarm_toggle(chan, dir));
> >
> >> + strcpy(buf, "1\n");
> >> + return 2;
> >
> >> + }
> >> +
> >> + strcpy(buf, "0\n");
> >> + return 2;
> >
> > Better to assign the value and
> >
> > return sysfs_emit(...);
> >
> > which will make even easier to recognize that this is supplied to user via
> > sysfs.
>
> :l
>
> the things we do to avoid memcpy...
...for the cost of readability. Also this is a slow path.
--
With Best Regards,
Andy Shevchenko
^ permalink raw reply [flat|nested] 62+ messages in thread
* Re: [PATCH 1/7] math64: Add div64_s64_rem
2025-07-15 17:36 ` Sean Anderson
@ 2025-07-16 10:15 ` Andy Shevchenko
0 siblings, 0 replies; 62+ messages in thread
From: Andy Shevchenko @ 2025-07-16 10:15 UTC (permalink / raw)
To: Sean Anderson
Cc: Jonathan Cameron, Jean Delvare, Guenter Roeck, linux-iio,
linux-hwmon, Andy Shevchenko, Nuno Sá, linux-kernel,
David Lechner
On Tue, Jul 15, 2025 at 01:36:33PM -0400, Sean Anderson wrote:
> On 7/15/25 04:03, Andy Shevchenko wrote:
> > On Mon, Jul 14, 2025 at 09:20:17PM -0400, Sean Anderson wrote:
> >> Add a function to do signed 64-bit division with remainder. This is
> >> implemented using div64_u64_rem in the same way that div_s64_rem is
> >> implemented using div_u64_rem.
> >
> > LGTM, but one important Q. Can we (start to) add the test cases, please?
>
> Well, this just calls div64_u64_rem. So I am inclined to make the test something
> like
>
> #define test(n, d, q, r) ({ \
> u64 _q, _r; \
> _q = div64_u64_rem(n, d, &r); \
> assert(_q == q); \
> assert(_r == r); \
> })
>
> test( 3, 2, 1, 1);
> test( 3, -2, -1, 1);
> test(-3, 2, -1, -1);
> test(-3, -2, 1, -1);
Perhaps, but it should be done somewhere in lib/tests/...
--
With Best Regards,
Andy Shevchenko
^ permalink raw reply [flat|nested] 62+ messages in thread
* Re: [PATCH 7/7] hwmon: iio: Add alarm support
2025-07-16 6:37 ` Nuno Sá
@ 2025-07-17 16:00 ` Sean Anderson
2025-07-31 10:52 ` Nuno Sá
0 siblings, 1 reply; 62+ messages in thread
From: Sean Anderson @ 2025-07-17 16:00 UTC (permalink / raw)
To: Nuno Sá, Jonathan Cameron, Jean Delvare, Guenter Roeck,
linux-iio, linux-hwmon
Cc: Andy Shevchenko, Nuno Sá, linux-kernel, David Lechner
On 7/16/25 02:37, Nuno Sá wrote:
> On Tue, 2025-07-15 at 13:02 -0400, Sean Anderson wrote:
>> On 7/15/25 07:28, Nuno Sá wrote:
>> > On Mon, 2025-07-14 at 21:20 -0400, Sean Anderson wrote:
>> > > Add alarm support based on IIO threshold events. The alarm is cleared on
>> > > read, but will be set again if the condition is still present. This is
>> > > detected by disabling and re-enabling the event. The same trick is done
>> > > when creating the attribute to detect already-triggered events.
>> > >
>> > > The alarms are updated by an event listener. To keep the notifier call
>> > > chain short, we create one listener per iio device, shared across all
>> > > hwmon devices.
>> > >
>> > > To avoid dynamic creation of alarms, alarms for all possible events are
>> > > allocated at creation. Lookup is done by a linear scan, as I expect
>> > > events to occur rarely. If performance becomes an issue, a binary search
>> > > could be done instead (or some kind of hash lookup).
>> > >
>> > > Signed-off-by: Sean Anderson <sean.anderson@linux.dev>
>> > > ---
>> > >
>> > > drivers/hwmon/iio_hwmon.c | 322 +++++++++++++++++++++++++++++++++++++-
>> > > 1 file changed, 321 insertions(+), 1 deletion(-)
>> > >
>> > > diff --git a/drivers/hwmon/iio_hwmon.c b/drivers/hwmon/iio_hwmon.c
>> > > index 3db4d4b30022..c963bc5452ba 100644
>> > > --- a/drivers/hwmon/iio_hwmon.c
>> > > +++ b/drivers/hwmon/iio_hwmon.c
>> > > @@ -8,6 +8,7 @@
>> > > #include <linux/slab.h>
>> > > #include <linux/mod_devicetable.h>
>> > > #include <linux/module.h>
>> > > +#include <linux/notifier.h>
>> > > #include <linux/err.h>
>> > > #include <linux/platform_device.h>
>> > > #include <linux/property.h>
>> > > @@ -15,7 +16,192 @@
>> > > #include <linux/hwmon.h>
>> > > #include <linux/hwmon-sysfs.h>
>> > > #include <linux/iio/consumer.h>
>> > > +#include <linux/iio/events.h>
>> > > +#include <linux/iio/iio.h>
>> > > #include <linux/iio/types.h>
>> > > +#include <uapi/linux/iio/events.h>
>> > > +
>> > > +/* Protects iio_hwmon_listeners and listeners' refcnt */
>> > > +DEFINE_MUTEX(iio_hwmon_listener_lock);
>> > > +LIST_HEAD(iio_hwmon_listeners);
>> > > +
>> > > +/**
>> > > + * struct iio_hwmon_listener - Listener for IIO events
>> > > + * @block: Notifier for events
>> > > + * @ids: Array of IIO event ids, one per alarm
>> > > + * @alarms: Bitmap of alarms
>> > > + * @num_alarms: Length of @ids and @alarms
>> > > + * @indio_dev: Device we are listening to
>> > > + * @list: List of all listeners
>> > > + * @refcnt: Reference count
>> > > + */
>> > > +struct iio_hwmon_listener {
>> > > + struct notifier_block block;
>> > > + u64 *ids;
>> > > + unsigned long *alarms;
>> > > + size_t num_alarms;
>> > > +
>> > > + struct iio_dev *indio_dev;
>> > > + struct list_head list;
>> > > + unsigned int refcnt;
>> > > +};
>> > > +
>> > > +/**
>> > > + * iio_hwmon_lookup_alarm() - Find an alarm by id
>> > > + * @listener: Event listener
>> > > + * @id: IIO event id
>> > > + *
>> > > + * Return: index of @id in @listener->ids, or -1 if not found
>> > > + */
>> > > +static ssize_t iio_hwmon_lookup_alarm(struct iio_hwmon_listener *listener,
>> > > + u64 id)
>> > > +{
>> > > + ssize_t i;
>> > > +
>> > > + for (i = 0; i < listener->num_alarms; i++)
>> > > + if (listener->ids[i] == id)
>> > > + return i;
>> > > +
>> > > + return -1;
>> > > +}
>> > > +
>> > > +static int iio_hwmon_listener_callback(struct notifier_block *block,
>> > > + unsigned long action, void *data)
>> > > +{
>> > > + struct iio_hwmon_listener *listener =
>> > > + container_of(block, struct iio_hwmon_listener, block);
>> > > + struct iio_event_data *ev = data;
>> > > + ssize_t i;
>> > > +
>> > > + if (action != IIO_NOTIFY_EVENT)
>> > > + return NOTIFY_DONE;
>> > > +
>> > > + i = iio_hwmon_lookup_alarm(listener, ev->id);
>> > > + if (i >= 0)
>> > > + set_bit(i, listener->alarms);
>> > > + else
>> > > + dev_warn_once(&listener->indio_dev->dev,
>> > > + "unknown event %016llx\n", ev->id);
>> > > +
>> > > + return NOTIFY_DONE;
>> > > +}
>> > > +
>> > > +/**
>> > > + * iio_event_id() - Calculate an IIO event id
>> > > + * @channel: IIO channel for this event
>> > > + * @type: Event type (theshold, rate-of-change, etc.)
>> > > + * @dir: Event direction (rising, falling, etc.)
>> > > + *
>> > > + * Return: IIO event id corresponding to this event's IIO id
>> > > + */
>> > > +static u64 iio_event_id(struct iio_chan_spec const *chan,
>> > > + enum iio_event_type type,
>> > > + enum iio_event_direction dir)
>> > > +{
>> > > + if (chan->differential)
>> > > + return IIO_DIFF_EVENT_CODE(chan->type, chan->channel,
>> > > + chan->channel2, type, dir);
>> > > + if (chan->modified)
>> > > + return IIO_MOD_EVENT_CODE(chan->type, chan->channel,
>> > > + chan->channel2, type, dir);
>> > > + return IIO_UNMOD_EVENT_CODE(chan->type, chan->channel, type, dir);
>> > > +}
>> > > +
>> > > +/**
>> > > + * iio_hwmon_listener_get() - Get a listener for an IIO device
>> > > + * @indio_dev: IIO device to listen to
>> > > + *
>> > > + * Look up or create a new listener for @indio_dev. The returned listener is
>> > > + * registered with @indio_dev, but events still need to be manually enabled.
>> > > + * You must call iio_hwmon_listener_put() when you are done.
>> > > + *
>> > > + * Return: Listener for @indio_dev, or an error pointer
>> > > + */
>> > > +static struct iio_hwmon_listener *iio_hwmon_listener_get(struct iio_dev
>> > > *indio_dev)
>> > > +{
>> > > + struct iio_hwmon_listener *listener;
>> > > + int err = -ENOMEM;
>> > > + size_t i, j;
>> > > +
>> > > + guard(mutex)(&iio_hwmon_listener_lock);
>> > > + list_for_each_entry(listener, &iio_hwmon_listeners, list) {
>> > > + if (listener->indio_dev == indio_dev) {
>> > > + if (likely(listener->refcnt != UINT_MAX))
>> > > + listener->refcnt++;
>> >
>> > I dunno for the above to ever happen :).
>>
>> Well, I can remove it if you like.
>>
>> > And as Andy stated, let's just use proper refcount APIs.
>>
>> No point in using atomic ops if they are only accessed under a mutex.
>
> Not the point... If there are proper APIs for handling things like this, not sure why
> not using and then coming up with things like the above? And the same goes to the
> release path.
The API is for doing reference counts *atomically*. If you do not need
atomic reference counting, then it is the *wrong* API. I suggest reading
the block comment at the beginning of refcnt.h to see the sorts of
contortions it has to go through because it is an atomic API. Since we
hold a mutex, we can just increment/decrement. I will remove the
saturation check to avoid confusion.
--Sean
^ permalink raw reply [flat|nested] 62+ messages in thread
* Re: [PATCH 6/7] hwmon: iio: Add min/max support
2025-07-16 10:01 ` Andy Shevchenko
@ 2025-07-17 16:11 ` Sean Anderson
0 siblings, 0 replies; 62+ messages in thread
From: Sean Anderson @ 2025-07-17 16:11 UTC (permalink / raw)
To: Andy Shevchenko
Cc: Jonathan Cameron, Jean Delvare, Guenter Roeck, linux-iio,
linux-hwmon, Andy Shevchenko, Nuno Sá, linux-kernel,
David Lechner
On 7/16/25 06:01, Andy Shevchenko wrote:
> On Tue, Jul 15, 2025 at 12:05:15PM -0400, Sean Anderson wrote:
>> On 7/15/25 04:41, Andy Shevchenko wrote:
>> > On Mon, Jul 14, 2025 at 09:20:22PM -0400, Sean Anderson wrote:
>
> ...
>
>> >> + return sprintf(buf, "%d\n", result);
>> >
>> > Mustn't be sysfs_emit() ?
>>
>> It doesn't matter in this case (as %d can never emit more
>> than 20ish characters), but that works too.
>
> Have you read the documentation? It uses word 'must'...
>
Documentation for what? sysfs_emit just says it's aware of the
PAGE_SIZE buffer. Nothing about that it "must" be used.
--Sean
^ permalink raw reply [flat|nested] 62+ messages in thread
* Re: [PATCH 7/7] hwmon: iio: Add alarm support
2025-07-16 10:08 ` Andy Shevchenko
@ 2025-07-17 16:23 ` Sean Anderson
2025-07-21 7:42 ` Andy Shevchenko
0 siblings, 1 reply; 62+ messages in thread
From: Sean Anderson @ 2025-07-17 16:23 UTC (permalink / raw)
To: Andy Shevchenko
Cc: Jonathan Cameron, Jean Delvare, Guenter Roeck, linux-iio,
linux-hwmon, Andy Shevchenko, Nuno Sá, linux-kernel,
David Lechner
On 7/16/25 06:08, Andy Shevchenko wrote:
> On Tue, Jul 15, 2025 at 12:20:24PM -0400, Sean Anderson wrote:
>> On 7/15/25 04:50, Andy Shevchenko wrote:
>> > On Mon, Jul 14, 2025 at 09:20:23PM -0400, Sean Anderson wrote:
>
> ...
>
>> >> #include <linux/hwmon-sysfs.h>
>> >
>> > + blank line here..
>>
>> why?
>
> To group the subsystem related headers (which are more custom and less generic).
> This allows to follow what the subsystems are in use and what APIs / types are
> taken.
Then you should send a patch for coding-style.rst.
>> >> #include <linux/iio/consumer.h>
>> >> +#include <linux/iio/events.h>
>> >> +#include <linux/iio/iio.h>
>> >> #include <linux/iio/types.h>
>> >
>> > ...and here?
>>
>> OK
>>
>> >> +#include <uapi/linux/iio/events.h>
>
> As similar here, to visually split uAPI and the rest. This increases
> readability and maintenance.
>
> ...
>
>> >> +static ssize_t iio_hwmon_lookup_alarm(struct iio_hwmon_listener *listener,
>> >> + u64 id)
>> >> +{
>> >> + ssize_t i;
>> >> +
>> >> + for (i = 0; i < listener->num_alarms; i++)
>> >> + if (listener->ids[i] == id)
>> >> + return i;
>> >
>> >> + return -1;
>> >
>> > -ENOENT ?
>> > This will allow to propagate an error code to the upper layer(s).
>>
>> I suppose. But I think
>>
>> alarm = iio_hwmon_lookup_alarm(...);
>> if (alarm < 0)
>> return -ENOENT;
>>
>> is clearer than
>
> I disagree. This makes it worth as it shadows other possible code(s), if any,
> and makes harder to follow as reader has to check the callee implementation.
>
> The shadow error codes need a justification.
OK, I will return a bool next time to avoid any misconceptions that the return
code means anything other than "found" or "not found"
>> alarm = iio_hwmon_lookup_alarm(...);
>> if (alarm < 0)
>> return alarm;
>>
>> because you don't have to read the definition of iio_hwmon_lookup_alarm
>> to determine what the return value is.
>
> Exactly my point!
your point is that you want readers to have to read the definition of
iio_hwmon_lookup_alarm in order to determine that ENOENT is a possible
error from add_alarm_attr? I don't follow.
>> >> +}
>
> ...
>
>> >> +err_alarms:
>> >> + kfree(listener->alarms);
>> >> + kfree(listener->ids);
>> >> +err_listener:
>> >> + kfree(listener);
>> >> +err_unlock:
>> >> + mutex_unlock(&iio_hwmon_listener_lock);
>> >> + return ERR_PTR(err);
>> >
>> > What about using __free()?
>>
>> That works for listener, but not for alarms or ids.
>
> Why not?
>
> ...
>
>> >> +static void iio_hwmon_listener_put(void *data)
>> >> +{
>> >> + struct iio_hwmon_listener *listener = data;
>> >> +
>> >> + scoped_guard(mutex, &iio_hwmon_listener_lock) {
>> >> + if (unlikely(listener->refcnt == UINT_MAX))
>> >> + return;
>> >> +
>> >> + if (--listener->refcnt)
>> >> + return;
>> >
>> > Can the refcount_t be used with the respective APIs? Or even kref?
>>
>> Why? We do all the manipulation under a mutex, so there is no point in
>> atomic access. Instead of the games refcnt_t has to play to try and
>> prevent overflow we can just check for it directly.
>
> refcount_t provides a facility of overflow/underflow.
refcount_t can't prevent underflow because it's atomic. All it can do is
warn after the fact. And of course overflow is handled properly here.
But it can't occur in practice unless you specifically load multiple
devicetrees at runtime. So we don't need it anyway.
> Also it gives better
> understanding from the data type to see which value and how does that.
That's why I named the variable "refcnt".
>> >> + list_del(&listener->list);
>> >> + iio_event_unregister(listener->indio_dev, &listener->block);
>> >> + }
>> >> +
>> >> + kfree(listener->alarms);
>> >> + kfree(listener->ids);
>> >> + kfree(listener);
>> >> +}
>
> ...
>
>> >> + if (test_and_clear_bit(sattr->alarm, sattr->listener->alarms)) {
>> >> + u64 id = sattr->listener->ids[sattr->alarm];
>> >> + enum iio_event_direction dir = IIO_EVENT_CODE_EXTRACT_DIR(id);
>> >> +
>> >> + WARN_ON(iio_hwmon_alarm_toggle(chan, dir));
>> >
>> >> + strcpy(buf, "1\n");
>> >> + return 2;
>> >
>> >> + }
>> >> +
>> >> + strcpy(buf, "0\n");
>> >> + return 2;
>> >
>> > Better to assign the value and
>> >
>> > return sysfs_emit(...);
>> >
>> > which will make even easier to recognize that this is supplied to user via
>> > sysfs.
>>
>> :l
>>
>> the things we do to avoid memcpy...
>
> ...for the cost of readability. Also this is a slow path.
^ permalink raw reply [flat|nested] 62+ messages in thread
* Re: [PATCH 2/7] iio: inkern: Add API for reading/writing events
2025-07-16 9:28 ` Andy Shevchenko
@ 2025-07-17 16:42 ` Sean Anderson
2025-07-27 15:55 ` Jonathan Cameron
0 siblings, 1 reply; 62+ messages in thread
From: Sean Anderson @ 2025-07-17 16:42 UTC (permalink / raw)
To: Andy Shevchenko
Cc: Jonathan Cameron, Jean Delvare, Guenter Roeck, linux-iio,
linux-hwmon, Andy Shevchenko, Nuno Sá, linux-kernel,
David Lechner
On 7/16/25 05:28, Andy Shevchenko wrote:
> On Tue, Jul 15, 2025 at 11:42:05AM -0400, Sean Anderson wrote:
>> On 7/15/25 04:18, Andy Shevchenko wrote:
>> > On Mon, Jul 14, 2025 at 09:20:18PM -0400, Sean Anderson wrote:
>
> ...
>
>> >> +EXPORT_SYMBOL_GPL(iio_event_mode);
>> >
>> > Can we move this to namespace? Otherwise it will be never ending story...
>> > Ditto for other new APIs.
>>
>> Never ending story of what?
>
> Of converting IIO core to use exported namespaces.
What's the purpose?
>> >> + if (scale64 <= INT_MAX && scale64 >= INT_MIN)
>> >> + raw64 = processed / (int)scale64;
>> >
>> > Do you need the casting? (I mean if the compiler is dumb enough to not see this)
>>
>> AIUI 64-bit division is not available on 32-bit platforms. The cast
>> ensures we get 32-bit division.
>
> I put specifically a remark in the parentheses. So, the Q is if the compiler
> doesn't recognize that. Can you confirm that 32-bit compilation without cast
> is broken?
inkern.c:(.text.iio_write_event_processed_scale+0x14c): undefined reference to `__aeabi_ldivmod'
>> >> + *raw = clamp(raw64, (s64)INT_MIN, (s64)INT_MAX);
>> >
>> > You already have similar approach here...
>>
>> Well, I can spell it 0x7fffffffLL if you'd like...
>
> Nope, I like to have named constants instead of magics, but actually are those
> castings needed for the clamp()?
Apparently not. The checks in __clamp_once are only for matching signedness. And
the ints are promoted to s64s when the comparison is made.
--Sean
^ permalink raw reply [flat|nested] 62+ messages in thread
* Re: [PATCH 7/7] hwmon: iio: Add alarm support
2025-07-17 16:23 ` Sean Anderson
@ 2025-07-21 7:42 ` Andy Shevchenko
2025-07-21 14:24 ` Sean Anderson
0 siblings, 1 reply; 62+ messages in thread
From: Andy Shevchenko @ 2025-07-21 7:42 UTC (permalink / raw)
To: Sean Anderson
Cc: Jonathan Cameron, Jean Delvare, Guenter Roeck, linux-iio,
linux-hwmon, Andy Shevchenko, Nuno Sá, linux-kernel,
David Lechner
On Thu, Jul 17, 2025 at 12:23:58PM -0400, Sean Anderson wrote:
> On 7/16/25 06:08, Andy Shevchenko wrote:
> > On Tue, Jul 15, 2025 at 12:20:24PM -0400, Sean Anderson wrote:
> >> On 7/15/25 04:50, Andy Shevchenko wrote:
> >> > On Mon, Jul 14, 2025 at 09:20:23PM -0400, Sean Anderson wrote:
...
> >> >> #include <linux/hwmon-sysfs.h>
> >> >
> >> > + blank line here..
> >>
> >> why?
> >
> > To group the subsystem related headers (which are more custom and less generic).
> > This allows to follow what the subsystems are in use and what APIs / types are
> > taken.
>
> Then you should send a patch for coding-style.rst.
Does any of the common sense approach need to be written in the documentation?
> >> >> #include <linux/iio/consumer.h>
> >> >> +#include <linux/iio/events.h>
> >> >> +#include <linux/iio/iio.h>
> >> >> #include <linux/iio/types.h>
> >> >
> >> > ...and here?
> >>
> >> OK
> >>
> >> >> +#include <uapi/linux/iio/events.h>
> >
> > As similar here, to visually split uAPI and the rest. This increases
> > readability and maintenance.
...
> >> >> +static ssize_t iio_hwmon_lookup_alarm(struct iio_hwmon_listener *listener,
> >> >> + u64 id)
> >> >> +{
> >> >> + ssize_t i;
> >> >> +
> >> >> + for (i = 0; i < listener->num_alarms; i++)
> >> >> + if (listener->ids[i] == id)
> >> >> + return i;
> >> >
> >> >> + return -1;
> >> >
> >> > -ENOENT ?
> >> > This will allow to propagate an error code to the upper layer(s).
> >>
> >> I suppose. But I think
> >>
> >> alarm = iio_hwmon_lookup_alarm(...);
> >> if (alarm < 0)
> >> return -ENOENT;
> >>
> >> is clearer than
> >
> > I disagree. This makes it worth as it shadows other possible code(s), if any,
> > and makes harder to follow as reader has to check the callee implementation.
> >
> > The shadow error codes need a justification.
>
> OK, I will return a bool next time to avoid any misconceptions that the return
> code means anything other than "found" or "not found"
This makes sense. And IIRC it's even documented.
> >> alarm = iio_hwmon_lookup_alarm(...);
> >> if (alarm < 0)
> >> return alarm;
> >>
> >> because you don't have to read the definition of iio_hwmon_lookup_alarm
> >> to determine what the return value is.
> >
> > Exactly my point!
>
> your point is that you want readers to have to read the definition of
> iio_hwmon_lookup_alarm in order to determine that ENOENT is a possible
> error from add_alarm_attr? I don't follow.
No, my point is that readers should not care about error code. If it's
propagated to the upper layer, the upper layer will decide on how to proceed.
And -ENOENT is de facto standard for "entity not found".
> >> >> +}
...
> >> >> +err_alarms:
> >> >> + kfree(listener->alarms);
> >> >> + kfree(listener->ids);
> >> >> +err_listener:
> >> >> + kfree(listener);
> >> >> +err_unlock:
> >> >> + mutex_unlock(&iio_hwmon_listener_lock);
> >> >> + return ERR_PTR(err);
> >> >
> >> > What about using __free()?
> >>
> >> That works for listener, but not for alarms or ids.
> >
> > Why not?
No answer? Have you checked how cleanup.h suggests to avoid cleaning the memory
when it's supposed to be used later on?
...
> >> >> +static void iio_hwmon_listener_put(void *data)
> >> >> +{
> >> >> + struct iio_hwmon_listener *listener = data;
> >> >> +
> >> >> + scoped_guard(mutex, &iio_hwmon_listener_lock) {
> >> >> + if (unlikely(listener->refcnt == UINT_MAX))
> >> >> + return;
> >> >> +
> >> >> + if (--listener->refcnt)
> >> >> + return;
> >> >
> >> > Can the refcount_t be used with the respective APIs? Or even kref?
> >>
> >> Why? We do all the manipulation under a mutex, so there is no point in
> >> atomic access. Instead of the games refcnt_t has to play to try and
> >> prevent overflow we can just check for it directly.
> >
> > refcount_t provides a facility of overflow/underflow.
>
> refcount_t can't prevent underflow because it's atomic. All it can do is
> warn after the fact. And of course overflow is handled properly here.
> But it can't occur in practice unless you specifically load multiple
> devicetrees at runtime. So we don't need it anyway.
It will warn the user in such cases. Your code won't do it, even if it's not a
big deal or never happens situation, it's still better to use in-kernel
standard ways of handling these things.
> > Also it gives better
> > understanding from the data type to see which value and how does that.
>
> That's why I named the variable "refcnt".
Yes, and that's why I asked about existing interface / API / type to use.
> >> >> + list_del(&listener->list);
> >> >> + iio_event_unregister(listener->indio_dev, &listener->block);
> >> >> + }
> >> >> +
> >> >> + kfree(listener->alarms);
> >> >> + kfree(listener->ids);
> >> >> + kfree(listener);
> >> >> +}
--
With Best Regards,
Andy Shevchenko
^ permalink raw reply [flat|nested] 62+ messages in thread
* Re: [PATCH 7/7] hwmon: iio: Add alarm support
2025-07-21 7:42 ` Andy Shevchenko
@ 2025-07-21 14:24 ` Sean Anderson
0 siblings, 0 replies; 62+ messages in thread
From: Sean Anderson @ 2025-07-21 14:24 UTC (permalink / raw)
To: Andy Shevchenko
Cc: Jonathan Cameron, Jean Delvare, Guenter Roeck, linux-iio,
linux-hwmon, Andy Shevchenko, Nuno Sá, linux-kernel,
David Lechner
On 7/21/25 03:42, Andy Shevchenko wrote:
> On Thu, Jul 17, 2025 at 12:23:58PM -0400, Sean Anderson wrote:
>> On 7/16/25 06:08, Andy Shevchenko wrote:
>> > On Tue, Jul 15, 2025 at 12:20:24PM -0400, Sean Anderson wrote:
>> >> On 7/15/25 04:50, Andy Shevchenko wrote:
>> >> > On Mon, Jul 14, 2025 at 09:20:23PM -0400, Sean Anderson wrote:
>
> ...
>
>> >> >> #include <linux/hwmon-sysfs.h>
>> >> >
>> >> > + blank line here..
>> >>
>> >> why?
>> >
>> > To group the subsystem related headers (which are more custom and less generic).
>> > This allows to follow what the subsystems are in use and what APIs / types are
>> > taken.
>>
>> Then you should send a patch for coding-style.rst.
>
> Does any of the common sense approach need to be written in the documentation?
Yes! My base assumption would be that includes should be alphabetized,
but that no other ordering or spacing is necessary. Your "common sense"
is not so common.
>> >> >> #include <linux/iio/consumer.h>
>> >> >> +#include <linux/iio/events.h>
>> >> >> +#include <linux/iio/iio.h>
>> >> >> #include <linux/iio/types.h>
>> >> >
>> >> > ...and here?
>> >>
>> >> OK
>> >>
>> >> >> +#include <uapi/linux/iio/events.h>
>> >
>> > As similar here, to visually split uAPI and the rest. This increases
>> > readability and maintenance.
>
> ...
>
>> >> >> +static ssize_t iio_hwmon_lookup_alarm(struct iio_hwmon_listener *listener,
>> >> >> + u64 id)
>> >> >> +{
>> >> >> + ssize_t i;
>> >> >> +
>> >> >> + for (i = 0; i < listener->num_alarms; i++)
>> >> >> + if (listener->ids[i] == id)
>> >> >> + return i;
>> >> >
>> >> >> + return -1;
>> >> >
>> >> > -ENOENT ?
>> >> > This will allow to propagate an error code to the upper layer(s).
>> >>
>> >> I suppose. But I think
>> >>
>> >> alarm = iio_hwmon_lookup_alarm(...);
>> >> if (alarm < 0)
>> >> return -ENOENT;
>> >>
>> >> is clearer than
>> >
>> > I disagree. This makes it worth as it shadows other possible code(s), if any,
>> > and makes harder to follow as reader has to check the callee implementation.
>> >
>> > The shadow error codes need a justification.
>>
>> OK, I will return a bool next time to avoid any misconceptions that the return
>> code means anything other than "found" or "not found"
>
> This makes sense. And IIRC it's even documented.
>
>> >> alarm = iio_hwmon_lookup_alarm(...);
>> >> if (alarm < 0)
>> >> return alarm;
>> >>
>> >> because you don't have to read the definition of iio_hwmon_lookup_alarm
>> >> to determine what the return value is.
>> >
>> > Exactly my point!
>>
>> your point is that you want readers to have to read the definition of
>> iio_hwmon_lookup_alarm in order to determine that ENOENT is a possible
>> error from add_alarm_attr? I don't follow.
>
> No, my point is that readers should not care about error code. If it's
> propagated to the upper layer, the upper layer will decide on how to proceed.
> And -ENOENT is de facto standard for "entity not found".
>
>> >> >> +}
>
> ...
>
>> >> >> +err_alarms:
>> >> >> + kfree(listener->alarms);
>> >> >> + kfree(listener->ids);
>> >> >> +err_listener:
>> >> >> + kfree(listener);
>> >> >> +err_unlock:
>> >> >> + mutex_unlock(&iio_hwmon_listener_lock);
>> >> >> + return ERR_PTR(err);
>> >> >
>> >> > What about using __free()?
>> >>
>> >> That works for listener, but not for alarms or ids.
>> >
>> > Why not?
>
> No answer? Have you checked how cleanup.h suggests to avoid cleaning the memory
> when it's supposed to be used later on?
Sorry, I missed this the first time. Anyway the reason it doesn't work
for alarms/ids is that those are members of listener and not separate
variables. And I think it's more concise this way. Compare the existing
listener->alarms = bitmap_zalloc(listener->num_alarms, GFP_KERNEL);
if (!listener->alarms)
goto err_listener;
/* ... */
return listener;
err_alarms:
kfree(listener->alarms);
/* ... */
vs
unsigned long __free alarms = NULL;
alarms = bitmap_zalloc(listener->num_alarms, GFP_KERNEL);
if (!alarms)
return -ENOMEM;
listener->alarms = alarms;
/* ... */
no_free_ptr(alarms);
return listener;
I don't really think there's an advantage.
>> >> >> +static void iio_hwmon_listener_put(void *data)
>> >> >> +{
>> >> >> + struct iio_hwmon_listener *listener = data;
>> >> >> +
>> >> >> + scoped_guard(mutex, &iio_hwmon_listener_lock) {
>> >> >> + if (unlikely(listener->refcnt == UINT_MAX))
>> >> >> + return;
>> >> >> +
>> >> >> + if (--listener->refcnt)
>> >> >> + return;
>> >> >
>> >> > Can the refcount_t be used with the respective APIs? Or even kref?
>> >>
>> >> Why? We do all the manipulation under a mutex, so there is no point in
>> >> atomic access. Instead of the games refcnt_t has to play to try and
>> >> prevent overflow we can just check for it directly.
>> >
>> > refcount_t provides a facility of overflow/underflow.
>>
>> refcount_t can't prevent underflow because it's atomic. All it can do is
>> warn after the fact. And of course overflow is handled properly here.
>> But it can't occur in practice unless you specifically load multiple
>> devicetrees at runtime. So we don't need it anyway.
>
> It will warn the user in such cases. Your code won't do it, even if it's not a
> big deal or never happens situation, it's still better to use in-kernel
> standard ways of handling these things.
It's not the standard for refcounts protected by a mutex. There are
literally hundreds of existing examples of this.
--Sean
^ permalink raw reply [flat|nested] 62+ messages in thread
* Re: [PATCH 2/7] iio: inkern: Add API for reading/writing events
2025-07-17 16:42 ` Sean Anderson
@ 2025-07-27 15:55 ` Jonathan Cameron
0 siblings, 0 replies; 62+ messages in thread
From: Jonathan Cameron @ 2025-07-27 15:55 UTC (permalink / raw)
To: Sean Anderson
Cc: Andy Shevchenko, Jean Delvare, Guenter Roeck, linux-iio,
linux-hwmon, Andy Shevchenko, Nuno Sá, linux-kernel,
David Lechner
On Thu, 17 Jul 2025 12:42:13 -0400
Sean Anderson <sean.anderson@linux.dev> wrote:
> On 7/16/25 05:28, Andy Shevchenko wrote:
> > On Tue, Jul 15, 2025 at 11:42:05AM -0400, Sean Anderson wrote:
> >> On 7/15/25 04:18, Andy Shevchenko wrote:
> >> > On Mon, Jul 14, 2025 at 09:20:18PM -0400, Sean Anderson wrote:
> >
> > ...
> >
> >> >> +EXPORT_SYMBOL_GPL(iio_event_mode);
> >> >
> >> > Can we move this to namespace? Otherwise it will be never ending story...
> >> > Ditto for other new APIs.
> >>
> >> Never ending story of what?
> >
> > Of converting IIO core to use exported namespaces.
>
> What's the purpose?
Aim here is in general to reduce the massive exposed ABI by applying some
namespaces so that only drivers that opt in to specific functionality
can use particular symbols.
We've used it extensively for groups of related drivers and to some
libraries and the DMA buffers, but so far not pushed it into the IIO core.
I'd be fine with these new functions all being under IIO_CONSUMER or similar.
Quite a bit of feedback on this set will be of the lines of don't do it
the way we did it before as now we know better!
>
> >> >> + if (scale64 <= INT_MAX && scale64 >= INT_MIN)
> >> >> + raw64 = processed / (int)scale64;
> >> >
> >> > Do you need the casting? (I mean if the compiler is dumb enough to not see this)
> >>
> >> AIUI 64-bit division is not available on 32-bit platforms. The cast
> >> ensures we get 32-bit division.
> >
> > I put specifically a remark in the parentheses. So, the Q is if the compiler
> > doesn't recognize that. Can you confirm that 32-bit compilation without cast
> > is broken?
>
> inkern.c:(.text.iio_write_event_processed_scale+0x14c): undefined reference to `__aeabi_ldivmod'
>
> >> >> + *raw = clamp(raw64, (s64)INT_MIN, (s64)INT_MAX);
> >> >
> >> > You already have similar approach here...
> >>
> >> Well, I can spell it 0x7fffffffLL if you'd like...
> >
> > Nope, I like to have named constants instead of magics, but actually are those
> > castings needed for the clamp()?
>
> Apparently not. The checks in __clamp_once are only for matching signedness. And
> the ints are promoted to s64s when the comparison is made.
>
> --Sean
^ permalink raw reply [flat|nested] 62+ messages in thread
* Re: [PATCH 2/7] iio: inkern: Add API for reading/writing events
2025-07-15 1:20 ` [PATCH 2/7] iio: inkern: Add API for reading/writing events Sean Anderson
2025-07-15 8:18 ` Andy Shevchenko
2025-07-15 10:35 ` Nuno Sá
@ 2025-07-27 16:13 ` Jonathan Cameron
2 siblings, 0 replies; 62+ messages in thread
From: Jonathan Cameron @ 2025-07-27 16:13 UTC (permalink / raw)
To: Sean Anderson
Cc: Jean Delvare, Guenter Roeck, linux-iio, linux-hwmon,
Andy Shevchenko, Nuno Sá, linux-kernel, David Lechner
On Mon, 14 Jul 2025 21:20:18 -0400
Sean Anderson <sean.anderson@linux.dev> wrote:
> Add an in-kernel API for reading/writing event properties. Like the
> raw-to-processed conversion, with processed-to-raw we only convert the
> integer part, introducing some round-off error.
>
> A common case is for other drivers to re-expose IIO events as sysfs
> properties with a different API. To help out with this, iio_event_mode
> returns the appropriate mode. It can also be used to test for existence
> if the consumer doesn't care about read/write capability.
>
> Signed-off-by: Sean Anderson <sean.anderson@linux.dev>
Hi Sean,
A few minor comments inline.
> ---
>
> drivers/iio/inkern.c | 198 +++++++++++++++++++++++++++++++++++
> include/linux/iio/consumer.h | 56 ++++++++++
> 2 files changed, 254 insertions(+)
>
> diff --git a/drivers/iio/inkern.c b/drivers/iio/inkern.c
> index c174ebb7d5e6..d3bbd2444fb5 100644
> --- a/drivers/iio/inkern.c
> +++ b/drivers/iio/inkern.c
> @@ -1028,3 +1028,201 @@ ssize_t iio_read_channel_label(struct iio_channel *chan, char *buf)
> return do_iio_read_channel_label(chan->indio_dev, chan->channel, buf);
> }
> EXPORT_SYMBOL_GPL(iio_read_channel_label);
> +umode_t iio_event_mode(struct iio_channel *chan, enum iio_event_type type,
> + enum iio_event_direction dir, enum iio_event_info info)
> +{
> + struct iio_dev *indio_dev = chan->indio_dev;
> + struct iio_dev_opaque *iio_dev_opaque = to_iio_dev_opaque(indio_dev);
> + umode_t mode = 0;
> +
> + guard(mutex)(&iio_dev_opaque->info_exist_lock);
> + if (!iio_event_exists(chan, type, dir, info))
> + return 0;
> +
> + if (info == IIO_EV_INFO_ENABLE) {
> + if (indio_dev->info->read_event_config)
> + mode |= 0444;
> +
> + if (indio_dev->info->write_event_config)
> + mode |= 0200;
> + } else {
> + if (indio_dev->info->read_event_value)
> + mode |= 0444;
> +
> + if (indio_dev->info->write_event_value)
> + mode |= 0200;
> + }
> +
> + return mode;
> +}
> +EXPORT_SYMBOL_GPL(iio_event_mode);
> +
> +int iio_read_event_processed_scale(struct iio_channel *chan,
> + enum iio_event_type type,
> + enum iio_event_direction dir,
> + enum iio_event_info info, int *val,
Maybe rename info to ev_info or similar to avoid confusion with
indio_dev->info
> + unsigned int scale)
> +{
> + struct iio_dev *indio_dev = chan->indio_dev;
> + struct iio_dev_opaque *iio_dev_opaque = to_iio_dev_opaque(indio_dev);
> + int ret, raw;
> +
> + guard(mutex)(&iio_dev_opaque->info_exist_lock);
> + if (!iio_event_exists(chan, type, dir, info))
> + return -ENODEV;
> +
Perhaps a local variable
struct iio_info info;
info = indio_dev->info;
> + if (info == IIO_EV_INFO_ENABLE) {
> + if (!indio_dev->info->read_event_config)
> + return -EINVAL;
> +
> + raw = indio_dev->info->read_event_config(indio_dev,
raw = info->read_event_config(indio_dev, chan->channel,
type, dir);
> + chan->channel, type,
> + dir);
> + if (raw < 0)
> + return raw;
> +
> + *val = raw;
> + return 0;
> + }
> +
> + if (!indio_dev->info->read_event_value)
> + return -EINVAL;
> +
> + ret = indio_dev->info->read_event_value(indio_dev, chan->channel, type,
> + dir, info, &raw, NULL);
> + if (ret < 0)
> + return ret;
> +
> + return iio_convert_raw_to_processed_unlocked(chan, raw, val, scale);
> +}
> +EXPORT_SYMBOL_GPL(iio_read_event_processed_scale);
> +
> +int iio_write_event_processed_scale(struct iio_channel *chan,
> + enum iio_event_type type,
> + enum iio_event_direction dir,
> + enum iio_event_info info, int processed,
> + unsigned int scale)
> +{
> + struct iio_dev *indio_dev = chan->indio_dev;
> + struct iio_dev_opaque *iio_dev_opaque = to_iio_dev_opaque(chan->indio_dev);
> + int ret, raw;
> +
> + guard(mutex)(&iio_dev_opaque->info_exist_lock);
> + if (!iio_event_exists(chan, type, dir, info))
> + return -ENODEV;
> +
> + if (info == IIO_EV_INFO_ENABLE) {
> + if (!indio_dev->info->write_event_config)
> + return -EINVAL;
> +
> + return indio_dev->info->write_event_config(indio_dev,
Similar to above, feels like a local variable to shorten these would be good,
> + chan->channel, type,
> + dir, processed);
> + }
> +
> + if (!indio_dev->info->write_event_value)
> + return -EINVAL;
> +
> + ret = iio_convert_processed_to_raw_unlocked(chan, processed, &raw,
> + scale);
> + if (ret < 0)
> + return ret;
> +
> + return indio_dev->info->write_event_value(indio_dev, chan->channel,
> + type, dir, info, raw, 0);
> +}
> +EXPORT_SYMBOL_GPL(iio_write_event_processed_scale);
> diff --git a/include/linux/iio/consumer.h b/include/linux/iio/consumer.h
> index 6a4479616479..16e7682474f3 100644
> --- a/include/linux/iio/consumer.h
> +++ b/include/linux/iio/consumer.h
> @@ -451,4 +451,60 @@ ssize_t iio_write_channel_ext_info(struct iio_channel *chan, const char *attr,
> */
> ssize_t iio_read_channel_label(struct iio_channel *chan, char *buf);
>
> +/**
> + * iio_event_mode() - get file mode for an event property
Can we name this something more specific. Sounds like it might be
the mode of the events, not the mode of the file.
> + * @chan: Channel being queried
> + * @type: Event type (theshold, rate-of-change, etc.)
> + * @dir: Event direction (rising, falling, etc.)
> + * @info: Event property (enable, value, etc.)
> + *
> + * Determine an appropriate mode for sysfs files derived from this event.
This isn't precise unfortunately as if we have a mix of read only and rw
for different events (maybe some thresholds fixed and others not).
We should maybe say that it may indicate more control than actually possible.
In most cases it'll be right though.
> + *
> + * Return:
> + * - `0000` if the event is unsupported or otherwise unavailable
> + * - `0444` if the event is read-only
> + * - `0200` if the event is write-only
> + * - `0644` if the event is read-write
> + */
So here is one of those bits of "don't do it the way we currently do it".
Move the docs next to the implementation in the c file rather than the header.
We are horribly inconsistent in IIO mostly because of younger me making a mess
of it. General thinking today is that we are much less likely to forget
to update docs if they are next to the code.
> +umode_t iio_event_mode(struct iio_channel *chan, enum iio_event_type type,
> + enum iio_event_direction dir, enum iio_event_info info);
> +
> +/**
> + * iio_read_event_processed_scale() - Read an event property
> + * @chan: Channel being queried
> + * @type: Event type (theshold, rate-of-change, etc.)
> + * @dir: Event direction (rising, falling, etc.)
> + * @info: Event property (enable, value, etc.)
> + * @val: Processed property value
> + * @scale: Factor to scale @val by
> + *
> + * Read a processed (scaled and offset) event property of a given channel.
> + *
> + * Return: 0 on success, or negative error on failure
> + */
> +int iio_read_event_processed_scale(struct iio_channel *chan,
> + enum iio_event_type type,
> + enum iio_event_direction dir,
> + enum iio_event_info info, int *val,
> + unsigned int scale);
> +
> +/**
> + * iio_write_event_processed_scale() - Read an event property
> + * @chan: Channel being queried
> + * @type: Event type (theshold, rate-of-change, etc.)
> + * @dir: Event direction (rising, falling, etc.)
> + * @info: Event property (enable, value, etc.)
> + * @processed: Processed property value
> + * @scale: Factor to scale @processed by
> + *
> + * Write a processed (scaled and offset) event property of a given channel.
> + *
> + * Return: 0 on success, or negative error on failure
> + */
> +int iio_write_event_processed_scale(struct iio_channel *chan,
> + enum iio_event_type type,
> + enum iio_event_direction dir,
> + enum iio_event_info info, int processed,
> + unsigned int scale);
> +
> #endif
^ permalink raw reply [flat|nested] 62+ messages in thread
* Re: [PATCH 3/7] iio: Add in-kernel API for events
2025-07-15 16:52 ` Sean Anderson
@ 2025-07-27 16:21 ` Jonathan Cameron
2025-07-28 22:44 ` Sean Anderson
0 siblings, 1 reply; 62+ messages in thread
From: Jonathan Cameron @ 2025-07-27 16:21 UTC (permalink / raw)
To: Sean Anderson
Cc: Nuno Sá, Jean Delvare, Guenter Roeck, linux-iio, linux-hwmon,
Andy Shevchenko, Nuno Sá, linux-kernel, David Lechner
On Tue, 15 Jul 2025 12:52:19 -0400
Sean Anderson <sean.anderson@linux.dev> wrote:
> On 7/15/25 07:09, Nuno Sá wrote:
> > On Mon, 2025-07-14 at 21:20 -0400, Sean Anderson wrote:
> >> Add an API to notify consumers about events. Events still need to be
> >> enabled using the iio_read_event/iio_write_event functions. Of course,
> >> userspace can also manipulate the enabled events. I don't think this is
> >> too much of an issue, since userspace can also manipulate the event
> >> thresholds. But enabling events may cause existing programs to be
> >> surprised when they get something unexpected. Maybe we should set the
> >> interface as busy when there are any in-kernel listeners?
> >>
> >
> > Sensible question. I'm not that familiar with events but I suspect is not
> > trivial (if doable) to do a similar approach as with buffers? With buffers, an
> > inkernal consumer get's it's own buffer object (that goes into a list of active
> > buffers in the iio device) with all channels enabled and then we demux the
> > appropriate channels for each consumer.
>
> For in-kernel consumers I think it's reasonable to expect them to handle
> events they didn't explicitly enable. I'm not sure about userspace
> consumers.
This already happens because we don't have a demux equivalent (what we do
for buffered data flow) so if a device only has a single enable bit that covers
multiple events (annoyingly common for accelerometers for example) then
userspace will get events it didn't ask for. We 'could' fix that,
but it's never really been worth the effort.
Events tend to be low data rate so an occasionally extra is rather different
to having to have much larger data buffers to handle a range of channels you
never asked for.
Lets be careful to document this behaviour as 'may enable extra events'
as then if we decide later to do demux type stuff we won't be breaking ABI.
No one will mind getting fewer spurious events due to a core improvement.
>
> > Independent of the above, we can argue that having both inkernel and userspace
> > changing thresholds is ok (I mean, there's nothing stopping two userspace apps
> > doing that) but we should likely be careful with enabling/disabling. If multiple
> > consumers enable the same event, one of them disabling it should not disable it
> > for all the consumers, right?
>
> Right now the HWMON consumer never permanently disable events to avoid this
> issue. It does toggle the enable to determine if an alarm should stay
> enabled:
> ________
> condition __/ \________
> _____ ____ ___
> enable \__/ \__/
>
> event | |
> __ ____
> alarm __/ \__/ \_____
>
> read 1 1 0
>
> I suppose this could also be done by comparing the raw threshold to the
> channel.
I wonder if we should add the option to do a 'get_exclusive' or similar
to block the IIO user interfaces if something critical is using the device.
If we were for instance to use this to block the IOCTL to get the events
fd then any built in driver etc will almost certainly load before anyone
can call the ioctl so it will fairly cleanly block things.
Jonathan
^ permalink raw reply [flat|nested] 62+ messages in thread
* Re: [PATCH 3/7] iio: Add in-kernel API for events
2025-07-15 1:20 ` [PATCH 3/7] iio: Add in-kernel API for events Sean Anderson
2025-07-15 8:20 ` Andy Shevchenko
2025-07-15 11:09 ` Nuno Sá
@ 2025-07-27 16:24 ` Jonathan Cameron
2 siblings, 0 replies; 62+ messages in thread
From: Jonathan Cameron @ 2025-07-27 16:24 UTC (permalink / raw)
To: Sean Anderson
Cc: Jean Delvare, Guenter Roeck, linux-iio, linux-hwmon,
Andy Shevchenko, Nuno Sá, linux-kernel, David Lechner
On Mon, 14 Jul 2025 21:20:19 -0400
Sean Anderson <sean.anderson@linux.dev> wrote:
> Add an API to notify consumers about events. Events still need to be
> enabled using the iio_read_event/iio_write_event functions. Of course,
> userspace can also manipulate the enabled events. I don't think this is
> too much of an issue, since userspace can also manipulate the event
> thresholds. But enabling events may cause existing programs to be
> surprised when they get something unexpected. Maybe we should set the
> interface as busy when there are any in-kernel listeners?
I think we definitely want that to be an option.
>
> Signed-off-by: Sean Anderson <sean.anderson@linux.dev>
^ permalink raw reply [flat|nested] 62+ messages in thread
* Re: [PATCH 5/7] hwmon: iio: Add helper function for creating attributes
2025-07-15 1:20 ` [PATCH 5/7] hwmon: iio: Add helper function for creating attributes Sean Anderson
2025-07-15 8:38 ` Andy Shevchenko
@ 2025-07-27 16:31 ` Jonathan Cameron
1 sibling, 0 replies; 62+ messages in thread
From: Jonathan Cameron @ 2025-07-27 16:31 UTC (permalink / raw)
To: Sean Anderson
Cc: Jean Delvare, Guenter Roeck, linux-iio, linux-hwmon,
Andy Shevchenko, Nuno Sá, linux-kernel, David Lechner
On Mon, 14 Jul 2025 21:20:21 -0400
Sean Anderson <sean.anderson@linux.dev> wrote:
> Add a helper function to create attributes and initialize their fields.
> This reduces repetition when creating several attributes per channel.
>
> Signed-off-by: Sean Anderson <sean.anderson@linux.dev>
> ---
>
> drivers/hwmon/iio_hwmon.c | 78 +++++++++++++++++++++------------------
> 1 file changed, 42 insertions(+), 36 deletions(-)
>
> diff --git a/drivers/hwmon/iio_hwmon.c b/drivers/hwmon/iio_hwmon.c
> index bba8919377eb..7dc156d2aea4 100644
> --- a/drivers/hwmon/iio_hwmon.c
> +++ b/drivers/hwmon/iio_hwmon.c
> @@ -24,13 +24,15 @@
> * @attr_group: the group of attributes
> * @groups: null terminated array of attribute groups
> * @attrs: null terminated array of attribute pointers.
> + * @num_attrs: length of @attrs
> */
> struct iio_hwmon_state {
> struct iio_channel *channels;
> - int num_channels;
> struct attribute_group attr_group;
> const struct attribute_group *groups[2];
> struct attribute **attrs;
> + size_t num_attrs;
It's a little unfortunate that we need to keep the state around when it
is only relevant in probe.
> + int num_channels;
> };
>
> static ssize_t iio_hwmon_read_label(struct device *dev,
> @@ -93,12 +95,39 @@ static ssize_t iio_hwmon_read_val(struct device *dev,
> return sprintf(buf, "%d\n", result);
> }
>
> +static int add_device_attr(struct device *dev, struct iio_hwmon_state *st,
That's a very generic name. I'd prefix it to avoid potential namespace
issues in future.
> + ssize_t (*show)(struct device *dev,
> + struct device_attribute *attr,
> + char *buf),
> + int i, const char *fmt, ...)
> +{
> + struct sensor_device_attribute *a;
> + va_list ap;
> +
> + a = devm_kzalloc(dev, sizeof(*a), GFP_KERNEL);
> + if (!a)
> + return -ENOMEM;
> +
> + sysfs_attr_init(&a->dev_attr.attr);
> + va_start(ap, fmt);
> + a->dev_attr.attr.name = devm_kvasprintf(dev, GFP_KERNEL, fmt, ap);
> + va_end(ap);
> + if (!a->dev_attr.attr.name)
> + return -ENOMEM;
> +
> + a->dev_attr.show = show;
> + a->dev_attr.attr.mode = 0444;
> + a->index = i;
> +
> + st->attrs[st->num_attrs++] = &a->dev_attr.attr
I wonder if we should break this up into
static attribute *iio_hwmon_alloc_attr();
and setting st->attrs in the caller as then we can make num_attrs a local
variable.
> + return 0;
> +}
> +
> static int iio_hwmon_probe(struct platform_device *pdev)
> {
> struct device *dev = &pdev->dev;
> struct iio_hwmon_state *st;
> - struct sensor_device_attribute *a;
> - int ret, i, attr = 0;
> + int ret, i;
> int in_i = 1, temp_i = 1, curr_i = 1, humidity_i = 1, power_i = 1;
> enum iio_chan_type type;
> struct iio_channel *channels;
> @@ -136,11 +165,6 @@ static int iio_hwmon_probe(struct platform_device *pdev)
> const char *prefix;
> int n;
>
> - a = devm_kzalloc(dev, sizeof(*a), GFP_KERNEL);
> - if (a == NULL)
> - return -ENOMEM;
> -
> - sysfs_attr_init(&a->dev_attr.attr);
> ret = iio_get_channel_type(&st->channels[i], &type);
> if (ret < 0)
> return ret;
> @@ -170,36 +194,18 @@ static int iio_hwmon_probe(struct platform_device *pdev)
> return -EINVAL;
> }
>
> - a->dev_attr.attr.name = devm_kasprintf(dev, GFP_KERNEL,
> - "%s%d_input",
> - prefix, n);
> - if (a->dev_attr.attr.name == NULL)
> - return -ENOMEM;
> -
> - a->dev_attr.show = iio_hwmon_read_val;
> - a->dev_attr.attr.mode = 0444;
> - a->index = i;
> - st->attrs[attr++] = &a->dev_attr.attr;
> + ret = add_device_attr(dev, st, iio_hwmon_read_val, i,
> + "%s%d_input", prefix, n);
> + if (ret)
> + return ret;
>
> /* Let's see if we have a label... */
> - if (iio_read_channel_label(&st->channels[i], buf) < 0)
> - continue;
> -
> - a = devm_kzalloc(dev, sizeof(*a), GFP_KERNEL);
> - if (a == NULL)
> - return -ENOMEM;
> -
> - sysfs_attr_init(&a->dev_attr.attr);
> - a->dev_attr.attr.name = devm_kasprintf(dev, GFP_KERNEL,
> - "%s%d_label",
> - prefix, n);
> - if (!a->dev_attr.attr.name)
> - return -ENOMEM;
> -
> - a->dev_attr.show = iio_hwmon_read_label;
> - a->dev_attr.attr.mode = 0444;
> - a->index = i;
> - st->attrs[attr++] = &a->dev_attr.attr;
> + if (iio_read_channel_label(&st->channels[i], buf) >= 0) {
> + ret = add_device_attr(dev, st, iio_hwmon_read_label,
> + i, "%s%d_label", prefix, n);
> + if (ret)
> + return ret;
> + }
> }
>
> devm_free_pages(dev, (unsigned long)buf);
^ permalink raw reply [flat|nested] 62+ messages in thread
* Re: [PATCH 6/7] hwmon: iio: Add min/max support
2025-07-15 1:20 ` [PATCH 6/7] hwmon: iio: Add min/max support Sean Anderson
2025-07-15 8:41 ` Andy Shevchenko
@ 2025-07-27 16:35 ` Jonathan Cameron
2025-07-28 22:32 ` Sean Anderson
1 sibling, 1 reply; 62+ messages in thread
From: Jonathan Cameron @ 2025-07-27 16:35 UTC (permalink / raw)
To: Sean Anderson
Cc: Jean Delvare, Guenter Roeck, linux-iio, linux-hwmon,
Andy Shevchenko, Nuno Sá, linux-kernel, David Lechner
On Mon, 14 Jul 2025 21:20:22 -0400
Sean Anderson <sean.anderson@linux.dev> wrote:
> Add support for minimum/maximum attributes. Like the _input attribute,
> we just need to call into the IIO API.
>
> Signed-off-by: Sean Anderson <sean.anderson@linux.dev>
Similar comments to previous. I'm not keen on the blend of allocation of
attributes and registration. If we can break that link I think it will give
cleaner code.
> static int add_device_attr(struct device *dev, struct iio_hwmon_state *st,
> ssize_t (*show)(struct device *dev,
> struct device_attribute *attr,
> @@ -123,6 +171,40 @@ static int add_device_attr(struct device *dev, struct iio_hwmon_state *st,
> return 0;
> }
>
> +static int add_event_attr(struct device *dev, struct iio_hwmon_state *st,
> + int i, enum iio_event_direction dir,
> + const char *fmt, ...)
> +{
> + struct sensor_device_attribute_2 *a;
> + umode_t mode;
> + va_list ap;
> +
> + mode = iio_event_mode(&st->channels[i], IIO_EV_TYPE_THRESH, dir,
> + IIO_EV_INFO_VALUE);
> + if (!mode)
> + return 0;
> +
> + a = devm_kzalloc(dev, sizeof(*a), GFP_KERNEL);
> + if (!a)
> + return -ENOMEM;
> +
> + sysfs_attr_init(&a->dev_attr.attr);
> + va_start(ap, fmt);
> + a->dev_attr.attr.name = devm_kvasprintf(dev, GFP_KERNEL, fmt, ap);
> + va_end(ap);
> + if (!a->dev_attr.attr.name)
> + return -ENOMEM;
> +
> + a->dev_attr.show = iio_hwmon_read_event;
> + a->dev_attr.store = iio_hwmon_write_event;
> + a->dev_attr.attr.mode = mode;
> + a->index = i;
> + a->nr = dir;
> +
> + st->attrs[st->num_attrs++] = &a->dev_attr.attr;
similar comment to the previous, though here I think we'd
need to pass in the channel to an iio_hwmon_alloc_event_attr() as ideally we'd
not be messing with st at all in here. So maybe it doesn't work out.
> + return 0;
> +}
^ permalink raw reply [flat|nested] 62+ messages in thread
* Re: [PATCH 7/7] hwmon: iio: Add alarm support
2025-07-15 1:20 ` [PATCH 7/7] hwmon: iio: Add alarm support Sean Anderson
` (4 preceding siblings ...)
2025-07-16 7:44 ` kernel test robot
@ 2025-07-27 16:50 ` Jonathan Cameron
5 siblings, 0 replies; 62+ messages in thread
From: Jonathan Cameron @ 2025-07-27 16:50 UTC (permalink / raw)
To: Sean Anderson
Cc: Jean Delvare, Guenter Roeck, linux-iio, linux-hwmon,
Andy Shevchenko, Nuno Sá, linux-kernel, David Lechner
On Mon, 14 Jul 2025 21:20:23 -0400
Sean Anderson <sean.anderson@linux.dev> wrote:
> Add alarm support based on IIO threshold events. The alarm is cleared on
> read, but will be set again if the condition is still present. This is
> detected by disabling and re-enabling the event. The same trick is done
> when creating the attribute to detect already-triggered events.
>
> The alarms are updated by an event listener. To keep the notifier call
> chain short, we create one listener per iio device, shared across all
> hwmon devices.
>
> To avoid dynamic creation of alarms, alarms for all possible events are
> allocated at creation. Lookup is done by a linear scan, as I expect
> events to occur rarely. If performance becomes an issue, a binary search
> could be done instead (or some kind of hash lookup).
>
> Signed-off-by: Sean Anderson <sean.anderson@linux.dev>
A few minor comments inline.
Thanks,
Jonathan
> + * iio_hwmon_listener_get() - Get a listener for an IIO device
> + * @indio_dev: IIO device to listen to
> + *
> + * Look up or create a new listener for @indio_dev. The returned listener is
> + * registered with @indio_dev, but events still need to be manually enabled.
> + * You must call iio_hwmon_listener_put() when you are done.
> + *
> + * Return: Listener for @indio_dev, or an error pointer
> + */
> +static struct iio_hwmon_listener *iio_hwmon_listener_get(struct iio_dev *indio_dev)
> +{
> + struct iio_hwmon_listener *listener;
> + int err = -ENOMEM;
> + size_t i, j;
> +
> + guard(mutex)(&iio_hwmon_listener_lock);
Guard + unlock. However, in general combining cleanup.h stuff and
gotos is a bad idea. You might better off wrapping a function
with the lock in the outer function
Alternative, use local variables for ids and alarms. Use __free()
on those + return_ptr(listener) and no_free_ptr() as you assign
the elements of listener after there is no possibility of error.
> + list_for_each_entry(listener, &iio_hwmon_listeners, list) {
> + if (listener->indio_dev == indio_dev) {
Deep nest. I'd do
if (listener->indio_dev != indio-dev)
continue;
if (likely(listener->refcnt != UINT_MAX)
//Needs a comment on why this make sense as opposed to returning an error.
listener->refcnt++;
return listener;
> + if (likely(listener->refcnt != UINT_MAX))
> + listener->refcnt++;
> + return listener;
> + }
> + }
> +
> + listener = kzalloc(sizeof(*listener), GFP_KERNEL);
> + if (!listener)
> + goto err_unlock;
> +
> + listener->refcnt = 1;
> + listener->indio_dev = indio_dev;
> + listener->block.notifier_call = iio_hwmon_listener_callback;
> + for (i = 0; i < indio_dev->num_channels; i++)
> + listener->num_alarms += indio_dev->channels[i].num_event_specs;
> +
> + listener->ids = kcalloc(listener->num_alarms, sizeof(*listener->ids),
> + GFP_KERNEL);
> + listener->alarms = bitmap_zalloc(listener->num_alarms, GFP_KERNEL);
> + if (!listener->ids || !listener->alarms)
> + goto err_listener;
I'd prefer this split into a pair of checks and separate labels.
> +
> + i = 0;
> + for (j = 0; j < indio_dev->num_channels; j++) {
> + struct iio_chan_spec const *chan = &indio_dev->channels[j];
> + size_t k;
> +
> + for (k = 0; k < chan->num_event_specs; k++)
> + listener->ids[i++] =
> + iio_event_id(chan, chan->event_spec[k].type,
> + chan->event_spec[k].dir);
> + }
> +
> + err = iio_event_register(indio_dev, &listener->block);
> + if (err)
> + goto err_alarms;
> +
> + list_add(&listener->list, &iio_hwmon_listeners);
> + mutex_unlock(&iio_hwmon_listener_lock);
> + return listener;
> +
> +err_alarms:
> + kfree(listener->alarms);
> + kfree(listener->ids);
> +err_listener:
> + kfree(listener);
> +err_unlock:
> + mutex_unlock(&iio_hwmon_listener_lock);
> + return ERR_PTR(err);
> +}
> +
> +/**
> + * iio_hwmon_listener_put() - Release a listener
Maybe use a kref and kref_put() with a suitable release to
do the cleanup.
> + * @data: &struct iio_hwmon_listener to release
> + *
> + * For convenience, @data is void.
> + */
> +static void iio_hwmon_listener_put(void *data)
> +{
> + struct iio_hwmon_listener *listener = data;
> +
> + scoped_guard(mutex, &iio_hwmon_listener_lock) {
> + if (unlikely(listener->refcnt == UINT_MAX))
> + return;
> +
> + if (--listener->refcnt)
> + return;
> +
> + list_del(&listener->list);
> + iio_event_unregister(listener->indio_dev, &listener->block);
> + }
> +
> + kfree(listener->alarms);
> + kfree(listener->ids);
> + kfree(listener);
> +}
>
^ permalink raw reply [flat|nested] 62+ messages in thread
* Re: [PATCH 6/7] hwmon: iio: Add min/max support
2025-07-27 16:35 ` Jonathan Cameron
@ 2025-07-28 22:32 ` Sean Anderson
2025-07-29 18:37 ` Jonathan Cameron
0 siblings, 1 reply; 62+ messages in thread
From: Sean Anderson @ 2025-07-28 22:32 UTC (permalink / raw)
To: Jonathan Cameron
Cc: Jean Delvare, Guenter Roeck, linux-iio, linux-hwmon,
Andy Shevchenko, Nuno Sá, linux-kernel, David Lechner
On 7/27/25 12:35, Jonathan Cameron wrote:
> On Mon, 14 Jul 2025 21:20:22 -0400
> Sean Anderson <sean.anderson@linux.dev> wrote:
>
>> Add support for minimum/maximum attributes. Like the _input attribute,
>> we just need to call into the IIO API.
>>
>> Signed-off-by: Sean Anderson <sean.anderson@linux.dev>
>
> Similar comments to previous. I'm not keen on the blend of allocation of
> attributes and registration. If we can break that link I think it will give
> cleaner code.
>
>> static int add_device_attr(struct device *dev, struct iio_hwmon_state *st,
>> ssize_t (*show)(struct device *dev,
>> struct device_attribute *attr,
>> @@ -123,6 +171,40 @@ static int add_device_attr(struct device *dev, struct iio_hwmon_state *st,
>> return 0;
>> }
>>
>> +static int add_event_attr(struct device *dev, struct iio_hwmon_state *st,
>> + int i, enum iio_event_direction dir,
>> + const char *fmt, ...)
>> +{
>> + struct sensor_device_attribute_2 *a;
>> + umode_t mode;
>> + va_list ap;
>> +
>> + mode = iio_event_mode(&st->channels[i], IIO_EV_TYPE_THRESH, dir,
>> + IIO_EV_INFO_VALUE);
>> + if (!mode)
>> + return 0;
>> +
>> + a = devm_kzalloc(dev, sizeof(*a), GFP_KERNEL);
>> + if (!a)
>> + return -ENOMEM;
>> +
>> + sysfs_attr_init(&a->dev_attr.attr);
>> + va_start(ap, fmt);
>> + a->dev_attr.attr.name = devm_kvasprintf(dev, GFP_KERNEL, fmt, ap);
>> + va_end(ap);
>> + if (!a->dev_attr.attr.name)
>> + return -ENOMEM;
>> +
>> + a->dev_attr.show = iio_hwmon_read_event;
>> + a->dev_attr.store = iio_hwmon_write_event;
>> + a->dev_attr.attr.mode = mode;
>> + a->index = i;
>> + a->nr = dir;
>> +
>> + st->attrs[st->num_attrs++] = &a->dev_attr.attr;
> similar comment to the previous, though here I think we'd
> need to pass in the channel to an iio_hwmon_alloc_event_attr() as ideally we'd
> not be messing with st at all in here. So maybe it doesn't work out.
Well, I used to have
+ if (iio_read_channel_label(&st->channels[i], buf) >= 0) {
+ st->attrs[attr] = create_attr(dev, iio_hwmon_read_label,
+ NULL, 0444, i, 0, 0, 0,
+ "%s%d_label", prefix, n);
+ if (!st->attrs[attr++])
+ return -ENOMEM;
+ }
but even with a shorter function name, all the parameters are starting
to get bunched up on the right side. And if we make it longer as you
propose it starts looking like
+ if (iio_read_channel_label(&st->channels[i], buf) >= 0) {
+ st->attrs[attr] =
+ iio_hwmon_create_device_attr(dev,
+ iio_hwmon_read_label,
+ NULL, 0444, i, 0, 0,
+ 0, "%s%d_label",
+ prefix, n);
+ if (!st->attrs[attr++])
+ return -ENOMEM;
+ }
which is IMO really terrible-looking.
Maybe we should just stick everything in an xarray and linearize it at
the end of probe...
--Sean
^ permalink raw reply [flat|nested] 62+ messages in thread
* Re: [PATCH 3/7] iio: Add in-kernel API for events
2025-07-27 16:21 ` Jonathan Cameron
@ 2025-07-28 22:44 ` Sean Anderson
2025-07-29 18:33 ` Jonathan Cameron
0 siblings, 1 reply; 62+ messages in thread
From: Sean Anderson @ 2025-07-28 22:44 UTC (permalink / raw)
To: Jonathan Cameron
Cc: Nuno Sá, Jean Delvare, Guenter Roeck, linux-iio, linux-hwmon,
Andy Shevchenko, Nuno Sá, linux-kernel, David Lechner
On 7/27/25 12:21, Jonathan Cameron wrote:
> On Tue, 15 Jul 2025 12:52:19 -0400
> Sean Anderson <sean.anderson@linux.dev> wrote:
>
>> On 7/15/25 07:09, Nuno Sá wrote:
>> > On Mon, 2025-07-14 at 21:20 -0400, Sean Anderson wrote:
>> >> Add an API to notify consumers about events. Events still need to be
>> >> enabled using the iio_read_event/iio_write_event functions. Of course,
>> >> userspace can also manipulate the enabled events. I don't think this is
>> >> too much of an issue, since userspace can also manipulate the event
>> >> thresholds. But enabling events may cause existing programs to be
>> >> surprised when they get something unexpected. Maybe we should set the
>> >> interface as busy when there are any in-kernel listeners?
>> >>
>> >
>> > Sensible question. I'm not that familiar with events but I suspect is not
>> > trivial (if doable) to do a similar approach as with buffers? With buffers, an
>> > inkernal consumer get's it's own buffer object (that goes into a list of active
>> > buffers in the iio device) with all channels enabled and then we demux the
>> > appropriate channels for each consumer.
>>
>> For in-kernel consumers I think it's reasonable to expect them to handle
>> events they didn't explicitly enable. I'm not sure about userspace
>> consumers.
>
> This already happens because we don't have a demux equivalent (what we do
> for buffered data flow) so if a device only has a single enable bit that covers
> multiple events (annoyingly common for accelerometers for example) then
> userspace will get events it didn't ask for. We 'could' fix that,
> but it's never really been worth the effort.
>
> Events tend to be low data rate so an occasionally extra is rather different
> to having to have much larger data buffers to handle a range of channels you
> never asked for.
>
> Lets be careful to document this behaviour as 'may enable extra events'
> as then if we decide later to do demux type stuff we won't be breaking ABI.
> No one will mind getting fewer spurious events due to a core improvement.
Where would this get documented?
>>
>> > Independent of the above, we can argue that having both inkernel and userspace
>> > changing thresholds is ok (I mean, there's nothing stopping two userspace apps
>> > doing that) but we should likely be careful with enabling/disabling. If multiple
>> > consumers enable the same event, one of them disabling it should not disable it
>> > for all the consumers, right?
>>
>> Right now the HWMON consumer never permanently disable events to avoid this
>> issue. It does toggle the enable to determine if an alarm should stay
>> enabled:
>> ________
>> condition __/ \________
>> _____ ____ ___
>> enable \__/ \__/
>>
>> event | |
>> __ ____
>> alarm __/ \__/ \_____
>>
>> read 1 1 0
>>
>> I suppose this could also be done by comparing the raw threshold to the
>> channel.
>
> I wonder if we should add the option to do a 'get_exclusive' or similar
> to block the IIO user interfaces if something critical is using the device.
>
> If we were for instance to use this to block the IOCTL to get the events
> fd then any built in driver etc will almost certainly load before anyone
> can call the ioctl so it will fairly cleanly block things.
This is how it currently works for userspace. Only one process can create
the event fd, and everyone else gets -EBUSY.
Of course, it would be pretty surprising to have an IIO device where
some channels were used by userspace and others were used by hwmon and
then have your daemon stop working after you update your kernel because
now the hwmon driver takes exclusive event access.
I originally had kernel users read from the kfifo just like userspace,
but I was concerned about the above scenario.
--Sean
^ permalink raw reply [flat|nested] 62+ messages in thread
* Re: [PATCH 3/7] iio: Add in-kernel API for events
2025-07-28 22:44 ` Sean Anderson
@ 2025-07-29 18:33 ` Jonathan Cameron
2025-07-29 20:09 ` Sean Anderson
0 siblings, 1 reply; 62+ messages in thread
From: Jonathan Cameron @ 2025-07-29 18:33 UTC (permalink / raw)
To: Sean Anderson
Cc: Nuno Sá, Jean Delvare, Guenter Roeck, linux-iio, linux-hwmon,
Andy Shevchenko, Nuno Sá, linux-kernel, David Lechner
On Mon, 28 Jul 2025 18:44:30 -0400
Sean Anderson <sean.anderson@linux.dev> wrote:
> On 7/27/25 12:21, Jonathan Cameron wrote:
> > On Tue, 15 Jul 2025 12:52:19 -0400
> > Sean Anderson <sean.anderson@linux.dev> wrote:
> >
> >> On 7/15/25 07:09, Nuno Sá wrote:
> >> > On Mon, 2025-07-14 at 21:20 -0400, Sean Anderson wrote:
> >> >> Add an API to notify consumers about events. Events still need to be
> >> >> enabled using the iio_read_event/iio_write_event functions. Of course,
> >> >> userspace can also manipulate the enabled events. I don't think this is
> >> >> too much of an issue, since userspace can also manipulate the event
> >> >> thresholds. But enabling events may cause existing programs to be
> >> >> surprised when they get something unexpected. Maybe we should set the
> >> >> interface as busy when there are any in-kernel listeners?
> >> >>
> >> >
> >> > Sensible question. I'm not that familiar with events but I suspect is not
> >> > trivial (if doable) to do a similar approach as with buffers? With buffers, an
> >> > inkernal consumer get's it's own buffer object (that goes into a list of active
> >> > buffers in the iio device) with all channels enabled and then we demux the
> >> > appropriate channels for each consumer.
> >>
> >> For in-kernel consumers I think it's reasonable to expect them to handle
> >> events they didn't explicitly enable. I'm not sure about userspace
> >> consumers.
> >
> > This already happens because we don't have a demux equivalent (what we do
> > for buffered data flow) so if a device only has a single enable bit that covers
> > multiple events (annoyingly common for accelerometers for example) then
> > userspace will get events it didn't ask for. We 'could' fix that,
> > but it's never really been worth the effort.
> >
> > Events tend to be low data rate so an occasionally extra is rather different
> > to having to have much larger data buffers to handle a range of channels you
> > never asked for.
> >
> > Lets be careful to document this behaviour as 'may enable extra events'
> > as then if we decide later to do demux type stuff we won't be breaking ABI.
> > No one will mind getting fewer spurious events due to a core improvement.
>
> Where would this get documented?
Starting point will be in the docs for the ABI that asks for any events at all.
Also useful to add some thing to Documentation/IIO though there are lots of
other things those docs don't yet cover :(
>
> >>
> >> > Independent of the above, we can argue that having both inkernel and userspace
> >> > changing thresholds is ok (I mean, there's nothing stopping two userspace apps
> >> > doing that) but we should likely be careful with enabling/disabling. If multiple
> >> > consumers enable the same event, one of them disabling it should not disable it
> >> > for all the consumers, right?
> >>
> >> Right now the HWMON consumer never permanently disable events to avoid this
> >> issue. It does toggle the enable to determine if an alarm should stay
> >> enabled:
> >> ________
> >> condition __/ \________
> >> _____ ____ ___
> >> enable \__/ \__/
> >>
> >> event | |
> >> __ ____
> >> alarm __/ \__/ \_____
> >>
> >> read 1 1 0
> >>
> >> I suppose this could also be done by comparing the raw threshold to the
> >> channel.
> >
> > I wonder if we should add the option to do a 'get_exclusive' or similar
> > to block the IIO user interfaces if something critical is using the device.
> >
> > If we were for instance to use this to block the IOCTL to get the events
> > fd then any built in driver etc will almost certainly load before anyone
> > can call the ioctl so it will fairly cleanly block things.
>
> This is how it currently works for userspace. Only one process can create
> the event fd, and everyone else gets -EBUSY.
>
> Of course, it would be pretty surprising to have an IIO device where
> some channels were used by userspace and others were used by hwmon and
> then have your daemon stop working after you update your kernel because
> now the hwmon driver takes exclusive event access.
True. I wonder how many boards we don't know about are using the iio-hwmon
bridge. We can check the ones in kernel for whether they grab all the
channels (which would rule this out).
Another things we could do is have an opt in from the IIO driver.
That way only 'new' drivers would have this behaviour. Not nice though.
>
> I originally had kernel users read from the kfifo just like userspace,
> but I was concerned about the above scenario.
>
yeah, always a problem to retrofit policy.
> --Sean
>
^ permalink raw reply [flat|nested] 62+ messages in thread
* Re: [PATCH 6/7] hwmon: iio: Add min/max support
2025-07-28 22:32 ` Sean Anderson
@ 2025-07-29 18:37 ` Jonathan Cameron
0 siblings, 0 replies; 62+ messages in thread
From: Jonathan Cameron @ 2025-07-29 18:37 UTC (permalink / raw)
To: Sean Anderson
Cc: Jean Delvare, Guenter Roeck, linux-iio, linux-hwmon,
Andy Shevchenko, Nuno Sá, linux-kernel, David Lechner
On Mon, 28 Jul 2025 18:32:43 -0400
Sean Anderson <sean.anderson@linux.dev> wrote:
> On 7/27/25 12:35, Jonathan Cameron wrote:
> > On Mon, 14 Jul 2025 21:20:22 -0400
> > Sean Anderson <sean.anderson@linux.dev> wrote:
> >
> >> Add support for minimum/maximum attributes. Like the _input attribute,
> >> we just need to call into the IIO API.
> >>
> >> Signed-off-by: Sean Anderson <sean.anderson@linux.dev>
> >
> > Similar comments to previous. I'm not keen on the blend of allocation of
> > attributes and registration. If we can break that link I think it will give
> > cleaner code.
> >
> >> static int add_device_attr(struct device *dev, struct iio_hwmon_state *st,
> >> ssize_t (*show)(struct device *dev,
> >> struct device_attribute *attr,
> >> @@ -123,6 +171,40 @@ static int add_device_attr(struct device *dev, struct iio_hwmon_state *st,
> >> return 0;
> >> }
> >>
> >> +static int add_event_attr(struct device *dev, struct iio_hwmon_state *st,
> >> + int i, enum iio_event_direction dir,
> >> + const char *fmt, ...)
> >> +{
> >> + struct sensor_device_attribute_2 *a;
> >> + umode_t mode;
> >> + va_list ap;
> >> +
> >> + mode = iio_event_mode(&st->channels[i], IIO_EV_TYPE_THRESH, dir,
> >> + IIO_EV_INFO_VALUE);
> >> + if (!mode)
> >> + return 0;
> >> +
> >> + a = devm_kzalloc(dev, sizeof(*a), GFP_KERNEL);
> >> + if (!a)
> >> + return -ENOMEM;
> >> +
> >> + sysfs_attr_init(&a->dev_attr.attr);
> >> + va_start(ap, fmt);
> >> + a->dev_attr.attr.name = devm_kvasprintf(dev, GFP_KERNEL, fmt, ap);
> >> + va_end(ap);
> >> + if (!a->dev_attr.attr.name)
> >> + return -ENOMEM;
> >> +
> >> + a->dev_attr.show = iio_hwmon_read_event;
> >> + a->dev_attr.store = iio_hwmon_write_event;
> >> + a->dev_attr.attr.mode = mode;
> >> + a->index = i;
> >> + a->nr = dir;
> >> +
> >> + st->attrs[st->num_attrs++] = &a->dev_attr.attr;
> > similar comment to the previous, though here I think we'd
> > need to pass in the channel to an iio_hwmon_alloc_event_attr() as ideally we'd
> > not be messing with st at all in here. So maybe it doesn't work out.
>
> Well, I used to have
>
> + if (iio_read_channel_label(&st->channels[i], buf) >= 0) {
> + st->attrs[attr] = create_attr(dev, iio_hwmon_read_label,
> + NULL, 0444, i, 0, 0, 0,
> + "%s%d_label", prefix, n);
> + if (!st->attrs[attr++])
pushing attr off the end is not a good idea even if we know we don't use it
any more.
> + return -ENOMEM;
> + }
>
> but even with a shorter function name, all the parameters are starting
> to get bunched up on the right side. And if we make it longer as you
> propose it starts looking like
Using a local variable
struct attribute *att;
att = create_attr(dev, iio_hwmon_read_lanel,
...
if (!att)
return -ENOMEM;
st->attrs[attr++] = att;
helps but still ugly.
>
>
> + if (iio_read_channel_label(&st->channels[i], buf) >= 0) {
> + st->attrs[attr] =
> + iio_hwmon_create_device_attr(dev,
> + iio_hwmon_read_label,
> + NULL, 0444, i, 0, 0,
> + 0, "%s%d_label",
> + prefix, n);
> + if (!st->attrs[attr++])
> + return -ENOMEM;
> + }
>
> which is IMO really terrible-looking.
Fair enough. let's leave it as is.
>
> Maybe we should just stick everything in an xarray and linearize it at
> the end of probe...
If it looks nicer - feel free!
J
>
> --Sean
^ permalink raw reply [flat|nested] 62+ messages in thread
* Re: [PATCH 3/7] iio: Add in-kernel API for events
2025-07-29 18:33 ` Jonathan Cameron
@ 2025-07-29 20:09 ` Sean Anderson
2025-07-31 12:59 ` Jonathan Cameron
0 siblings, 1 reply; 62+ messages in thread
From: Sean Anderson @ 2025-07-29 20:09 UTC (permalink / raw)
To: Jonathan Cameron
Cc: Nuno Sá, Jean Delvare, Guenter Roeck, linux-iio, linux-hwmon,
Andy Shevchenko, Nuno Sá, linux-kernel, David Lechner
On 7/29/25 14:33, Jonathan Cameron wrote:
> On Mon, 28 Jul 2025 18:44:30 -0400
> Sean Anderson <sean.anderson@linux.dev> wrote:
>
>> On 7/27/25 12:21, Jonathan Cameron wrote:
>> > On Tue, 15 Jul 2025 12:52:19 -0400
>> > Sean Anderson <sean.anderson@linux.dev> wrote:
>> >
>> >> On 7/15/25 07:09, Nuno Sá wrote:
>> >> > On Mon, 2025-07-14 at 21:20 -0400, Sean Anderson wrote:
>> >> >> Add an API to notify consumers about events. Events still need to be
>> >> >> enabled using the iio_read_event/iio_write_event functions. Of course,
>> >> >> userspace can also manipulate the enabled events. I don't think this is
>> >> >> too much of an issue, since userspace can also manipulate the event
>> >> >> thresholds. But enabling events may cause existing programs to be
>> >> >> surprised when they get something unexpected. Maybe we should set the
>> >> >> interface as busy when there are any in-kernel listeners?
>> >> >>
>> >> >
>> >> > Sensible question. I'm not that familiar with events but I suspect is not
>> >> > trivial (if doable) to do a similar approach as with buffers? With buffers, an
>> >> > inkernal consumer get's it's own buffer object (that goes into a list of active
>> >> > buffers in the iio device) with all channels enabled and then we demux the
>> >> > appropriate channels for each consumer.
>> >>
>> >> For in-kernel consumers I think it's reasonable to expect them to handle
>> >> events they didn't explicitly enable. I'm not sure about userspace
>> >> consumers.
>> >
>> > This already happens because we don't have a demux equivalent (what we do
>> > for buffered data flow) so if a device only has a single enable bit that covers
>> > multiple events (annoyingly common for accelerometers for example) then
>> > userspace will get events it didn't ask for. We 'could' fix that,
>> > but it's never really been worth the effort.
>> >
>> > Events tend to be low data rate so an occasionally extra is rather different
>> > to having to have much larger data buffers to handle a range of channels you
>> > never asked for.
>> >
>> > Lets be careful to document this behaviour as 'may enable extra events'
>> > as then if we decide later to do demux type stuff we won't be breaking ABI.
>> > No one will mind getting fewer spurious events due to a core improvement.
>>
>> Where would this get documented?
>
> Starting point will be in the docs for the ABI that asks for any events at all.
>
> Also useful to add some thing to Documentation/IIO though there are lots of
> other things those docs don't yet cover :(
Notably the whole events API :l
>>
>> >>
>> >> > Independent of the above, we can argue that having both inkernel and userspace
>> >> > changing thresholds is ok (I mean, there's nothing stopping two userspace apps
>> >> > doing that) but we should likely be careful with enabling/disabling. If multiple
>> >> > consumers enable the same event, one of them disabling it should not disable it
>> >> > for all the consumers, right?
>> >>
>> >> Right now the HWMON consumer never permanently disable events to avoid this
>> >> issue. It does toggle the enable to determine if an alarm should stay
>> >> enabled:
>> >> ________
>> >> condition __/ \________
>> >> _____ ____ ___
>> >> enable \__/ \__/
>> >>
>> >> event | |
>> >> __ ____
>> >> alarm __/ \__/ \_____
>> >>
>> >> read 1 1 0
>> >>
>> >> I suppose this could also be done by comparing the raw threshold to the
>> >> channel.
>> >
>> > I wonder if we should add the option to do a 'get_exclusive' or similar
>> > to block the IIO user interfaces if something critical is using the device.
>> >
>> > If we were for instance to use this to block the IOCTL to get the events
>> > fd then any built in driver etc will almost certainly load before anyone
>> > can call the ioctl so it will fairly cleanly block things.
>>
>> This is how it currently works for userspace. Only one process can create
>> the event fd, and everyone else gets -EBUSY.
>>
>> Of course, it would be pretty surprising to have an IIO device where
>> some channels were used by userspace and others were used by hwmon and
>> then have your daemon stop working after you update your kernel because
>> now the hwmon driver takes exclusive event access.
>
> True. I wonder how many boards we don't know about are using the iio-hwmon
> bridge. We can check the ones in kernel for whether they grab all the
> channels (which would rule this out).
>
> Another things we could do is have an opt in from the IIO driver.
> That way only 'new' drivers would have this behaviour. Not nice though.
I would really like for this to "just work" if at all possible, so an
opt-out would be preferable. Maybe a hwmon module parameter.
But I think we can do better:
- Both kernel/userspace can/should handle unexpected events
- This includes extra (synthetic) events.
- Both kernel/userspace mostly just want to enable events
- Disabling events is not as important because of the previous bullet.
- But losing events is probably bad so we want to ensure we trigger
events at the same places they would have been triggered before.
So maybe we have an implementation where
- Enabling an event disables the backing event before re-enabling it if
there are any existing users
- Disabling an event only disables the backing event if all users are
gone
It could look something like
iio_sysfs_event_set(event, val):
if val:
if !event.user_enable
disable(event)
enable(event)
else if !event.kernel_enables
disable(event)
event.user_enable = val
iio_inkern_event_set(event, val):
if val:
if event.kernel_enables++ || event.user_enable
disable(event)
enable(event)
else if !--event.kernel_enables && !event.user_enable:
disable(event)
--Sean
>>
>> I originally had kernel users read from the kfifo just like userspace,
>> but I was concerned about the above scenario.
>>
>
> yeah, always a problem to retrofit policy.
>
>> --Sean
>>
>
^ permalink raw reply [flat|nested] 62+ messages in thread
* Re: [PATCH 7/7] hwmon: iio: Add alarm support
2025-07-17 16:00 ` Sean Anderson
@ 2025-07-31 10:52 ` Nuno Sá
2025-08-02 10:53 ` Jonathan Cameron
0 siblings, 1 reply; 62+ messages in thread
From: Nuno Sá @ 2025-07-31 10:52 UTC (permalink / raw)
To: Sean Anderson
Cc: Jonathan Cameron, Jean Delvare, Guenter Roeck, linux-iio,
linux-hwmon, Andy Shevchenko, Nuno Sá, linux-kernel,
David Lechner
On Thu, Jul 17, 2025 at 12:00:13PM -0400, Sean Anderson wrote:
> On 7/16/25 02:37, Nuno Sá wrote:
> > On Tue, 2025-07-15 at 13:02 -0400, Sean Anderson wrote:
> >> On 7/15/25 07:28, Nuno Sá wrote:
> >> > On Mon, 2025-07-14 at 21:20 -0400, Sean Anderson wrote:
> >> > > Add alarm support based on IIO threshold events. The alarm is cleared on
> >> > > read, but will be set again if the condition is still present. This is
> >> > > detected by disabling and re-enabling the event. The same trick is done
> >> > > when creating the attribute to detect already-triggered events.
> >> > >
> >> > > The alarms are updated by an event listener. To keep the notifier call
> >> > > chain short, we create one listener per iio device, shared across all
> >> > > hwmon devices.
> >> > >
> >> > > To avoid dynamic creation of alarms, alarms for all possible events are
> >> > > allocated at creation. Lookup is done by a linear scan, as I expect
> >> > > events to occur rarely. If performance becomes an issue, a binary search
> >> > > could be done instead (or some kind of hash lookup).
> >> > >
> >> > > Signed-off-by: Sean Anderson <sean.anderson@linux.dev>
> >> > > ---
> >> > >
> >> > > drivers/hwmon/iio_hwmon.c | 322 +++++++++++++++++++++++++++++++++++++-
> >> > > 1 file changed, 321 insertions(+), 1 deletion(-)
> >> > >
> >> > > diff --git a/drivers/hwmon/iio_hwmon.c b/drivers/hwmon/iio_hwmon.c
> >> > > index 3db4d4b30022..c963bc5452ba 100644
> >> > > --- a/drivers/hwmon/iio_hwmon.c
> >> > > +++ b/drivers/hwmon/iio_hwmon.c
> >> > > @@ -8,6 +8,7 @@
> >> > > #include <linux/slab.h>
> >> > > #include <linux/mod_devicetable.h>
> >> > > #include <linux/module.h>
> >> > > +#include <linux/notifier.h>
> >> > > #include <linux/err.h>
> >> > > #include <linux/platform_device.h>
> >> > > #include <linux/property.h>
> >> > > @@ -15,7 +16,192 @@
> >> > > #include <linux/hwmon.h>
> >> > > #include <linux/hwmon-sysfs.h>
> >> > > #include <linux/iio/consumer.h>
> >> > > +#include <linux/iio/events.h>
> >> > > +#include <linux/iio/iio.h>
> >> > > #include <linux/iio/types.h>
> >> > > +#include <uapi/linux/iio/events.h>
> >> > > +
> >> > > +/* Protects iio_hwmon_listeners and listeners' refcnt */
> >> > > +DEFINE_MUTEX(iio_hwmon_listener_lock);
> >> > > +LIST_HEAD(iio_hwmon_listeners);
> >> > > +
> >> > > +/**
> >> > > + * struct iio_hwmon_listener - Listener for IIO events
> >> > > + * @block: Notifier for events
> >> > > + * @ids: Array of IIO event ids, one per alarm
> >> > > + * @alarms: Bitmap of alarms
> >> > > + * @num_alarms: Length of @ids and @alarms
> >> > > + * @indio_dev: Device we are listening to
> >> > > + * @list: List of all listeners
> >> > > + * @refcnt: Reference count
> >> > > + */
> >> > > +struct iio_hwmon_listener {
> >> > > + struct notifier_block block;
> >> > > + u64 *ids;
> >> > > + unsigned long *alarms;
> >> > > + size_t num_alarms;
> >> > > +
> >> > > + struct iio_dev *indio_dev;
> >> > > + struct list_head list;
> >> > > + unsigned int refcnt;
> >> > > +};
> >> > > +
> >> > > +/**
> >> > > + * iio_hwmon_lookup_alarm() - Find an alarm by id
> >> > > + * @listener: Event listener
> >> > > + * @id: IIO event id
> >> > > + *
> >> > > + * Return: index of @id in @listener->ids, or -1 if not found
> >> > > + */
> >> > > +static ssize_t iio_hwmon_lookup_alarm(struct iio_hwmon_listener *listener,
> >> > > + u64 id)
> >> > > +{
> >> > > + ssize_t i;
> >> > > +
> >> > > + for (i = 0; i < listener->num_alarms; i++)
> >> > > + if (listener->ids[i] == id)
> >> > > + return i;
> >> > > +
> >> > > + return -1;
> >> > > +}
> >> > > +
> >> > > +static int iio_hwmon_listener_callback(struct notifier_block *block,
> >> > > + unsigned long action, void *data)
> >> > > +{
> >> > > + struct iio_hwmon_listener *listener =
> >> > > + container_of(block, struct iio_hwmon_listener, block);
> >> > > + struct iio_event_data *ev = data;
> >> > > + ssize_t i;
> >> > > +
> >> > > + if (action != IIO_NOTIFY_EVENT)
> >> > > + return NOTIFY_DONE;
> >> > > +
> >> > > + i = iio_hwmon_lookup_alarm(listener, ev->id);
> >> > > + if (i >= 0)
> >> > > + set_bit(i, listener->alarms);
> >> > > + else
> >> > > + dev_warn_once(&listener->indio_dev->dev,
> >> > > + "unknown event %016llx\n", ev->id);
> >> > > +
> >> > > + return NOTIFY_DONE;
> >> > > +}
> >> > > +
> >> > > +/**
> >> > > + * iio_event_id() - Calculate an IIO event id
> >> > > + * @channel: IIO channel for this event
> >> > > + * @type: Event type (theshold, rate-of-change, etc.)
> >> > > + * @dir: Event direction (rising, falling, etc.)
> >> > > + *
> >> > > + * Return: IIO event id corresponding to this event's IIO id
> >> > > + */
> >> > > +static u64 iio_event_id(struct iio_chan_spec const *chan,
> >> > > + enum iio_event_type type,
> >> > > + enum iio_event_direction dir)
> >> > > +{
> >> > > + if (chan->differential)
> >> > > + return IIO_DIFF_EVENT_CODE(chan->type, chan->channel,
> >> > > + chan->channel2, type, dir);
> >> > > + if (chan->modified)
> >> > > + return IIO_MOD_EVENT_CODE(chan->type, chan->channel,
> >> > > + chan->channel2, type, dir);
> >> > > + return IIO_UNMOD_EVENT_CODE(chan->type, chan->channel, type, dir);
> >> > > +}
> >> > > +
> >> > > +/**
> >> > > + * iio_hwmon_listener_get() - Get a listener for an IIO device
> >> > > + * @indio_dev: IIO device to listen to
> >> > > + *
> >> > > + * Look up or create a new listener for @indio_dev. The returned listener is
> >> > > + * registered with @indio_dev, but events still need to be manually enabled.
> >> > > + * You must call iio_hwmon_listener_put() when you are done.
> >> > > + *
> >> > > + * Return: Listener for @indio_dev, or an error pointer
> >> > > + */
> >> > > +static struct iio_hwmon_listener *iio_hwmon_listener_get(struct iio_dev
> >> > > *indio_dev)
> >> > > +{
> >> > > + struct iio_hwmon_listener *listener;
> >> > > + int err = -ENOMEM;
> >> > > + size_t i, j;
> >> > > +
> >> > > + guard(mutex)(&iio_hwmon_listener_lock);
> >> > > + list_for_each_entry(listener, &iio_hwmon_listeners, list) {
> >> > > + if (listener->indio_dev == indio_dev) {
> >> > > + if (likely(listener->refcnt != UINT_MAX))
> >> > > + listener->refcnt++;
> >> >
> >> > I dunno for the above to ever happen :).
> >>
> >> Well, I can remove it if you like.
> >>
> >> > And as Andy stated, let's just use proper refcount APIs.
> >>
> >> No point in using atomic ops if they are only accessed under a mutex.
> >
> > Not the point... If there are proper APIs for handling things like this, not sure why
> > not using and then coming up with things like the above? And the same goes to the
> > release path.
>
> The API is for doing reference counts *atomically*. If you do not need
> atomic reference counting, then it is the *wrong* API. I suggest reading
Well, It won't make your code wrong. It's just about re-using what we have already.
But my main complain was about having your own saturation checks in here.
I also dislike the release path where you do have to explicitly check for 0 to
call the cleanup API. That is all handled already. Not to mention that it is a fairly
common pattern to use these APIs even if you don't really __need__ it's atomicity.
> the block comment at the beginning of refcnt.h to see the sorts of
> contortions it has to go through because it is an atomic API. Since we
And? It's very well hidden in the API... This is also not a fastpath at
all so performance is also not a concern AFAICT.
Up to the maintainers anyways but I cannot say I agree with it. So, I
guess we can agree in disagreeing :)
- Nuno Sá
^ permalink raw reply [flat|nested] 62+ messages in thread
* Re: [PATCH 3/7] iio: Add in-kernel API for events
2025-07-29 20:09 ` Sean Anderson
@ 2025-07-31 12:59 ` Jonathan Cameron
0 siblings, 0 replies; 62+ messages in thread
From: Jonathan Cameron @ 2025-07-31 12:59 UTC (permalink / raw)
To: Sean Anderson
Cc: Jonathan Cameron, Nuno Sá, Jean Delvare, Guenter Roeck,
linux-iio, linux-hwmon, Andy Shevchenko, Nuno Sá,
linux-kernel, David Lechner
On Tue, 29 Jul 2025 16:09:20 -0400
Sean Anderson <sean.anderson@linux.dev> wrote:
> On 7/29/25 14:33, Jonathan Cameron wrote:
> > On Mon, 28 Jul 2025 18:44:30 -0400
> > Sean Anderson <sean.anderson@linux.dev> wrote:
> >
> >> On 7/27/25 12:21, Jonathan Cameron wrote:
> >> > On Tue, 15 Jul 2025 12:52:19 -0400
> >> > Sean Anderson <sean.anderson@linux.dev> wrote:
> >> >
> >> >> On 7/15/25 07:09, Nuno Sá wrote:
> >> >> > On Mon, 2025-07-14 at 21:20 -0400, Sean Anderson wrote:
> >> >> >> Add an API to notify consumers about events. Events still need to be
> >> >> >> enabled using the iio_read_event/iio_write_event functions. Of course,
> >> >> >> userspace can also manipulate the enabled events. I don't think this is
> >> >> >> too much of an issue, since userspace can also manipulate the event
> >> >> >> thresholds. But enabling events may cause existing programs to be
> >> >> >> surprised when they get something unexpected. Maybe we should set the
> >> >> >> interface as busy when there are any in-kernel listeners?
> >> >> >>
> >> >> >
> >> >> > Sensible question. I'm not that familiar with events but I suspect is not
> >> >> > trivial (if doable) to do a similar approach as with buffers? With buffers, an
> >> >> > inkernal consumer get's it's own buffer object (that goes into a list of active
> >> >> > buffers in the iio device) with all channels enabled and then we demux the
> >> >> > appropriate channels for each consumer.
> >> >>
> >> >> For in-kernel consumers I think it's reasonable to expect them to handle
> >> >> events they didn't explicitly enable. I'm not sure about userspace
> >> >> consumers.
> >> >
> >> > This already happens because we don't have a demux equivalent (what we do
> >> > for buffered data flow) so if a device only has a single enable bit that covers
> >> > multiple events (annoyingly common for accelerometers for example) then
> >> > userspace will get events it didn't ask for. We 'could' fix that,
> >> > but it's never really been worth the effort.
> >> >
> >> > Events tend to be low data rate so an occasionally extra is rather different
> >> > to having to have much larger data buffers to handle a range of channels you
> >> > never asked for.
> >> >
> >> > Lets be careful to document this behaviour as 'may enable extra events'
> >> > as then if we decide later to do demux type stuff we won't be breaking ABI.
> >> > No one will mind getting fewer spurious events due to a core improvement.
> >>
> >> Where would this get documented?
> >
> > Starting point will be in the docs for the ABI that asks for any events at all.
> >
> > Also useful to add some thing to Documentation/IIO though there are lots of
> > other things those docs don't yet cover :(
>
> Notably the whole events API :l
>
> >>
> >> >>
> >> >> > Independent of the above, we can argue that having both inkernel and userspace
> >> >> > changing thresholds is ok (I mean, there's nothing stopping two userspace apps
> >> >> > doing that) but we should likely be careful with enabling/disabling. If multiple
> >> >> > consumers enable the same event, one of them disabling it should not disable it
> >> >> > for all the consumers, right?
> >> >>
> >> >> Right now the HWMON consumer never permanently disable events to avoid this
> >> >> issue. It does toggle the enable to determine if an alarm should stay
> >> >> enabled:
> >> >> ________
> >> >> condition __/ \________
> >> >> _____ ____ ___
> >> >> enable \__/ \__/
> >> >>
> >> >> event | |
> >> >> __ ____
> >> >> alarm __/ \__/ \_____
> >> >>
> >> >> read 1 1 0
> >> >>
> >> >> I suppose this could also be done by comparing the raw threshold to the
> >> >> channel.
> >> >
> >> > I wonder if we should add the option to do a 'get_exclusive' or similar
> >> > to block the IIO user interfaces if something critical is using the device.
> >> >
> >> > If we were for instance to use this to block the IOCTL to get the events
> >> > fd then any built in driver etc will almost certainly load before anyone
> >> > can call the ioctl so it will fairly cleanly block things.
> >>
> >> This is how it currently works for userspace. Only one process can create
> >> the event fd, and everyone else gets -EBUSY.
> >>
> >> Of course, it would be pretty surprising to have an IIO device where
> >> some channels were used by userspace and others were used by hwmon and
> >> then have your daemon stop working after you update your kernel because
> >> now the hwmon driver takes exclusive event access.
> >
> > True. I wonder how many boards we don't know about are using the iio-hwmon
> > bridge. We can check the ones in kernel for whether they grab all the
> > channels (which would rule this out).
> >
> > Another things we could do is have an opt in from the IIO driver.
> > That way only 'new' drivers would have this behaviour. Not nice though.
>
> I would really like for this to "just work" if at all possible, so an
> opt-out would be preferable. Maybe a hwmon module parameter.
>
> But I think we can do better:
>
> - Both kernel/userspace can/should handle unexpected events
> - This includes extra (synthetic) events.
> - Both kernel/userspace mostly just want to enable events
> - Disabling events is not as important because of the previous bullet.
> - But losing events is probably bad so we want to ensure we trigger
> events at the same places they would have been triggered before.
>
> So maybe we have an implementation where
>
> - Enabling an event disables the backing event before re-enabling it if
> there are any existing users
> - Disabling an event only disables the backing event if all users are
> gone
>
> It could look something like
>
> iio_sysfs_event_set(event, val):
> if val:
> if !event.user_enable
> disable(event)
> enable(event)
> else if !event.kernel_enables
> disable(event)
> event.user_enable = val
>
> iio_inkern_event_set(event, val):
> if val:
> if event.kernel_enables++ || event.user_enable
> disable(event)
> enable(event)
> else if !--event.kernel_enables && !event.user_enable:
> disable(event)
Something like that should work. We'll need to be careful
to gate any push towards userspace on it waiting for something.
Given we only send them when IIO_BUSY_BIT_POS is set on the
event interface (which happens on requesting the fd) I think
we may be fine already.
Jonathan
>
> --Sean
>
> >>
> >> I originally had kernel users read from the kfifo just like userspace,
> >> but I was concerned about the above scenario.
> >>
> >
> > yeah, always a problem to retrofit policy.
> >
> >> --Sean
> >>
> >
>
>
^ permalink raw reply [flat|nested] 62+ messages in thread
* Re: [PATCH 7/7] hwmon: iio: Add alarm support
2025-07-31 10:52 ` Nuno Sá
@ 2025-08-02 10:53 ` Jonathan Cameron
0 siblings, 0 replies; 62+ messages in thread
From: Jonathan Cameron @ 2025-08-02 10:53 UTC (permalink / raw)
To: Nuno Sá
Cc: Sean Anderson, Jean Delvare, Guenter Roeck, linux-iio,
linux-hwmon, Andy Shevchenko, Nuno Sá, linux-kernel,
David Lechner
On Thu, 31 Jul 2025 11:52:10 +0100
Nuno Sá <noname.nuno@gmail.com> wrote:
> On Thu, Jul 17, 2025 at 12:00:13PM -0400, Sean Anderson wrote:
> > On 7/16/25 02:37, Nuno Sá wrote:
> > > On Tue, 2025-07-15 at 13:02 -0400, Sean Anderson wrote:
> > >> On 7/15/25 07:28, Nuno Sá wrote:
> > >> > On Mon, 2025-07-14 at 21:20 -0400, Sean Anderson wrote:
> > >> > > Add alarm support based on IIO threshold events. The alarm is cleared on
> > >> > > read, but will be set again if the condition is still present. This is
> > >> > > detected by disabling and re-enabling the event. The same trick is done
> > >> > > when creating the attribute to detect already-triggered events.
> > >> > >
> > >> > > The alarms are updated by an event listener. To keep the notifier call
> > >> > > chain short, we create one listener per iio device, shared across all
> > >> > > hwmon devices.
> > >> > >
> > >> > > To avoid dynamic creation of alarms, alarms for all possible events are
> > >> > > allocated at creation. Lookup is done by a linear scan, as I expect
> > >> > > events to occur rarely. If performance becomes an issue, a binary search
> > >> > > could be done instead (or some kind of hash lookup).
> > >> > >
> > >> > > Signed-off-by: Sean Anderson <sean.anderson@linux.dev>
> > >> > > ---
> > >> > >
> > >> > > drivers/hwmon/iio_hwmon.c | 322 +++++++++++++++++++++++++++++++++++++-
> > >> > > 1 file changed, 321 insertions(+), 1 deletion(-)
> > >> > >
> > >> > > diff --git a/drivers/hwmon/iio_hwmon.c b/drivers/hwmon/iio_hwmon.c
> > >> > > index 3db4d4b30022..c963bc5452ba 100644
> > >> > > --- a/drivers/hwmon/iio_hwmon.c
> > >> > > +++ b/drivers/hwmon/iio_hwmon.c
> > >> > > @@ -8,6 +8,7 @@
> > >> > > #include <linux/slab.h>
> > >> > > #include <linux/mod_devicetable.h>
> > >> > > #include <linux/module.h>
> > >> > > +#include <linux/notifier.h>
> > >> > > #include <linux/err.h>
> > >> > > #include <linux/platform_device.h>
> > >> > > #include <linux/property.h>
> > >> > > @@ -15,7 +16,192 @@
> > >> > > #include <linux/hwmon.h>
> > >> > > #include <linux/hwmon-sysfs.h>
> > >> > > #include <linux/iio/consumer.h>
> > >> > > +#include <linux/iio/events.h>
> > >> > > +#include <linux/iio/iio.h>
> > >> > > #include <linux/iio/types.h>
> > >> > > +#include <uapi/linux/iio/events.h>
> > >> > > +
> > >> > > +/* Protects iio_hwmon_listeners and listeners' refcnt */
> > >> > > +DEFINE_MUTEX(iio_hwmon_listener_lock);
> > >> > > +LIST_HEAD(iio_hwmon_listeners);
> > >> > > +
> > >> > > +/**
> > >> > > + * struct iio_hwmon_listener - Listener for IIO events
> > >> > > + * @block: Notifier for events
> > >> > > + * @ids: Array of IIO event ids, one per alarm
> > >> > > + * @alarms: Bitmap of alarms
> > >> > > + * @num_alarms: Length of @ids and @alarms
> > >> > > + * @indio_dev: Device we are listening to
> > >> > > + * @list: List of all listeners
> > >> > > + * @refcnt: Reference count
> > >> > > + */
> > >> > > +struct iio_hwmon_listener {
> > >> > > + struct notifier_block block;
> > >> > > + u64 *ids;
> > >> > > + unsigned long *alarms;
> > >> > > + size_t num_alarms;
> > >> > > +
> > >> > > + struct iio_dev *indio_dev;
> > >> > > + struct list_head list;
> > >> > > + unsigned int refcnt;
> > >> > > +};
> > >> > > +
> > >> > > +/**
> > >> > > + * iio_hwmon_lookup_alarm() - Find an alarm by id
> > >> > > + * @listener: Event listener
> > >> > > + * @id: IIO event id
> > >> > > + *
> > >> > > + * Return: index of @id in @listener->ids, or -1 if not found
> > >> > > + */
> > >> > > +static ssize_t iio_hwmon_lookup_alarm(struct iio_hwmon_listener *listener,
> > >> > > + u64 id)
> > >> > > +{
> > >> > > + ssize_t i;
> > >> > > +
> > >> > > + for (i = 0; i < listener->num_alarms; i++)
> > >> > > + if (listener->ids[i] == id)
> > >> > > + return i;
> > >> > > +
> > >> > > + return -1;
> > >> > > +}
> > >> > > +
> > >> > > +static int iio_hwmon_listener_callback(struct notifier_block *block,
> > >> > > + unsigned long action, void *data)
> > >> > > +{
> > >> > > + struct iio_hwmon_listener *listener =
> > >> > > + container_of(block, struct iio_hwmon_listener, block);
> > >> > > + struct iio_event_data *ev = data;
> > >> > > + ssize_t i;
> > >> > > +
> > >> > > + if (action != IIO_NOTIFY_EVENT)
> > >> > > + return NOTIFY_DONE;
> > >> > > +
> > >> > > + i = iio_hwmon_lookup_alarm(listener, ev->id);
> > >> > > + if (i >= 0)
> > >> > > + set_bit(i, listener->alarms);
> > >> > > + else
> > >> > > + dev_warn_once(&listener->indio_dev->dev,
> > >> > > + "unknown event %016llx\n", ev->id);
> > >> > > +
> > >> > > + return NOTIFY_DONE;
> > >> > > +}
> > >> > > +
> > >> > > +/**
> > >> > > + * iio_event_id() - Calculate an IIO event id
> > >> > > + * @channel: IIO channel for this event
> > >> > > + * @type: Event type (theshold, rate-of-change, etc.)
> > >> > > + * @dir: Event direction (rising, falling, etc.)
> > >> > > + *
> > >> > > + * Return: IIO event id corresponding to this event's IIO id
> > >> > > + */
> > >> > > +static u64 iio_event_id(struct iio_chan_spec const *chan,
> > >> > > + enum iio_event_type type,
> > >> > > + enum iio_event_direction dir)
> > >> > > +{
> > >> > > + if (chan->differential)
> > >> > > + return IIO_DIFF_EVENT_CODE(chan->type, chan->channel,
> > >> > > + chan->channel2, type, dir);
> > >> > > + if (chan->modified)
> > >> > > + return IIO_MOD_EVENT_CODE(chan->type, chan->channel,
> > >> > > + chan->channel2, type, dir);
> > >> > > + return IIO_UNMOD_EVENT_CODE(chan->type, chan->channel, type, dir);
> > >> > > +}
> > >> > > +
> > >> > > +/**
> > >> > > + * iio_hwmon_listener_get() - Get a listener for an IIO device
> > >> > > + * @indio_dev: IIO device to listen to
> > >> > > + *
> > >> > > + * Look up or create a new listener for @indio_dev. The returned listener is
> > >> > > + * registered with @indio_dev, but events still need to be manually enabled.
> > >> > > + * You must call iio_hwmon_listener_put() when you are done.
> > >> > > + *
> > >> > > + * Return: Listener for @indio_dev, or an error pointer
> > >> > > + */
> > >> > > +static struct iio_hwmon_listener *iio_hwmon_listener_get(struct iio_dev
> > >> > > *indio_dev)
> > >> > > +{
> > >> > > + struct iio_hwmon_listener *listener;
> > >> > > + int err = -ENOMEM;
> > >> > > + size_t i, j;
> > >> > > +
> > >> > > + guard(mutex)(&iio_hwmon_listener_lock);
> > >> > > + list_for_each_entry(listener, &iio_hwmon_listeners, list) {
> > >> > > + if (listener->indio_dev == indio_dev) {
> > >> > > + if (likely(listener->refcnt != UINT_MAX))
> > >> > > + listener->refcnt++;
> > >> >
> > >> > I dunno for the above to ever happen :).
> > >>
> > >> Well, I can remove it if you like.
> > >>
> > >> > And as Andy stated, let's just use proper refcount APIs.
> > >>
> > >> No point in using atomic ops if they are only accessed under a mutex.
> > >
> > > Not the point... If there are proper APIs for handling things like this, not sure why
> > > not using and then coming up with things like the above? And the same goes to the
> > > release path.
> >
> > The API is for doing reference counts *atomically*. If you do not need
> > atomic reference counting, then it is the *wrong* API. I suggest reading
>
> Well, It won't make your code wrong. It's just about re-using what we have already.
> But my main complain was about having your own saturation checks in here.
> I also dislike the release path where you do have to explicitly check for 0 to
> call the cleanup API. That is all handled already. Not to mention that it is a fairly
> common pattern to use these APIs even if you don't really __need__ it's atomicity.
>
> > the block comment at the beginning of refcnt.h to see the sorts of
> > contortions it has to go through because it is an atomic API. Since we
>
> And? It's very well hidden in the API... This is also not a fastpath at
> all so performance is also not a concern AFAICT.
>
> Up to the maintainers anyways but I cannot say I agree with it. So, I
> guess we can agree in disagreeing :)
I'd prefer using the standard refcount API. Sure it's not a performant
choice in all cases, but it wins on basis of familiarity and reduced
burden to review and maintenance.
Jonathan
>
> - Nuno Sá
>
^ permalink raw reply [flat|nested] 62+ messages in thread
end of thread, other threads:[~2025-08-02 10:53 UTC | newest]
Thread overview: 62+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2025-07-15 1:20 [PATCH 0/7] hwmon: iio: Add alarm support Sean Anderson
2025-07-15 1:20 ` [PATCH 1/7] math64: Add div64_s64_rem Sean Anderson
2025-07-15 8:03 ` Andy Shevchenko
2025-07-15 17:36 ` Sean Anderson
2025-07-16 10:15 ` Andy Shevchenko
2025-07-15 1:20 ` [PATCH 2/7] iio: inkern: Add API for reading/writing events Sean Anderson
2025-07-15 8:18 ` Andy Shevchenko
2025-07-15 15:42 ` Sean Anderson
2025-07-16 9:28 ` Andy Shevchenko
2025-07-17 16:42 ` Sean Anderson
2025-07-27 15:55 ` Jonathan Cameron
2025-07-15 10:35 ` Nuno Sá
2025-07-15 15:43 ` Sean Anderson
2025-07-16 6:23 ` Nuno Sá
2025-07-27 16:13 ` Jonathan Cameron
2025-07-15 1:20 ` [PATCH 3/7] iio: Add in-kernel API for events Sean Anderson
2025-07-15 8:20 ` Andy Shevchenko
2025-07-15 15:47 ` Sean Anderson
2025-07-16 9:47 ` Andy Shevchenko
2025-07-15 11:09 ` Nuno Sá
2025-07-15 16:52 ` Sean Anderson
2025-07-27 16:21 ` Jonathan Cameron
2025-07-28 22:44 ` Sean Anderson
2025-07-29 18:33 ` Jonathan Cameron
2025-07-29 20:09 ` Sean Anderson
2025-07-31 12:59 ` Jonathan Cameron
2025-07-27 16:24 ` Jonathan Cameron
2025-07-15 1:20 ` [PATCH 4/7] hwmon: iio: Refactor scale calculation into helper Sean Anderson
2025-07-15 8:35 ` Andy Shevchenko
2025-07-15 1:20 ` [PATCH 5/7] hwmon: iio: Add helper function for creating attributes Sean Anderson
2025-07-15 8:38 ` Andy Shevchenko
2025-07-15 15:55 ` Sean Anderson
2025-07-16 10:00 ` Andy Shevchenko
2025-07-27 16:31 ` Jonathan Cameron
2025-07-15 1:20 ` [PATCH 6/7] hwmon: iio: Add min/max support Sean Anderson
2025-07-15 8:41 ` Andy Shevchenko
2025-07-15 16:05 ` Sean Anderson
2025-07-16 10:01 ` Andy Shevchenko
2025-07-17 16:11 ` Sean Anderson
2025-07-27 16:35 ` Jonathan Cameron
2025-07-28 22:32 ` Sean Anderson
2025-07-29 18:37 ` Jonathan Cameron
2025-07-15 1:20 ` [PATCH 7/7] hwmon: iio: Add alarm support Sean Anderson
2025-07-15 8:50 ` Andy Shevchenko
2025-07-15 16:20 ` Sean Anderson
2025-07-16 10:08 ` Andy Shevchenko
2025-07-17 16:23 ` Sean Anderson
2025-07-21 7:42 ` Andy Shevchenko
2025-07-21 14:24 ` Sean Anderson
2025-07-15 11:28 ` Nuno Sá
2025-07-15 17:02 ` Sean Anderson
2025-07-15 19:26 ` Guenter Roeck
2025-07-15 19:40 ` Sean Anderson
2025-07-16 6:37 ` Nuno Sá
2025-07-17 16:00 ` Sean Anderson
2025-07-31 10:52 ` Nuno Sá
2025-08-02 10:53 ` Jonathan Cameron
2025-07-15 16:13 ` kernel test robot
2025-07-15 19:34 ` Guenter Roeck
2025-07-15 20:08 ` Sean Anderson
2025-07-16 7:44 ` kernel test robot
2025-07-27 16:50 ` Jonathan Cameron
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).