linux-stable/drivers/iio/industrialio-event.c
Alexandru Ardelean 32f171724e iio: core: rework iio device group creation
Up until now, the device groups that an IIO device had were limited to 6.
Two of these groups would account for buffer attributes (the buffer/ and
scan_elements/ directories).

Since we want to add multiple buffers per IIO device, this number may not
be enough, when adding a second buffer. So, this change reallocates the
groups array whenever an IIO device group is added, via a
iio_device_register_sysfs_group() helper.

This also means that the groups array should be assigned to
'indio_dev.dev.groups' really late, right before {cdev_}device_add() is
called to do the entire setup.
And we also must take care to free this array when the sysfs resources are
being cleaned up.

With this change we can also move the 'groups' & 'groupcounter' fields to
the iio_dev_opaque object. Up until now, this didn't make a whole lot of
sense (especially since we weren't sure how multibuffer support would look
like in the end).
But doing it now kills one birds with one stone.

An alternative, would be to add a configurable Kconfig symbol
CONFIG_IIO_MAX_BUFFERS_PER_DEVICE (or something like that) and compute a
static maximum of the groups we can support per IIO device. But that would
probably annoy a few people since that would make the system less
configurable.

Signed-off-by: Alexandru Ardelean <alexandru.ardelean@analog.com>
Link: https://lore.kernel.org/r/20210215104043.91251-11-alexandru.ardelean@analog.com
Signed-off-by: Jonathan Cameron <Jonathan.Cameron@huawei.com>
2021-03-11 20:47:03 +00:00

594 lines
15 KiB
C

