From be3a5db65a9f66e13cc2c98d445106e5ea5b1ebd Mon Sep 17 00:00:00 2001 From: Paul Cercueil Date: Mon, 6 Nov 2023 16:40:21 +0100 Subject: [PATCH 01/16] iio.h: Add new iio_chan_type entries Add the new iio_chan_type enum entries that were added in kernel v6.7. Signed-off-by: Paul Cercueil --- channel.c | 4 ++++ include/iio/iio.h | 4 ++++ 2 files changed, 8 insertions(+) diff --git a/channel.c b/channel.c index db9e7b8e6..233a1f717 100644 --- a/channel.c +++ b/channel.c @@ -50,6 +50,10 @@ static const char * const iio_chan_type_name_spec[] = { [IIO_POSITIONRELATIVE] = "positionrelative", [IIO_PHASE] = "phase", [IIO_MASSCONCENTRATION] = "massconcentration", + [IIO_DELTA_ANGL] = "delta_angl", + [IIO_DELTA_VELOCITY] = "delta_velocity", + [IIO_COLORTEMP] = "colortemp", + [IIO_CHROMATICITY] = "chromaticity", }; static const char * const hwmon_chan_type_name_spec[] = { diff --git a/include/iio/iio.h b/include/iio/iio.h index 49393251e..fad39ddc2 100644 --- a/include/iio/iio.h +++ b/include/iio/iio.h @@ -195,6 +195,10 @@ enum iio_chan_type { IIO_POSITIONRELATIVE, IIO_PHASE, IIO_MASSCONCENTRATION, + IIO_DELTA_ANGL, + IIO_DELTA_VELOCITY, + IIO_COLORTEMP, + IIO_CHROMATICITY, IIO_CHAN_TYPE_UNKNOWN = INT_MAX }; From 5080ab094e2f5bf2104c0713188d57971061fe05 Mon Sep 17 00:00:00 2001 From: Paul Cercueil Date: Tue, 24 Oct 2023 13:36:41 +0200 Subject: [PATCH 02/16] Add support for IIO events Add a new API and the corresponding high-level code to support reading and decoding IIO events. Signed-off-by: Paul Cercueil --- CMakeLists.txt | 16 ++++- events.c | 144 ++++++++++++++++++++++++++++++++++++++ iio-private.h | 2 +- include/iio/iio-backend.h | 7 ++ include/iio/iio.h | 82 ++++++++++++++++++++++ 5 files changed, 249 insertions(+), 2 deletions(-) create mode 100644 events.c diff --git a/CMakeLists.txt b/CMakeLists.txt index 71736d5d5..cd90442ea 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -30,7 +30,21 @@ endif() option(BUILD_SHARED_LIBS "Build shared libraries" ON) -add_library(iio attr.c backend.c block.c channel.c device.c context.c buffer.c mask.c utilities.c scan.c sort.c task.c stream.c +add_library(iio + attr.c + backend.c + block.c + buffer.c + channel.c + context.c + device.c + events.c + mask.c + scan.c + sort.c + stream.c + task.c + utilities.c ${CMAKE_CURRENT_BINARY_DIR}/iio-config.h ) diff --git a/events.c b/events.c new file mode 100644 index 000000000..4b130eb74 --- /dev/null +++ b/events.c @@ -0,0 +1,144 @@ +// SPDX-License-Identifier: LGPL-2.1-or-later +/* + * libiio - Library for interfacing industrial I/O (IIO) devices + * + * Copyright (C) 2023 Analog Devices, Inc. + * Author: Paul Cercueil + */ + +#include "iio-private.h" + +#include +#include +#include + +struct iio_event_stream_pdata; + +struct iio_event_stream { + const struct iio_device *dev; + struct iio_event_stream_pdata *pdata; +}; + +/* Corresponds to IIO_EVENT_CODE_EXTRACT_CHAN() and + * IIO_EVENT_CODE_EXTRACT_CHAN2() macros of */ +static inline int16_t +iio_event_get_channel_id(const struct iio_event *event, unsigned int channel) +{ + return (int16_t)(event->id >> (channel << 4)); +} + +/* Corresponds to IIO_EVENT_CODE_EXTRACT_DIFF() of */ +static inline bool +iio_event_is_differential(const struct iio_event *event) +{ + return event->id & BIT(55); +} + +/* Corresponds to IIO_EVENT_CODE_EXTRACT_MODIFIER() of */ +static inline enum iio_modifier +iio_event_get_modifier(const struct iio_event *event) +{ + return (enum iio_modifier)((event->id >> 40) & 0xff); +} + +/* Corresponds to IIO_EVENT_CODE_EXTRACT_CHAN_TYPE() of */ +static inline enum iio_chan_type +iio_event_get_chan_type(const struct iio_event *event) +{ + return (enum iio_chan_type)((event->id >> 32) & 0xff); +} + +const struct iio_channel * +iio_event_get_channel(const struct iio_event *event, + const struct iio_device *dev, bool diff) +{ + const struct iio_channel *chn = NULL; + const char *ptr; + unsigned int i; + int16_t chid; + + if (diff && !iio_event_is_differential(event)) + return NULL; + + chid = iio_event_get_channel_id(event, diff); + if (chid < 0) + return NULL; + + if ((unsigned int)chid >= dev->nb_channels) { + dev_warn(dev, "Unexpected IIO event channel ID\n"); + return NULL; + } + + for (i = 0; i < dev->nb_channels; i++) { + chn = dev->channels[i]; + + if (chn->type != iio_event_get_chan_type(event) + || chn->modifier != iio_event_get_modifier(event)) { + continue; + } + + for (ptr = chn->id; *ptr && isalpha((unsigned char)*ptr); ) + ptr++; + + if (!*ptr && chid <= 0) + break; + + if ((uint16_t)chid == strtoul(ptr, NULL, 10)) + break; + } + + if (chn) { + chn_dbg(chn, "Found channel %s for event\n", + iio_channel_get_id(chn)); + } else { + dev_dbg(dev, "Unable to find channel for event\n"); + } + + return chn; +} + +struct iio_event_stream * +iio_device_create_event_stream(const struct iio_device *dev) +{ + struct iio_event_stream *stream; + int err; + + if (!dev->ctx->ops->open_ev) + return iio_ptr(-ENOSYS); + + stream = zalloc(sizeof(*stream)); + if (!stream) + return iio_ptr(-ENOMEM); + + stream->dev = dev; + + stream->pdata = dev->ctx->ops->open_ev(dev); + err = iio_err(stream->pdata); + if (err) { + free(stream); + return iio_ptr(err); + } + + return stream; +} + +void iio_event_stream_destroy(struct iio_event_stream *stream) +{ + if (stream->dev->ctx->ops->close_ev) + stream->dev->ctx->ops->close_ev(stream->pdata); + + free(stream); +} + +int iio_event_stream_read(struct iio_event_stream *stream, + struct iio_event *out_event, + bool nonblock) +{ + if (!stream->dev->ctx->ops->read_ev) + return -ENOSYS; + + if (!out_event) + return -EINVAL; + + return stream->dev->ctx->ops->read_ev(stream->pdata, out_event, nonblock); +} diff --git a/iio-private.h b/iio-private.h index f5b271d68..3a5330f8b 100644 --- a/iio-private.h +++ b/iio-private.h @@ -34,7 +34,7 @@ #define is_little_endian() (__BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__) #endif -#define BIT(x) (1 << (x)) +#define BIT(x) (1ull << (x)) #define BIT_MASK(bit) BIT((bit) % 32) #define BIT_WORD(bit) ((bit) / 32) diff --git a/include/iio/iio-backend.h b/include/iio/iio-backend.h index 8df8031af..73b5fec6c 100644 --- a/include/iio/iio-backend.h +++ b/include/iio/iio-backend.h @@ -53,6 +53,7 @@ struct iio_buffer_pdata; struct iio_context_pdata; struct iio_device_pdata; struct iio_channel_pdata; +struct iio_event_stream_pdata; enum iio_backend_api_ver { IIO_BACKEND_API_V1 = 1, @@ -124,6 +125,12 @@ struct iio_backend_ops { int (*dequeue_block)(struct iio_block_pdata *pdata, bool nonblock); int (*get_dmabuf_fd)(struct iio_block_pdata *pdata); + + struct iio_event_stream_pdata *(*open_ev)(const struct iio_device *dev); + void (*close_ev)(struct iio_event_stream_pdata *pdata); + int (*read_ev)(struct iio_event_stream_pdata *pdata, + struct iio_event *out_event, + bool nonblock); }; /** diff --git a/include/iio/iio.h b/include/iio/iio.h index fad39ddc2..bd2b355a1 100644 --- a/include/iio/iio.h +++ b/include/iio/iio.h @@ -92,6 +92,7 @@ struct iio_context; struct iio_device; struct iio_channel; struct iio_channels_mask; +struct iio_event_stream; struct iio_buffer; struct iio_scan; struct iio_stream; @@ -1294,6 +1295,87 @@ static inline bool iio_device_is_hwmon(const struct iio_device *dev) } +/** @} *//* ------------------------------------------------------------------*/ +/* ---------------------------- IIO events support ---------------------------*/ +/** @defgroup Events Functions to read IIO events + * @{ + * @struct iio_event + * @brief Represents a IIO event. + * + * This structure is the same as 'iio_event_data' from . + */ +struct iio_event { + uint64_t id; + int64_t timestamp; +}; + +/** + * @brief Get the type of a given IIO event + * @param event A pointer to an iio_event structure + * @return An enum iio_event_type. + * + * NOTE:Corresponds to the IIO_EVENT_CODE_EXTRACT_TYPE macro of + * . */ +static inline enum iio_event_type +iio_event_get_type(const struct iio_event *event) +{ + return (enum iio_event_type)((event->id >> 56) & 0xff); +} + +/** + * @brief Get the direction of a given IIO event + * @param event A pointer to an iio_event structure + * @return An enum iio_event_direction. + * + * NOTE:Corresponds to the IIO_EVENT_CODE_EXTRACT_DIR macro of + * . */ +static inline enum iio_event_direction +iio_event_get_direction(const struct iio_event *event) +{ + return (enum iio_event_direction)((event->id >> 48) & 0x7f); +} + +/** + * @brief Get a pointer to the IIO channel that corresponds to this event. + * @param event A pointer to an iio_event structure + * @param dev A pointer to the iio_device structure that delivered the event + * @param diff If set, retrieve the differential channel + * @return On success, a pointer to an iio_channel structure + * @return On error, NULL is returned */ +__api __check_ret const struct iio_channel * +iio_event_get_channel(const struct iio_event *event, + const struct iio_device *dev, bool diff); + +/** + * @brief Create an events stream for the given IIO device. + * @param dev A pointer to an iio_device structure + * @return On success, a pointer to an iio_event_stream structure + * @return On failure, a pointer-encoded error is returned */ +__api __check_ret struct iio_event_stream * +iio_device_create_event_stream(const struct iio_device *dev); + +/** + * @brief Destroy the given event stream. + * @param stream A pointer to an iio_event_stream structure */ +__api void iio_event_stream_destroy(struct iio_event_stream *stream); + +/** + * @brief Read an event from the event stream. + * @param stream A pointer to an iio_event_stream structure + * @param out_event An pointer to an iio_event structure, that will be filled by + * this function. + * @param nonblock if True, the operation won't block and return -EBUSY if + * there is currently no event in the queue. + * @return On success, 0 is returned + * @return On error, a negative errno code is returned. + * + * NOTE: it is possible to stop a blocking call of iio_event_stream_read + * by calling iio_event_stream_destroy in a different thread or signal handler. + * In that case, iio_event_stream_read will return -EINTR. */ +__api int iio_event_stream_read(struct iio_event_stream *stream, + struct iio_event *out_event, + bool nonblock); + /** @} *//* ------------------------------------------------------------------*/ /* ------------------------- Low-level functions -----------------------------*/ /** @defgroup Debug Debug and low-level functions From ba393cab611ca3c37c7dfb4bbb16fd5da52ac256 Mon Sep 17 00:00:00 2001 From: Paul Cercueil Date: Tue, 24 Oct 2023 15:08:13 +0200 Subject: [PATCH 03/16] local: Keep file descriptor of dev node opened Keep a file descriptor to the /dev/iio:deviceX node opened. When creating a buffer, this file descriptor will be duplicated. This ensures that we can create a IIO events file descriptor without having to create a IIO buffer first, and without the problem of trying to open the main FD twice. Signed-off-by: Paul Cercueil --- local.c | 70 +++++++++++++++++++++++++++++++++++++++++++++------------ 1 file changed, 56 insertions(+), 14 deletions(-) diff --git a/local.c b/local.c index ffc183293..079904f18 100644 --- a/local.c +++ b/local.c @@ -47,6 +47,10 @@ local_create_context(const struct iio_context_params *params, const char *args); static int local_context_scan(const struct iio_context_params *params, struct iio_scan *ctx, const char *args); +struct iio_device_pdata { + int fd; +}; + struct iio_channel_pdata { char *enable_fn; struct iio_attr_list protected; @@ -91,6 +95,8 @@ static void local_free_pdata(struct iio_device *device) for (i = 0; i < device->nb_channels; i++) local_free_channel_pdata(device->channels[i]); + + free(device->pdata); } static void local_shutdown(struct iio_context *ctx) @@ -101,6 +107,8 @@ static void local_shutdown(struct iio_context *ctx) for (i = 0; i < iio_context_get_devices_count(ctx); i++) { struct iio_device *dev = iio_context_get_device(ctx, i); + if (dev->pdata && dev->pdata->fd >= 0) + close(dev->pdata->fd); local_free_pdata(dev); } } @@ -1369,10 +1377,12 @@ local_create_buffer(const struct iio_device *dev, unsigned int idx, { struct iio_buffer_pdata *pdata; const struct iio_channel *chn; - char buf[1024]; - int err, fd, cancel_fd, new_fd = idx; + int err, cancel_fd, fd = idx; unsigned int i; + if (dev->pdata->fd < 0) + return iio_ptr(dev->pdata->fd); + pdata = zalloc(sizeof(*pdata)); if (!pdata) return iio_ptr(-ENOMEM); @@ -1392,20 +1402,17 @@ local_create_buffer(const struct iio_device *dev, unsigned int idx, goto err_free_mmap_pdata; } - iio_snprintf(buf, sizeof(buf), "/dev/%s", dev->id); - fd = open(buf, O_RDWR | O_CLOEXEC | O_NONBLOCK); - if (fd == -1) { - err = -errno; - goto err_close_eventfd; - } - - err = ioctl_nointr(fd, IIO_BUFFER_GET_FD_IOCTL, &new_fd); + err = ioctl_nointr(dev->pdata->fd, IIO_BUFFER_GET_FD_IOCTL, &fd); if (err == 0) { - close(fd); - fd = new_fd; pdata->multi_buffer = true; - } else if (idx > 0) { - goto err_close; + } else if (idx == 0) { + fd = dup(dev->pdata->fd); + if (fd == -1) { + err = -errno; + goto err_close_eventfd; + } + } else { + goto err_close_eventfd; } pdata->cancel_fd = cancel_fd; @@ -1562,6 +1569,37 @@ const struct iio_backend iio_local_backend = { .default_timeout_ms = 1000, }; +static int local_open_buffer(const struct iio_device *dev) +{ + char buf[1024]; + int fd; + + iio_snprintf(buf, sizeof(buf), "/dev/%s", dev->id); + fd = open(buf, O_RDWR | O_CLOEXEC | O_NONBLOCK); + if (fd == -1) + return -errno; + + return fd; +} + +static int init_devices(struct iio_context *ctx) +{ + struct iio_device *dev; + unsigned int i; + + for (i = 0; i < ctx->nb_devices; i++) { + dev = ctx->devices[i]; + + dev->pdata = malloc(sizeof(*dev->pdata)); + if (!dev->pdata) + return -ENOMEM; + + dev->pdata->fd = local_open_buffer(dev); + } + + return 0; +} + static int populate_context_attrs(struct iio_context *ctx, const char *file) { struct INI *ini; @@ -1677,6 +1715,10 @@ local_create_context(const struct iio_context_params *params, const char *args) if (ret < 0) goto err_context_destroy; + ret = init_devices(ctx); + if (ret < 0) + goto err_context_destroy; + return ctx; err_context_destroy: From 600735cd8f4805e0775b08ff37bba669daf90a67 Mon Sep 17 00:00:00 2001 From: Paul Cercueil Date: Fri, 3 Nov 2023 15:10:57 +0100 Subject: [PATCH 04/16] local: Populate channels with new IIO events attributes This allows applications to control the conditions under which IIO events will trigger. Signed-off-by: Paul Cercueil --- local.c | 50 ++++++++++++++++++++++++++++++++++++++------------ 1 file changed, 38 insertions(+), 12 deletions(-) diff --git a/local.c b/local.c index 079904f18..ebaeb15b2 100644 --- a/local.c +++ b/local.c @@ -1114,31 +1114,36 @@ static int add_buffer_attr(void *d, const char *path) } static int add_attr_or_channel_helper(struct iio_device *dev, - const char *path, bool dir_is_scan_elements) + const char *path, const char *prefix, + bool dir_is_scan_elements) { char buf[1024]; const char *name = strrchr(path, '/') + 1; - if (dir_is_scan_elements) { - iio_snprintf(buf, sizeof(buf), "scan_elements/%s", name); - path = buf; - } else { - if (!is_channel(dev, name, true)) - return add_attr_to_device(dev, name); - path = name; - } + if (!dir_is_scan_elements && !is_channel(dev, name, true)) + return add_attr_to_device(dev, name); + + iio_snprintf(buf, sizeof(buf), "%s%s", prefix, name); - return add_channel(dev, name, path, dir_is_scan_elements); + return add_channel(dev, name, buf, dir_is_scan_elements); } static int add_attr_or_channel(void *d, const char *path) { - return add_attr_or_channel_helper((struct iio_device *) d, path, false); + return add_attr_or_channel_helper((struct iio_device *) d, + path, "", false); +} + +static int add_event(void *d, const char *path) +{ + return add_attr_or_channel_helper((struct iio_device *) d, + path, "events/", false); } static int add_scan_element(void *d, const char *path) { - return add_attr_or_channel_helper((struct iio_device *) d, path, true); + return add_attr_or_channel_helper((struct iio_device *) d, + path, "scan_elements/", true); } static int foreach_in_dir(const struct iio_context *ctx, @@ -1227,6 +1232,23 @@ static int add_buffer_attributes(struct iio_device *dev, const char *devpath) return 0; } +static int add_events(struct iio_device *dev, const char *devpath) +{ + const struct iio_context *ctx = iio_device_get_context(dev); + struct stat st; + char buf[1024]; + + iio_snprintf(buf, sizeof(buf), "%s/events", devpath); + + if (!stat(buf, &st) && S_ISDIR(st.st_mode)) { + int ret = foreach_in_dir(ctx, dev, buf, false, add_event); + if (ret < 0) + return ret; + } + + return 0; +} + static int create_device(void *d, const char *path) { unsigned int i; @@ -1251,6 +1273,10 @@ static int create_device(void *d, const char *path) if (ret < 0) goto err_free_device; + ret = add_events(dev, path); + if (ret < 0) + goto err_free_scan_elements; + ret = add_scan_elements(dev, path); if (ret < 0) goto err_free_scan_elements; From 7ec1936c3c0a321af30be315c57dab012283cb19 Mon Sep 17 00:00:00 2001 From: Paul Cercueil Date: Tue, 24 Oct 2023 15:11:47 +0200 Subject: [PATCH 05/16] local: Add callbacks to support events Add the necesary callbacks to create an events stream, and read events with the local backend. Signed-off-by: Paul Cercueil --- local.c | 102 ++++++++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 99 insertions(+), 3 deletions(-) diff --git a/local.c b/local.c index ebaeb15b2..89b337684 100644 --- a/local.c +++ b/local.c @@ -35,6 +35,7 @@ #define NB_BLOCKS 4 +#define IIO_GET_EVENT_FD_IOCTL _IOR('i', 0x90, int) #define IIO_BUFFER_GET_FD_IOCTL _IOWR('i', 0x91, int) /* Forward declarations */ @@ -51,6 +52,11 @@ struct iio_device_pdata { int fd; }; +struct iio_event_stream_pdata { + const struct iio_device *dev; + int fd, cancel_fd; +}; + struct iio_channel_pdata { char *enable_fn; struct iio_attr_list protected; @@ -1337,19 +1343,23 @@ static int add_debug(void *d, const char *path) return foreach_in_dir(ctx, dev, path, false, add_debug_attr); } -static void local_cancel_buffer(struct iio_buffer_pdata *pdata) +static void local_signal_cancel_fd(const struct iio_device *dev, int fd) { - const struct iio_device *dev = pdata->dev; uint64_t event = 1; int ret; - ret = write(pdata->cancel_fd, &event, sizeof(event)); + ret = write(fd, &event, sizeof(event)); if (ret == -1) { /* If this happens something went very seriously wrong */ dev_perror(dev, -errno, "Unable to signal cancellation event"); } } +static void local_cancel_buffer(struct iio_buffer_pdata *pdata) +{ + local_signal_cancel_fd(pdata->dev, pdata->cancel_fd); +} + static char * local_get_description(const struct iio_context *ctx) { char *description; @@ -1564,6 +1574,88 @@ int local_dequeue_block(struct iio_block_pdata *pdata, bool nonblock) return -ENOSYS; } +static struct iio_event_stream_pdata * +local_open_events_fd(const struct iio_device *dev) +{ + struct iio_event_stream_pdata *pdata; + int err; + + if (dev->pdata->fd < 0) + return iio_ptr(dev->pdata->fd); + + pdata = zalloc(sizeof(*pdata)); + if (!pdata) + return iio_ptr(-ENOMEM); + + pdata->dev = dev; + + pdata->cancel_fd = eventfd(0, EFD_CLOEXEC | EFD_NONBLOCK); + if (pdata->cancel_fd == -1) { + err = -errno; + free(pdata); + return iio_ptr(err); + } + + err = ioctl_nointr(dev->pdata->fd, IIO_GET_EVENT_FD_IOCTL, &pdata->fd); + if (err < 0) { + close(pdata->cancel_fd); + free(pdata); + return iio_ptr(err); + } + + return pdata; +} + +static void local_close_events_fd(struct iio_event_stream_pdata *pdata) +{ + local_signal_cancel_fd(pdata->dev, pdata->cancel_fd); + close(pdata->cancel_fd); + + close(pdata->fd); + free(pdata); +} + +static int local_read_event(struct iio_event_stream_pdata *pdata, + struct iio_event *out_event, bool nonblock) +{ + struct pollfd pollfd[2] = { + { + .fd = pdata->fd, + .events = POLLIN, + }, { + .fd = pdata->cancel_fd, + .events = POLLIN, + } + }; + int ret, timeout_rel = nonblock ? 0 : -1; + + do { + ret = poll(pollfd, 2, timeout_rel); + } while (ret == -1 && errno == EINTR); + + if ((pollfd[1].revents & (POLLIN | POLLNVAL))) + return -EINTR; + + if (ret < 0) + return -errno; + + if (!ret) { + /* No event available */ + return nonblock ? -EAGAIN : -EBUSY; + } + + + if (!(pollfd[0].revents & POLLIN)) { + /* Unknown error */ + return -EIO; + } + + if (read(pdata->fd, out_event, sizeof(*out_event)) < 0) /* Flawfinder: ignore */ + return -errno; + + return 0; +} + static const struct iio_backend_ops local_ops = { .scan = local_context_scan, .create = local_create_context, @@ -1585,6 +1677,10 @@ static const struct iio_backend_ops local_ops = { .readbuf = local_readbuf, .writebuf = local_writebuf, + + .open_ev = local_open_events_fd, + .close_ev = local_close_events_fd, + .read_ev = local_read_event, }; const struct iio_backend iio_local_backend = { From 81cf38f631ae37f5d0aa06516b8cdfa0560ee915 Mon Sep 17 00:00:00 2001 From: Paul Cercueil Date: Mon, 6 Nov 2023 10:42:58 +0100 Subject: [PATCH 06/16] iiod-responder: Add function iiod_io_set_timeout() This can be used to specify the timeout value for a specific communication pipe. Signed-off-by: Paul Cercueil --- iiod-responder.c | 6 ++++++ iiod-responder.h | 1 + 2 files changed, 7 insertions(+) diff --git a/iiod-responder.c b/iiod-responder.c index c16ac9458..1bd6e0c63 100644 --- a/iiod-responder.c +++ b/iiod-responder.c @@ -612,6 +612,12 @@ iiod_responder_set_timeout(struct iiod_responder *priv, unsigned int timeout_ms) priv->default_io->timeout_ms = timeout_ms; } +void +iiod_io_set_timeout(struct iiod_io *io, unsigned int timeout_ms) +{ + io->timeout_ms = timeout_ms; +} + struct iiod_responder * iiod_responder_create(const struct iiod_responder_ops *ops, void *d) { diff --git a/iiod-responder.h b/iiod-responder.h index 14ac62fe4..b0b2893e1 100644 --- a/iiod-responder.h +++ b/iiod-responder.h @@ -81,6 +81,7 @@ void iiod_responder_destroy(struct iiod_responder *responder); /* Set the timeout for I/O operations (default is 0 == infinite) */ void iiod_responder_set_timeout(struct iiod_responder *priv, unsigned int timeout_ms); +void iiod_io_set_timeout(struct iiod_io *io, unsigned int timeout_ms); /* Read the current value of the micro-second counter */ uint64_t iiod_responder_read_counter_us(void); From 5be9c99a97ad6d57f0395b49e72d486f2fe4d514 Mon Sep 17 00:00:00 2001 From: Paul Cercueil Date: Thu, 2 Nov 2023 13:36:44 +0100 Subject: [PATCH 07/16] iiod-client: Add functions to support events Add three new functions tailored for IIO events support. These functions allow to create an IIO events stream, read from it, and close it. The open / close functions use the regular I/O pipe of the responder, while the event read function will use an I/O pipe dedicated to the event stream. This allows the call to be blocking, only returning when an event has been read, without disturbing the command flow. Note that there is no listener thread that would continuously poll the IIO events; those are only read when iiod_client_read_event() is called. This is fine, as IIO events are buffered anyway on the Linux kernel side, so events are not lost. Signed-off-by: Paul Cercueil --- iiod-client.c | 143 ++++++++++++++++++++++++++++++++++++++ iiod-responder.h | 4 ++ include/iio/iiod-client.h | 9 +++ 3 files changed, 156 insertions(+) diff --git a/iiod-client.c b/iiod-client.c index 72b92e1f1..dfa31f5af 100644 --- a/iiod-client.c +++ b/iiod-client.c @@ -30,6 +30,9 @@ struct iiod_client { struct iio_mutex *lock; struct iiod_responder *responder; + + /* TODO: atomic? */ + uint16_t next_evstream_idx; }; struct iiod_client_io { @@ -69,6 +72,14 @@ struct iio_block_pdata { bool enqueued; }; +struct iio_event_stream_pdata { + struct iiod_client *client; + const struct iio_device *dev; + struct iio_event event; + struct iiod_io *io; + uint16_t idx; +}; + void iiod_client_mutex_lock(struct iiod_client *client) { iio_mutex_lock(client->lock); @@ -263,6 +274,7 @@ struct iiod_client * iiod_client_new(const struct iio_context_params *params, client->ops = ops; client->desc = desc; client->responder = NULL; + client->next_evstream_idx = (uint16_t)-1; err = iiod_client_enable_binary(client); if (err) @@ -1693,3 +1705,134 @@ bool iiod_client_uses_binary_interface(const struct iiod_client *client) { return !!client->responder; } + +static int iiod_client_request_event_read(struct iio_event_stream_pdata *pdata) +{ + struct iiod_command cmd = { + .op = IIOD_OP_READ_EVENT, + .dev = (uint8_t) iio_device_get_index(pdata->dev), + }; + struct iiod_buf buf = { + .ptr = &pdata->event, + .size = sizeof(pdata->event), + }; + int err; + + err = iiod_io_get_response_async(pdata->io, &buf, 1); + if (err) + return err; + + err = iiod_io_send_command(pdata->io, &cmd, NULL, 0); + if (err) + return err; + + return 0; +} + +struct iio_event_stream_pdata * +iiod_client_open_event_stream(struct iiod_client *client, + const struct iio_device *dev) +{ + struct iio_event_stream_pdata *pdata; + uint16_t idx = client->next_evstream_idx--; + struct iiod_command cmd = { + .op = IIOD_OP_CREATE_EVSTREAM, + }; + int err; + + if (!iiod_client_uses_binary_interface(client)) + return iio_ptr(-ENOSYS); + + pdata = zalloc(sizeof(*pdata)); + if (!pdata) + return iio_ptr(-ENOMEM); + + pdata->dev = dev; + pdata->client = client; + pdata->idx = idx; + + pdata->io = iiod_responder_create_io(client->responder, idx); + err = iio_err(pdata->io); + if (err) + goto err_free_pdata; + + cmd.dev = (uint8_t) iio_device_get_index(dev); + cmd.code = idx; + + err = iiod_io_exec_simple_command(pdata->io, &cmd); + if (err) + goto err_destroy_io; + + /* Use infinite timeout for blocking events reads, as those only make + * sense when running in a thread, and we don't want them to return + * without any event. */ + iiod_io_set_timeout(pdata->io, 0); + + /* Send a request to read an event. This bootstraps the event reading + * mechanism. */ + err = iiod_client_request_event_read(pdata); + if (err) { + iiod_client_close_event_stream(pdata); + return iio_ptr(err); + } + + return pdata; + +err_destroy_io: + iiod_io_cancel(pdata->io); + iiod_io_unref(pdata->io); +err_free_pdata: + free(pdata); + return iio_ptr(err); +} + +void iiod_client_close_event_stream(struct iio_event_stream_pdata *pdata) +{ + struct iiod_command cmd = { + .op = IIOD_OP_FREE_EVSTREAM, + .dev = (uint8_t) iio_device_get_index(pdata->dev), + .code = pdata->idx, + }; + struct iiod_io *io; + + /* Close the event stream using the default I/O, since there may + * be a blocking event read operation pending on the I/O pipe dedicated + * to this event stream. */ + io = iiod_responder_get_default_io(pdata->client->responder); + + iiod_io_exec_simple_command(io, &cmd); + + iiod_io_cancel(pdata->io); + iiod_io_unref(pdata->io); + free(pdata); +} + +int iiod_client_read_event(struct iio_event_stream_pdata *pdata, + struct iio_event *out_event, + bool nonblock) +{ + struct iiod_io *io = pdata->io; + int ret = 0; + + /* Get a reference to the I/O stream, so that it's not freed while + * we're using it */ + iiod_io_ref(io); + + if (!nonblock) + ret = (int)iiod_io_wait_for_response(io); + else if (!iiod_io_has_response(io)) + ret = -EAGAIN; + + if (ret >= 0) { + /* We have a response from the last command. + * Copy the event to the output buffer. */ + *out_event = pdata->event; + + /* Request a new event. */ + ret = iiod_client_request_event_read(pdata); + } + + iiod_io_unref(io); + + return ret; +} diff --git a/iiod-responder.h b/iiod-responder.h index b0b2893e1..42c8c1221 100644 --- a/iiod-responder.h +++ b/iiod-responder.h @@ -50,6 +50,10 @@ enum iiod_opcode { IIOD_OP_TRANSFER_BLOCK, IIOD_OP_ENQUEUE_BLOCK_CYCLIC, + IIOD_OP_CREATE_EVSTREAM, + IIOD_OP_FREE_EVSTREAM, + IIOD_OP_READ_EVENT, + IIOD_NB_OPCODES, }; diff --git a/include/iio/iiod-client.h b/include/iio/iiod-client.h index 30be7d2a0..7582f7c60 100644 --- a/include/iio/iiod-client.h +++ b/include/iio/iiod-client.h @@ -16,6 +16,7 @@ struct iiod_client; struct iiod_client_io; struct iiod_client_pdata; +struct iio_event_stream_pdata; struct iiod_client_ops { ssize_t (*write)(struct iiod_client_pdata *desc, @@ -94,6 +95,14 @@ __api ssize_t iiod_client_readbuf(struct iiod_client_buffer_pdata *pdata, __api ssize_t iiod_client_writebuf(struct iiod_client_buffer_pdata *pdata, const void *src, size_t len); +struct iio_event_stream_pdata * +iiod_client_open_event_stream(struct iiod_client *client, + const struct iio_device *dev); +void iiod_client_close_event_stream(struct iio_event_stream_pdata *pdata); +int iiod_client_read_event(struct iio_event_stream_pdata *pdata, + struct iio_event *out_event, + bool nonblock); + #undef __api #endif /* _IIOD_CLIENT_H */ From a55c644a8645f4bc2f8a321eae7d3c40ebca731e Mon Sep 17 00:00:00 2001 From: Paul Cercueil Date: Mon, 6 Nov 2023 10:57:58 +0100 Subject: [PATCH 08/16] iiod: Constify iio_device pointer There is no need to have a mutable pointer there, use a const pointer instead. Signed-off-by: Paul Cercueil --- iiod/ops.h | 2 +- iiod/responder.c | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/iiod/ops.h b/iiod/ops.h index b274205b5..115419e00 100644 --- a/iiod/ops.h +++ b/iiod/ops.h @@ -69,7 +69,7 @@ struct block_entry { struct buffer_entry { SLIST_ENTRY(buffer_entry) entry; struct parser_pdata *pdata; - struct iio_device *dev; + const struct iio_device *dev; struct iio_buffer *buf; struct iio_task *enqueue_task, *dequeue_task; uint32_t *words; diff --git a/iiod/responder.c b/iiod/responder.c index 561190503..160c6db6a 100644 --- a/iiod/responder.c +++ b/iiod/responder.c @@ -329,7 +329,7 @@ static void handle_create_buffer(struct parser_pdata *pdata, struct iiod_io *io = iiod_command_get_default_io(cmd_data); const struct iio_context *ctx = pdata->ctx; struct iio_channels_mask *mask; - struct iio_device *dev; + const struct iio_device *dev; struct iio_channel *chn; struct buffer_entry *entry; struct iio_buffer *buf; From 29efc2a5bfa99e51472f6b6433400d44eb13c890 Mon Sep 17 00:00:00 2001 From: Paul Cercueil Date: Mon, 6 Nov 2023 10:59:50 +0100 Subject: [PATCH 09/16] iiod: Avoid uninitialized pointer deference With IIOD's current responder design, we can only answer to a command if we know which I/O pipe is used for answering. This means that if we cannot obtain that information, we cannot answer. In that case, Signed-off-by: Paul Cercueil --- iiod/responder.c | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/iiod/responder.c b/iiod/responder.c index 160c6db6a..c0c00c6e2 100644 --- a/iiod/responder.c +++ b/iiod/responder.c @@ -719,13 +719,17 @@ static void handle_transfer_block(struct parser_pdata *pdata, buf = get_iio_buffer(pdata, cmd, &entry); ret = iio_err(buf); - if (ret) - goto out_send_response; + if (ret) { + IIO_ERROR("handle_transfer_block: Could not find IIO buffer\n"); + return; + } block = get_iio_block(pdata, entry, cmd, &block_entry); ret = iio_err(block); - if (ret) - goto out_send_response; + if (ret) { + IIO_ERROR("handle_transfer_block: Could not find IIO block\n"); + return; + } readbuf.ptr = &bytes_used; readbuf.size = 8; From 7d8851c9583cc3c8d5b041fc9e0405d78ba46275 Mon Sep 17 00:00:00 2001 From: Paul Cercueil Date: Thu, 2 Nov 2023 14:34:36 +0100 Subject: [PATCH 10/16] iiod: Add support for events Add support for the IIOD_OP_CREATE_EVSTREAM, IIOD_OP_FREE_EVSTREAM, AND IIOD_OP_READ_EVENT commands. These can be used to open and close event streams, and read events from an event stream. Signed-off-by: Paul Cercueil --- iiod-client.c | 4 +- iiod/ops.h | 10 +++ iiod/responder.c | 200 +++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 211 insertions(+), 3 deletions(-) diff --git a/iiod-client.c b/iiod-client.c index dfa31f5af..5039f0ace 100644 --- a/iiod-client.c +++ b/iiod-client.c @@ -1737,6 +1737,7 @@ iiod_client_open_event_stream(struct iiod_client *client, uint16_t idx = client->next_evstream_idx--; struct iiod_command cmd = { .op = IIOD_OP_CREATE_EVSTREAM, + .dev = (uint8_t) iio_device_get_index(dev), }; int err; @@ -1756,9 +1757,6 @@ iiod_client_open_event_stream(struct iiod_client *client, if (err) goto err_free_pdata; - cmd.dev = (uint8_t) iio_device_get_index(dev); - cmd.code = idx; - err = iiod_io_exec_simple_command(pdata->io, &cmd); if (err) goto err_destroy_io; diff --git a/iiod/ops.h b/iiod/ops.h index 115419e00..b02db5566 100644 --- a/iiod/ops.h +++ b/iiod/ops.h @@ -79,6 +79,16 @@ struct buffer_entry { pthread_mutex_t lock; }; +struct evstream_entry { + SLIST_ENTRY(evstream_entry) entry; + struct parser_pdata *pdata; + const struct iio_device *dev; + struct iio_event_stream *stream; + struct iio_task *task; + struct iiod_io *io; + uint16_t client_id; +}; + struct parser_pdata { struct iio_context *ctx; bool stop, binary, verbose; diff --git a/iiod/responder.c b/iiod/responder.c index c0c00c6e2..276c23998 100644 --- a/iiod/responder.c +++ b/iiod/responder.c @@ -31,6 +31,11 @@ static SLIST_HEAD(BufferList, buffer_entry) bufferlist; /* Protect bufferlist from parallel access */ static pthread_mutex_t buflist_lock = PTHREAD_MUTEX_INITIALIZER; +static SLIST_HEAD(EventStreamList, evstream_entry) evlist; + +/* Protect evlist from parallel access */ +static pthread_mutex_t evlist_lock = PTHREAD_MUTEX_INITIALIZER; + static void free_block_entry(struct block_entry *entry) { iiod_io_cancel(entry->io); @@ -769,6 +774,182 @@ static void handle_transfer_block(struct parser_pdata *pdata, iiod_io_send_response_code(block_entry->io, ret); } +static int evstream_read(void *priv, void *d) +{ + struct evstream_entry *entry = priv; + struct iio_event event; + struct iiod_buf buf = { + .ptr = &event, + .size = sizeof(event), + }; + int ret; + + ret = iio_event_stream_read(entry->stream, &event, false); + if (ret < 0) + return iiod_io_send_response_code(entry->io, ret); + + return iiod_io_send_response(entry->io, sizeof(event), &buf, 1); +} + +static void handle_create_evstream(struct parser_pdata *pdata, + const struct iiod_command *cmd, + struct iiod_command_data *cmd_data) +{ + const struct iio_context *ctx = pdata->ctx; + const struct iio_device *dev; + struct evstream_entry *entry; + struct iiod_io *io; + int ret = -EINVAL; + + io = iiod_command_create_io(cmd, cmd_data); + ret = iio_err(io); + if (ret) { + /* TODO: How to handle this error? */ + return; + } + + dev = iio_context_get_device(ctx, cmd->dev); + if (!dev) + goto out_send_response; + + entry = zalloc(sizeof(*entry)); + if (!entry) { + ret = -ENOMEM; + goto out_send_response; + } + + entry->io = io; + entry->dev = dev; + entry->client_id = cmd->client_id; + entry->pdata = pdata; + + entry->stream = iio_device_create_event_stream(dev); + ret = iio_err(entry->stream); + if (ret) { + free(entry); + goto out_send_response; + } + + entry->task = iio_task_create(evstream_read, entry, + "evstream-read-thd"); + ret = iio_err(entry->task); + if (ret) { + iio_event_stream_destroy(entry->stream); + free(entry); + goto out_send_response; + } + + iio_task_start(entry->task); + + /* Keep a reference to the iiod_io until the evstream is freed. */ + iiod_io_ref(io); + + pthread_mutex_lock(&evlist_lock); + SLIST_INSERT_HEAD(&evlist, entry, entry); + pthread_mutex_unlock(&evlist_lock); + +out_send_response: + iiod_io_send_response_code(io, ret); + iiod_io_unref(io); +} + +static struct evstream_entry * get_evstream(struct parser_pdata *pdata, + const struct iiod_command *cmd, + uint16_t idx, bool remove) +{ + const struct iio_context *ctx = pdata->ctx; + const struct iio_device *dev; + struct evstream_entry *entry; + + dev = iio_context_get_device(ctx, cmd->dev); + if (!dev) + return NULL; + + pthread_mutex_lock(&evlist_lock); + SLIST_FOREACH(entry, &evlist, entry) { + if (entry->client_id == idx + && entry->dev == dev + && entry->pdata == pdata) { + break; + } + } + + if (entry && remove) + SLIST_REMOVE(&evlist, entry, evstream_entry, entry); + + pthread_mutex_unlock(&evlist_lock); + + return entry; +} + +static void free_evstream(struct evstream_entry *entry) +{ + + iio_event_stream_destroy(entry->stream); + iiod_io_cancel(entry->io); + + iio_task_stop(entry->task); + iio_task_destroy(entry->task); + + iiod_io_unref(entry->io); + free(entry); +} + +static void handle_free_evstream(struct parser_pdata *pdata, + const struct iiod_command *cmd, + struct iiod_command_data *cmd_data) +{ + struct evstream_entry *entry, *each; + struct iiod_io *io = iiod_command_get_default_io(cmd_data); + int ret = 0; + + entry = get_evstream(pdata, cmd, cmd->code, true); + if (!entry) { + ret = -EBADF; + goto out_send_response; + } + + free_evstream(entry); + +out_send_response: + iiod_io_send_response_code(io, ret); +} + +static void handle_read_event(struct parser_pdata *pdata, + const struct iiod_command *cmd, + struct iiod_command_data *cmd_data) +{ + struct evstream_entry *entry; + int ret; + + entry = get_evstream(pdata, cmd, cmd->client_id, false); + if (!entry) { + /* TODO: How to handle this error? */ + return; + } + + if (cmd->code) { + struct iio_event event; + struct iiod_buf buf = { + .ptr = &event, + .size = sizeof(event), + }; + + /* Nonblock mode: run iio_event_stream_read() inline, + * and respond here. */ + ret = iio_event_stream_read(entry->stream, &event, true); + if (ret < 0) + iiod_io_send_response_code(entry->io, ret); + else + iiod_io_send_response(entry->io, sizeof(event), &buf, 1); + } else { + /* Blocking mode: defer the answer. */ + ret = iio_task_enqueue_autoclear(entry->task, entry); + if (ret) + iiod_io_send_response_code(entry->io, ret); + } +} + typedef void (*iiod_opcode_fn)(struct parser_pdata *, const struct iiod_command *, struct iiod_command_data *cmd_data); @@ -796,6 +977,10 @@ static const iiod_opcode_fn iiod_op_functions[] = { [IIOD_OP_FREE_BLOCK] = handle_free_block, [IIOD_OP_TRANSFER_BLOCK] = handle_transfer_block, [IIOD_OP_ENQUEUE_BLOCK_CYCLIC] = handle_transfer_block, + + [IIOD_OP_CREATE_EVSTREAM] = handle_create_evstream, + [IIOD_OP_FREE_EVSTREAM] = handle_free_evstream, + [IIOD_OP_READ_EVENT] = handle_read_event, }; static int iiod_cmd(const struct iiod_command *cmd, @@ -832,6 +1017,7 @@ static const struct iiod_responder_ops iiod_responder_ops = { static void iiod_responder_free_resources(struct parser_pdata *pdata) { struct buffer_entry *buf_entry, *buf_next; + struct evstream_entry *ev_entry, *ev_next; pthread_mutex_lock(&buflist_lock); @@ -847,6 +1033,20 @@ static void iiod_responder_free_resources(struct parser_pdata *pdata) } pthread_mutex_unlock(&buflist_lock); + + pthread_mutex_lock(&evlist_lock); + + for (ev_entry = SLIST_FIRST(&evlist); ev_entry; ev_entry = ev_next) { + ev_next = SLIST_NEXT(ev_entry, entry); + + /* Only free the event streams that this client created */ + if (ev_entry->pdata == pdata) { + SLIST_REMOVE(&evlist, ev_entry, evstream_entry, entry); + free_evstream(ev_entry); + } + } + + pthread_mutex_unlock(&evlist_lock); } int binary_parse(struct parser_pdata *pdata) From 8bc719fd434b937ab603f625c329489db6f0967d Mon Sep 17 00:00:00 2001 From: Paul Cercueil Date: Mon, 6 Nov 2023 10:43:38 +0100 Subject: [PATCH 11/16] network: Add support for IIO events Provide the necesary callbacks to support IIO events with the network backend. Signed-off-by: Paul Cercueil --- network.c | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/network.c b/network.c index 4f97ac27e..d6482b25a 100644 --- a/network.c +++ b/network.c @@ -496,6 +496,15 @@ struct iio_block_pdata * network_create_block(struct iio_buffer_pdata *pdata, return iiod_client_create_block(pdata->pdata, size, data); } +static struct iio_event_stream_pdata * +network_open_events_fd(const struct iio_device *dev) +{ + const struct iio_context *ctx = iio_device_get_context(dev); + struct iio_context_pdata *pdata = iio_context_get_pdata(ctx); + + return iiod_client_open_event_stream(pdata->iiod_client, dev); +} + static const struct iio_backend_ops network_ops = { .scan = IF_ENABLED(HAVE_DNS_SD, dnssd_context_scan), .create = network_create_context, @@ -518,6 +527,10 @@ static const struct iio_backend_ops network_ops = { .free_block = iiod_client_free_block, .enqueue_block = iiod_client_enqueue_block, .dequeue_block = iiod_client_dequeue_block, + + .open_ev = network_open_events_fd, + .close_ev = iiod_client_close_event_stream, + .read_ev = iiod_client_read_event, }; __api_export_if(WITH_NETWORK_BACKEND_DYNAMIC) From 8270801bf0f1c8e5f8f7e1fed97c284ada7501e5 Mon Sep 17 00:00:00 2001 From: Paul Cercueil Date: Mon, 6 Nov 2023 11:09:22 +0100 Subject: [PATCH 12/16] usb: Add support for IIO events Provide the necesary callbacks to support IIO events with the USB backend. Signed-off-by: Paul Cercueil --- usb.c | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/usb.c b/usb.c index b9baf97f6..d8e255909 100644 --- a/usb.c +++ b/usb.c @@ -501,6 +501,15 @@ usb_create_block(struct iio_buffer_pdata *pdata, size_t size, void **data) return iiod_client_create_block(pdata->pdata, size, data); } +static struct iio_event_stream_pdata * +usb_open_events_fd(const struct iio_device *dev) +{ + const struct iio_context *ctx = iio_device_get_context(dev); + struct iio_context_pdata *pdata = iio_context_get_pdata(ctx); + + return iiod_client_open_event_stream(pdata->io_ctx.iiod_client, dev); +} + static const struct iio_backend_ops usb_ops = { .scan = usb_context_scan, .create = usb_create_context_from_args, @@ -523,6 +532,10 @@ static const struct iio_backend_ops usb_ops = { .free_block = iiod_client_free_block, .enqueue_block = iiod_client_enqueue_block, .dequeue_block = iiod_client_dequeue_block, + + .open_ev = usb_open_events_fd, + .close_ev = iiod_client_close_event_stream, + .read_ev = iiod_client_read_event, }; __api_export_if(WITH_USB_BACKEND_DYNAMIC) From 9916f46e1332b4be4d9b99a35a1b4378c1e11c00 Mon Sep 17 00:00:00 2001 From: Paul Cercueil Date: Mon, 6 Nov 2023 11:09:51 +0100 Subject: [PATCH 13/16] serial: Add support for IIO events Provide the necesary callbacks to support IIO events with the serial backend. Signed-off-by: Paul Cercueil --- serial.c | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/serial.c b/serial.c index 59178557a..3ce96f208 100644 --- a/serial.c +++ b/serial.c @@ -236,6 +236,15 @@ serial_create_block(struct iio_buffer_pdata *buf, size_t size, void **data) return iiod_client_create_block(buf->pdata, size, data); } +static struct iio_event_stream_pdata * +serial_open_events_fd(const struct iio_device *dev) +{ + const struct iio_context *ctx = iio_device_get_context(dev); + struct iio_context_pdata *pdata = iio_context_get_pdata(ctx); + + return iiod_client_open_event_stream(pdata->iiod_client, dev); +} + static const struct iio_backend_ops serial_ops = { .create = serial_create_context_from_args, .read_attr = serial_read_attr, @@ -252,6 +261,10 @@ static const struct iio_backend_ops serial_ops = { .free_block = iiod_client_free_block, .enqueue_block = iiod_client_enqueue_block, .dequeue_block = iiod_client_dequeue_block, + + .open_ev = serial_open_events_fd, + .close_ev = iiod_client_close_event_stream, + .read_ev = iiod_client_read_event, }; __api_export_if(WITH_SERIAL_BACKEND_DYNAMIC) From 29165c76bc1cc8090c4812b784e9f1c4a1a9949a Mon Sep 17 00:00:00 2001 From: Paul Cercueil Date: Wed, 8 Nov 2023 10:42:02 +0100 Subject: [PATCH 14/16] utils: Fix invalid parameters passed to fprintf The previous commit(s) reworking IIO attributes changed the type of the 'attr' variable from a const string to a 'struct iio_attr *'. It appears that the code was not properly updated at two different spots; update it so that it works properly. Signed-off-by: Paul Cercueil --- utils/gen_code.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/utils/gen_code.c b/utils/gen_code.c index 7cb10d245..ee82eb268 100644 --- a/utils/gen_code.c +++ b/utils/gen_code.c @@ -297,9 +297,11 @@ void gen_function(const char* prefix, const char* target, if (wbuf) { fprintf(fd, " # Write string to %s attribute:\n", prefix); if (!strcmp(prefix, "device") || !strcmp(prefix, "channel")) { - fprintf(fd, " %s.attrs[\"%s\"].value = str(\"%s\")\n", target, attr, wbuf); + fprintf(fd, " %s.attrs[\"%s\"].value = str(\"%s\")\n", target, + iio_attr_get_name(attr), wbuf); } else if (!strcmp(prefix, "device_debug")) { - fprintf(fd, " %s.debug_attrs[\"%s\"].value = str(\"%s\")\n", target, attr, wbuf); + fprintf(fd, " %s.debug_attrs[\"%s\"].value = str(\"%s\")\n", target, + iio_attr_get_name(attr), wbuf); } else { fprintf(fd, " # Write for %s / %s not implemented yet\n", prefix, target); } From 94fc3637acd8200a9b4bc8f157e18ade19fe79f0 Mon Sep 17 00:00:00 2001 From: Paul Cercueil Date: Mon, 6 Nov 2023 17:48:17 +0100 Subject: [PATCH 15/16] utils: Add 'iio_event' program This utility program can be used to monitor events sent by a given IIO device. Signed-off-by: Paul Cercueil --- utils/CMakeLists.txt | 2 +- utils/iio_event.c | 181 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 182 insertions(+), 1 deletion(-) create mode 100644 utils/iio_event.c diff --git a/utils/CMakeLists.txt b/utils/CMakeLists.txt index 9e548335e..41a327700 100644 --- a/utils/CMakeLists.txt +++ b/utils/CMakeLists.txt @@ -1,6 +1,6 @@ cmake_minimum_required(VERSION 3.10) -set(IIO_TESTS_TARGETS iio_genxml iio_info iio_attr iio_rwdev iio_reg) +set(IIO_TESTS_TARGETS iio_genxml iio_info iio_attr iio_rwdev iio_reg iio_event) if (PTHREAD_LIBRARIES OR ANDROID) LIST(APPEND IIO_TESTS_TARGETS iio_stresstest) endif() diff --git a/utils/iio_event.c b/utils/iio_event.c new file mode 100644 index 000000000..0001f9d26 --- /dev/null +++ b/utils/iio_event.c @@ -0,0 +1,181 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * iio_event - Part of the industrial I/O (IIO) utilities + * + * Copyright (C) 2023 Analog Devices, Inc. + * Author: Paul Cercueil + */ + +#include +#include +#include +#include +#include +#include + +#include "iio_common.h" + +#define MY_NAME "iio_event" + +static struct iio_event_stream *stream; +static int exit_code = EXIT_FAILURE; + +static const struct option options[] = { + {0, 0, 0, 0}, +}; + +static const char *options_descriptions[] = { + "\n" +}; + +static void quit_stream(int sig) +{ + exit_code = sig; + + if (stream) + iio_event_stream_destroy(stream); + stream = NULL; +} + +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", + [IIO_EV_TYPE_MAG_REFERENCED] = "mag_referenced", + [IIO_EV_TYPE_GESTURE] = "gesture", +}; + +static const char * const iio_ev_dir_text[] = { + [IIO_EV_DIR_EITHER] = "either", + [IIO_EV_DIR_RISING] = "rising", + [IIO_EV_DIR_FALLING] = "falling", + [IIO_EV_DIR_SINGLETAP] = "singletap", + [IIO_EV_DIR_DOUBLETAP] = "doubletap", +}; + +static void print_event(const struct iio_device *dev, + const struct iio_event *event) +{ + const struct iio_channel *chn; + enum iio_event_type type = iio_event_get_type(event); + enum iio_event_direction dir = iio_event_get_direction(event); + + printf("Event: time: %lld", event->timestamp); + + chn = iio_event_get_channel(event, dev, false); + if (chn) + printf(", channel(s): %s", iio_channel_get_id(chn)); + + chn = iio_event_get_channel(event, dev, true); + if (chn) + printf("-%s", iio_channel_get_id(chn)); + + printf(", evtype: %s", iio_ev_type_text[type]); + + if (dir != IIO_EV_DIR_NONE) + printf(", direction: %s", iio_ev_dir_text[dir]); + + printf("\n"); +} + +int main(int argc, char **argv) +{ + const struct iio_device *dev; + struct iio_context *ctx; + struct iio_event event; + struct option *opts; + char **argw, *name; + int c, ret; + + argw = dup_argv(MY_NAME, argc, argv); + if (!argw) + return EXIT_FAILURE; + + ctx = handle_common_opts(MY_NAME, argc, argw, "", + options, options_descriptions, &ret); + opts = add_common_options(options); + if (!opts) { + fprintf(stderr, "Failed to add common options\n"); + ret = EXIT_FAILURE; + goto out_ctx_destroy; + } + while ((c = getopt_long(argc, argw, "+" COMMON_OPTIONS, /* Flawfinder: ignore */ + opts, NULL)) != -1) { + switch (c) { + /* All these are handled in the common */ + case 'h': + case 'V': + case 'u': + case 'T': + break; + case 'S': + case 'a': + if (!optarg && argc > optind && argv[optind] != NULL + && argv[optind][0] != '-') + optind++; + break; + case '?': + printf("Unknown argument '%c'\n", c); + ret = EXIT_FAILURE; + free(opts); + goto out_ctx_destroy; + } + } + free(opts); + + if ((argc - optind) != 1) { + usage(MY_NAME, options, options_descriptions); + ret = EXIT_FAILURE; + goto out_ctx_destroy; + } + + name = cmn_strndup(argw[optind], NAME_MAX); + dev = iio_context_find_device(ctx, name); + free(name); + if (!dev) { + ctx_err(ctx, "Unable to find device\n"); + ret = EXIT_FAILURE; + goto out_ctx_destroy; + } + + stream = iio_device_create_event_stream(dev); + ret = iio_err(stream); + if (ret) { + dev_perror(dev, ret, "Unable to create event stream"); + ret = EXIT_FAILURE; + goto out_ctx_destroy; + } + + signal(SIGINT, quit_stream); + signal(SIGTERM, quit_stream); +#ifndef _WIN32 + signal(SIGHUP, quit_stream); +#endif + + for (;;) { + ret = iio_event_stream_read(stream, &event, false); + if (ret == -EINTR) + break; + + if (ret < 0) { + dev_perror(dev, ret, "Unable to read event"); + ret = EXIT_FAILURE; + goto out_stream_destroy; + } + + print_event(dev, &event); + } + +out_stream_destroy: + if (stream) + iio_event_stream_destroy(stream); +out_ctx_destroy: + if (ctx) + iio_context_destroy(ctx); +out_free_argw: + free_argw(argc, argw); + return ret; +} From b574558735f02552a19821e4ff881fcce690a4fb Mon Sep 17 00:00:00 2001 From: Paul Cercueil Date: Tue, 7 Nov 2023 16:21:04 +0100 Subject: [PATCH 16/16] python: Add support for IIO events Add support for reading IIO events in the Python bindings. This is done by calling iio.Device.event_stream() using a context manager, which will create an EventStream object, and then use the read() method: dev = ctx.devices["iio:device4"] with dev.event_stream() as evstream: ev = evstream.read() if ev is not None: print("Got event!") else: time.sleep(0.1) The read() method takes a 'nonblock' argument, which defaults to True. Python code can then poll for events regularly. Polling is better, because doing a blocking read does not give any guarantee about when the call will return. Signed-off-by: Paul Cercueil --- bindings/python/iio.py | 129 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 129 insertions(+) diff --git a/bindings/python/iio.py b/bindings/python/iio.py index aae654f9a..208799b01 100644 --- a/bindings/python/iio.py +++ b/bindings/python/iio.py @@ -17,7 +17,9 @@ Structure, c_char_p, c_uint, + c_uint64, c_int, + c_int64, c_long, c_longlong, c_size_t, @@ -34,6 +36,7 @@ memmove as _memmove, byref as _byref, ) +from contextlib import contextmanager from ctypes.util import find_library from enum import Enum from os import strerror as _strerror @@ -108,6 +111,8 @@ class _Channel(Structure): class _ChannelsMask(Structure): pass +class _EventStream(Structure): + pass class _Buffer(Structure): pass @@ -140,6 +145,47 @@ class DataFormat(Structure): ("offset", c_double), ] +class EventType(Enum): + """Represents the type of an IIO event.""" + + IIO_EV_TYPE_THRESH = 0 + IIO_EV_TYPE_MAG = 1 + IIO_EV_TYPE_ROC = 2 + IIO_EV_TYPE_THRESH_ADAPTIVE = 3 + IIO_EV_TYPE_MAG_ADAPTIVE = 4 + IIO_EV_TYPE_CHANGE = 5 + IIO_EV_TYPE_MAG_REFERENCED = 6 + IIO_EV_TYPE_GESTURE = 7 + +class EventDirection(Enum): + """Represents the direction of an IIO event.""" + + IIO_EV_DIR_EITHER = 0 + IIO_EV_DIR_RISING = 1 + IIO_EV_DIR_FALLING = 2 + IIO_EV_DIR_NONE = 3 + IIO_EV_DIR_SINGLETAP = 4 + IIO_EV_DIR_DOUBLETAP = 5 + +class Event(Structure): + """Represents an IIO event.""" + + _fields_ = [('id', c_uint64), ('timestamp', c_int64)] + + type = property( + lambda self: EventType((self.id >> 56) & 0xff), + None, + None, + "The type of the IIO event.\n\ttype=iio.EventType(Enum)", + ) + + direction = property( + lambda self: EventDirection((self.id >> 48) & 0x7f), + None, + None, + "The direction of the IIO event.\n\ttype=iio.EventDirection(Enum)", + ) + class ChannelModifier(Enum): """Contains the modifier type of an IIO channel.""" @@ -238,6 +284,8 @@ class ChannelType(Enum): _DevicePtr = _POINTER(_Device) _ChannelPtr = _POINTER(_Channel) _ChannelsMaskPtr = _POINTER(_ChannelsMask) +_EventPtr = _POINTER(Event) +_EventStreamPtr = _POINTER(_EventStream) _BufferPtr = _POINTER(_Buffer) _BlockPtr = _POINTER(_Block) _DataFormatPtr = _POINTER(DataFormat) @@ -400,6 +448,11 @@ class ChannelType(Enum): _d_get_attr.argtypes = (_DevicePtr, c_uint) _d_get_attr.errcheck = _check_null +_d_create_evstream = _lib.iio_device_create_event_stream +_d_create_evstream.restype = _EventStreamPtr +_d_create_evstream.argtypes = (_DevicePtr,) +_d_create_evstream.errcheck = _check_ptr_err + _d_debug_attr_count = _lib.iio_device_get_debug_attrs_count _d_debug_attr_count.restype = c_uint _d_debug_attr_count.argtypes = (_DevicePtr,) @@ -631,6 +684,18 @@ class ChannelType(Enum): _stream_get_next_block.argtypes = (_StreamPtr,) _stream_get_next_block.errcheck = _check_ptr_err +_evstream_destroy = _lib.iio_event_stream_destroy +_evstream_destroy.argtypes = (_EventStreamPtr,) + +_evstream_read = _lib.iio_event_stream_read +_evstream_read.restype = c_int +_evstream_read.argtypes = (_EventStreamPtr, _EventPtr, c_bool) +_evstream_read.errcheck = _check_negative + +_ev_get_channel = _lib.iio_event_get_channel +_ev_get_channel.restype = _ChannelPtr +_ev_get_channel.argtypes = (_EventPtr, _DevicePtr, c_bool) +_ev_get_channel.errcheck = _check_null # pylint: enable=invalid-name @@ -1082,6 +1147,56 @@ def __next__(self): return Block(self._buffer, self._block_size, next_hdl) +class EventStream(_IIO_Object): + """Class used to read IIO events. Cannot be instantiated directly.""" + + def __init__(self): + # Prevent the EventStream from being initialized manually. + raise NotImplementedError + + @classmethod + def _create(cls, device, stream): + self = cls.__new__(cls) + super(EventStream, self).__init__(stream, device) + + return self + + def read(self, nonblock=True): + """ + Read one event from the stream. + + :param nonblock: type=bool + If set, return None if there are no events in the stream. + If unset, it will wait until an event arrives. + + returns: type=(iio.Event, (iio.Channel, iio.Channel)) + The first element of the tuple is the Event read. + The second element is a tuple itself, that contains the + two channels related to the event. For non-differential + events the second channel will always be None. + """ + event = Event() + + try: + _evstream_read(self._hdl, _byref(event), nonblock) + except OSError as err: + if err.errno == 11: # EAGAIN + return None + + raise + + chn1 = None + chn2 = None + + try: + dev = self._parent + chn1 = Channel(dev, _ev_get_channel(_byref(event), dev._device, False)) + chn2 = Channel(dev, _ev_get_channel(_byref(event), dev._device, True)) + except OSError: + pass + + return (event, (chn1, chn2)) + class _DeviceOrTrigger(_IIO_Object): def __init__(self, _ctx, _device): super(_DeviceOrTrigger, self).__init__(_device, _ctx) @@ -1238,6 +1353,20 @@ def _get_trigger(self): return dev return None + @contextmanager + def event_stream(self): + """ + Create an events stream. + + returns: type=contextlib._GeneratorContextManager + A generator for a EventStream instance. + """ + try: + stream = _d_create_evstream(self._device) + yield EventStream._create(self, stream) + finally: + _evstream_destroy(stream) + trigger = property( _get_trigger, _set_trigger,