// SPDX-License-Identifier: GPL-2.0-only
/* Industrial I/O event handling
*
* Copyright (c) 2008 Jonathan Cameron
*
* Based on elements of hwmon and input subsystems.
*/
#include <linux/anon_inodes.h>
#include <linux/device.h>
#include <linux/fs.h>
#include <linux/kernel.h>
#include <linux/kfifo.h>
#include <linux/module.h>
#include <linux/poll.h>
#include <linux/sched.h>
#include <linux/slab.h>
#include <linux/uaccess.h>
#include <linux/wait.h>
#include <linux/iio/iio.h>
#include <linux/iio/iio-opaque.h>
#include "iio_core.h"
#include <linux/iio/sysfs.h>
#include <linux/iio/events.h>
/**
* struct iio_event_interface - chrdev interface for an event line
* @wait: wait queue to allow blocking reads of events
* @det_events: list of detected events
* @dev_attr_list: list of event interface sysfs attribute
* @flags: file operations related flags including busy flag.
* @group: event interface sysfs attribute group
* @read_lock: lock to protect kfifo read operations
* @ioctl_handler: handler for event ioctl() calls
*/
struct iio_event_interface {
wait_queue_head_t wait;
DECLARE_KFIFO(det_events, struct iio_event_data, 16);
struct list_head dev_attr_list;
unsigned long flags;
struct attribute_group group;
struct mutex read_lock;
struct iio_ioctl_handler ioctl_handler;
};
bool iio_event_enabled(const struct iio_event_interface *ev_int)
{
return !!test_bit(IIO_BUSY_BIT_POS, &ev_int->flags);
}
/**
* iio_push_event() - try to add event to the list for userspace reading
* @indio_dev: IIO device structure
* @ev_code: What event
* @timestamp: When the event occurred
*
* Note: The caller must make sure that this function is not running
* concurrently for the same indio_dev more than once.
*
* This function may be safely used as soon as a valid reference to iio_dev has
* been obtained via iio_device_alloc(), but any events that are submitted
* before iio_device_register() has successfully completed will be silently
* discarded.
**/
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;
int copied;
if (!ev_int)
return 0;
/* 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);
}
return 0;
}
EXPORT_SYMBOL(iio_push_event);
/**
* iio_event_poll() - poll the event queue to find out if it has data
* @filep: File structure pointer to identify the device
* @wait: Poll table pointer to add the wait queue on
*
* Return: (EPOLLIN | EPOLLRDNORM) if data is available for reading
* or a negative error code on failure
*/
static __poll_t iio_event_poll(struct file *filep,
struct poll_table_struct *wait)
{
struct iio_dev *indio_dev = filep->private_data;
struct iio_dev_opaque *iio_dev_opaque = to_iio_dev_opaque(indio_dev);
struct iio_event_interface *ev_int = iio_dev_opaque->event_interface;
__poll_t events = 0;
if (!indio_dev->info)
return events;
poll_wait(filep, &ev_int->wait, wait);
if (!kfifo_is_empty(&ev_int->det_events))
events = EPOLLIN | EPOLLRDNORM;
return events;
}
static ssize_t iio_event_chrdev_read(struct file *filep,
char __user *buf,
size_t count,
loff_t *f_ps)
{
struct iio_dev *indio_dev = filep->private_data;
struct iio_dev_opaque *iio_dev_opaque = to_iio_dev_opaque(indio_dev);
struct iio_event_interface *ev_int = iio_dev_opaque->event_interface;
unsigned int copied;
int ret;
if (!indio_dev->info)
return -ENODEV;
if (count < sizeof(struct iio_event_data))
return -EINVAL;
do {
if (kfifo_is_empty(&ev_int->det_events)) {
if (filep->f_flags & O_NONBLOCK)
return -EAGAIN;
ret = wait_event_interruptible(ev_int->wait,
!kfifo_is_empty(&ev_int->det_events) ||
indio_dev->info == NULL);
if (ret)
return ret;
if (indio_dev->info == NULL)
return -ENODEV;
}
if (mutex_lock_interruptible(&ev_int->read_lock))
return -ERESTARTSYS;
ret = kfifo_to_user(&ev_int->det_events, buf, count, &copied);
mutex_unlock(&ev_int->read_lock);
if (ret)
return ret;
/*
* If we couldn't read anything from the fifo (a different
* thread might have been faster) we either return -EAGAIN if
* the file descriptor is non-blocking, otherwise we go back to
* sleep and wait for more data to arrive.
*/
if (copied == 0 && (filep->f_flags & O_NONBLOCK))
return -EAGAIN;
} while (copied == 0);
return copied;
}
static int iio_event_chrdev_release(struct inode *inode, struct file *filep)
{
struct iio_dev *indio_dev = filep->private_data;
struct iio_dev_opaque *iio_dev_opaque = to_iio_dev_opaque(indio_dev);
struct iio_event_interface *ev_int = iio_dev_opaque->event_interface;
clear_bit(IIO_BUSY_BIT_POS, &ev_int->flags);
iio_device_put(indio_dev);
return 0;
}
static const struct file_operations iio_event_chrdev_fileops = {
.read = iio_event_chrdev_read,
.poll = iio_event_poll,
.release = iio_event_chrdev_release,
.owner = THIS_MODULE,
.llseek = noop_llseek,
};
static int iio_event_getfd(struct iio_dev *indio_dev)
{
struct iio_dev_opaque *iio_dev_opaque = to_iio_dev_opaque(indio_dev);
struct iio_event_interface *ev_int = iio_dev_opaque->event_interface;
int fd;
if (ev_int == NULL)
return -ENODEV;
fd = mutex_lock_interruptible(&indio_dev->mlock);
if (fd)
return fd;
if (test_and_set_bit(IIO_BUSY_BIT_POS, &ev_int->flags)) {
fd = -EBUSY;
goto unlock;
}
iio_device_get(indio_dev);
fd = anon_inode_getfd("iio:event", &iio_event_chrdev_fileops,
indio_dev, O_RDONLY | O_CLOEXEC);
if (fd < 0) {
clear_bit(IIO_BUSY_BIT_POS, &ev_int->flags);
iio_device_put(indio_dev);
} else {
kfifo_reset_out(&ev_int->det_events);
}
unlock:
mutex_unlock(&indio_dev->mlock);
return fd;
}
static const char * const iio_ev_type_text[] = {
[IIO_EV_TYPE_THRESH] = "thresh",
[IIO_EV_TYPE_MAG] = "mag",
[IIO_EV_TYPE_ROC] = "roc",
[IIO_EV_TYPE_THRESH_ADAPTIVE] = "thresh_adaptive",
[IIO_EV_TYPE_MAG_ADAPTIVE] = "mag_adaptive",
[IIO_EV_TYPE_CHANGE] = "change",
};
static const char * const iio_ev_dir_text[] = {
[IIO_EV_DIR_EITHER] = "either",
[IIO_EV_DIR_RISING] = "rising",
[IIO_EV_DIR_FALLING] = "falling"
};
static const char * const iio_ev_info_text[] = {
[IIO_EV_INFO_ENABLE] = "en",
[IIO_EV_INFO_VALUE] = "value",
[IIO_EV_INFO_HYSTERESIS] = "hysteresis",
[IIO_EV_INFO_PERIOD] = "period",
[IIO_EV_INFO_HIGH_PASS_FILTER_3DB] = "high_pass_filter_3db",
[IIO_EV_INFO_LOW_PASS_FILTER_3DB] = "low_pass_filter_3db",
};
static enum iio_event_direction iio_ev_attr_dir(struct iio_dev_attr *attr)
{
return attr->c->event_spec[attr->address & 0xffff].dir;
}
static enum iio_event_type iio_ev_attr_type(struct iio_dev_attr *attr)
{
return attr->c->event_spec[attr->address & 0xffff].type;
}
static enum iio_event_info iio_ev_attr_info(struct iio_dev_attr *attr)
{
return (attr->address >> 16) & 0xffff;
}
static ssize_t iio_ev_state_store(struct device *dev,
struct device_attribute *attr,
const char *buf,
size_t len)
{
struct iio_dev *indio_dev = dev_to_iio_dev(dev);
struct iio_dev_attr *this_attr = to_iio_dev_attr(attr);
int ret;
bool val;
ret = strtobool(buf, &val);
if (ret < 0)
return ret;
ret = indio_dev->info->write_event_config(indio_dev,
this_attr->c, iio_ev_attr_type(this_attr),
iio_ev_attr_dir(this_attr), val);
return (ret < 0) ? ret : len;
}
static ssize_t iio_ev_state_show(struct device *dev,
struct device_attribute *attr,
char *buf)
{
struct iio_dev *indio_dev = dev_to_iio_dev(dev);
struct iio_dev_attr *this_attr = to_iio_dev_attr(attr);
int val;
val = indio_dev->info->read_event_config(indio_dev,
this_attr->c, iio_ev_attr_type(this_attr),
iio_ev_attr_dir(this_attr));
if (val < 0)
return val;
else
return sprintf(buf, "%d\n", val);
}
static ssize_t iio_ev_value_show(struct device *dev,
struct device_attribute *attr,
char *buf)
{
struct iio_dev *indio_dev = dev_to_iio_dev(dev);
struct iio_dev_attr *this_attr = to_iio_dev_attr(attr);
int val, val2, val_arr[2];
int ret;
ret = indio_dev->info->read_event_value(indio_dev,
this_attr->c, iio_ev_attr_type(this_attr),
iio_ev_attr_dir(this_attr), iio_ev_attr_info(this_attr),
&val, &val2);
if (ret < 0)
return ret;
val_arr[0] = val;
val_arr[1] = val2;
return iio_format_value(buf, ret, 2, val_arr);
}
static ssize_t iio_ev_value_store(struct device *dev,
struct device_attribute *attr,
const char *buf,
size_t len)
{
struct iio_dev *indio_dev = dev_to_iio_dev(dev);
struct iio_dev_attr *this_attr = to_iio_dev_attr(attr);
int val, val2;
int ret;
if (!indio_dev->info->write_event_value)
return -EINVAL;
ret = iio_str_to_fixpoint(buf, 100000, &val, &val2);
if (ret)
return ret;
ret = indio_dev->info->write_event_value(indio_dev,
this_attr->c, iio_ev_attr_type(this_attr),
iio_ev_attr_dir(this_attr), iio_ev_attr_info(this_attr),
val, val2);
if (ret < 0)
return ret;
return len;
}
static int iio_device_add_event(struct iio_dev *indio_dev,
const struct iio_chan_spec *chan, unsigned int spec_index,
enum iio_event_type type, enum iio_event_direction dir,
enum iio_shared_by shared_by, const unsigned long *mask)
{
struct iio_dev_opaque *iio_dev_opaque = to_iio_dev_opaque(indio_dev);
ssize_t (*show)(struct device *, struct device_attribute *, char *);
ssize_t (*store)(struct device *, struct device_attribute *,
const char *, size_t);
unsigned int attrcount = 0;
unsigned int i;
char *postfix;
int ret;
for_each_set_bit(i, mask, sizeof(*mask)*8) {
if (i >= ARRAY_SIZE(iio_ev_info_text))
return -EINVAL;
if (dir != IIO_EV_DIR_NONE)
postfix = kasprintf(GFP_KERNEL, "%s_%s_%s",
iio_ev_type_text[type],
iio_ev_dir_text[dir],
iio_ev_info_text[i]);
else
postfix = kasprintf(GFP_KERNEL, "%s_%s",
iio_ev_type_text[type],
iio_ev_info_text[i]);
if (postfix == NULL)
return -ENOMEM;
if (i == IIO_EV_INFO_ENABLE) {
show = iio_ev_state_show;
store = iio_ev_state_store;
} else {
show = iio_ev_value_show;
store = iio_ev_value_store;
}
ret = __iio_add_chan_devattr(postfix, chan, show, store,
(i << 16) | spec_index, shared_by, &indio_dev->dev,
&iio_dev_opaque->event_interface->dev_attr_list);
kfree(postfix);
if ((ret == -EBUSY) && (shared_by != IIO_SEPARATE))
continue;
if (ret)
return ret;
attrcount++;
}
return attrcount;
}
static int iio_device_add_event_sysfs(struct iio_dev *indio_dev,
struct iio_chan_spec const *chan)
{
int ret = 0, i, attrcount = 0;
enum iio_event_direction dir;
enum iio_event_type type;
for (i = 0; i < chan->num_event_specs; i++) {
type = chan->event_spec[i].type;
dir = chan->event_spec[i].dir;
ret = iio_device_add_event(indio_dev, chan, i, type, dir,
IIO_SEPARATE, &chan->event_spec[i].mask_separate);
if (ret < 0)
return ret;
attrcount += ret;
ret = iio_device_add_event(indio_dev, chan, i, type, dir,
IIO_SHARED_BY_TYPE,
&chan->event_spec[i].mask_shared_by_type);
if (ret < 0)
return ret;
attrcount += ret;
ret = iio_device_add_event(indio_dev, chan, i, type, dir,
IIO_SHARED_BY_DIR,
&chan->event_spec[i].mask_shared_by_dir);
if (ret < 0)
return ret;
attrcount += ret;
ret = iio_device_add_event(indio_dev, chan, i, type, dir,
IIO_SHARED_BY_ALL,
&chan->event_spec[i].mask_shared_by_all);
if (ret < 0)
return ret;
attrcount += ret;
}
ret = attrcount;
return ret;
}
static inline int __iio_add_event_config_attrs(struct iio_dev *indio_dev)
{
int j, ret, attrcount = 0;
/* Dynamically created from the channels array */
for (j = 0; j < indio_dev->num_channels; j++) {
ret = iio_device_add_event_sysfs(indio_dev,
&indio_dev->channels[j]);
if (ret < 0)
return ret;
attrcount += ret;
}
return attrcount;
}
static bool iio_check_for_dynamic_events(struct iio_dev *indio_dev)
{
int j;
for (j = 0; j < indio_dev->num_channels; j++) {
if (indio_dev->channels[j].num_event_specs != 0)
return true;
}
return false;
}
static void iio_setup_ev_int(struct iio_event_interface *ev_int)
{
INIT_KFIFO(ev_int->det_events);
init_waitqueue_head(&ev_int->wait);
mutex_init(&ev_int->read_lock);
}
static long iio_event_ioctl(struct iio_dev *indio_dev, struct file *filp,
unsigned int cmd, unsigned long arg)
{
int __user *ip = (int __user *)arg;
int fd;
if (cmd == IIO_GET_EVENT_FD_IOCTL) {
fd = iio_event_getfd(indio_dev);
if (fd < 0)
return fd;
if (copy_to_user(ip, &fd, sizeof(fd)))
return -EFAULT;
return 0;
}
return IIO_IOCTL_UNHANDLED;
}
static const char *iio_event_group_name = "events";
int iio_device_register_eventset(struct iio_dev *indio_dev)
{
struct iio_dev_opaque *iio_dev_opaque = to_iio_dev_opaque(indio_dev);
struct iio_event_interface *ev_int;
struct iio_dev_attr *p;
int ret = 0, attrcount_orig = 0, attrcount, attrn;
struct attribute **attr;
if (!(indio_dev->info->event_attrs ||
iio_check_for_dynamic_events(indio_dev)))
return 0;
ev_int = kzalloc(sizeof(struct iio_event_interface), GFP_KERNEL);
if (ev_int == NULL)
return -ENOMEM;
iio_dev_opaque->event_interface = ev_int;
INIT_LIST_HEAD(&ev_int->dev_attr_list);
iio_setup_ev_int(ev_int);
if (indio_dev->info->event_attrs != NULL) {
attr = indio_dev->info->event_attrs->attrs;
while (*attr++ != NULL)
attrcount_orig++;
}
attrcount = attrcount_orig;
if (indio_dev->channels) {
ret = __iio_add_event_config_attrs(indio_dev);
if (ret < 0)
goto error_free_setup_event_lines;
attrcount += ret;
}
ev_int->group.name = iio_event_group_name;
ev_int->group.attrs = kcalloc(attrcount + 1,
sizeof(ev_int->group.attrs[0]),
GFP_KERNEL);
if (ev_int->group.attrs == NULL) {
ret = -ENOMEM;
goto error_free_setup_event_lines;
}
if (indio_dev->info->event_attrs)
memcpy(ev_int->group.attrs,
indio_dev->info->event_attrs->attrs,
sizeof(ev_int->group.attrs[0]) * attrcount_orig);
attrn = attrcount_orig;
/* Add all elements from the list. */
list_for_each_entry(p, &ev_int->dev_attr_list, l)
ev_int->group.attrs[attrn++] = &p->dev_attr.attr;
ret = iio_device_register_sysfs_group(indio_dev, &ev_int->group);
if (ret)
goto error_free_setup_event_lines;
ev_int->ioctl_handler.ioctl = iio_event_ioctl;
iio_device_ioctl_handler_register(&iio_dev_opaque->indio_dev,
&ev_int->ioctl_handler);
return 0;
error_free_setup_event_lines:
iio_free_chan_devattr_list(&ev_int->dev_attr_list);
kfree(ev_int);
iio_dev_opaque->event_interface = NULL;
return ret;
}
/**
* iio_device_wakeup_eventset - Wakes up the event waitqueue
* @indio_dev: The IIO device
*
* Wakes up the event waitqueue used for poll() and blocking read().
* Should usually be called when the device is unregistered.
*/
void iio_device_wakeup_eventset(struct iio_dev *indio_dev)
{
struct iio_dev_opaque *iio_dev_opaque = to_iio_dev_opaque(indio_dev);
if (iio_dev_opaque->event_interface == NULL)
return;
wake_up(&iio_dev_opaque->event_interface->wait);
}
void iio_device_unregister_eventset(struct iio_dev *indio_dev)
{
struct iio_dev_opaque *iio_dev_opaque = to_iio_dev_opaque(indio_dev);
struct iio_event_interface *ev_int = iio_dev_opaque->event_interface;
if (ev_int == NULL)
return;
iio_device_ioctl_handler_unregister(&ev_int->ioctl_handler);
iio_free_chan_devattr_list(&ev_int->dev_attr_list);
kfree(ev_int->group.attrs);
kfree(ev_int);
iio_dev_opaque->event_interface = NULL;
}